Для систем компании

API управления компанией

Кадровая, учётная или своя система компании делает через API то же, что админ в кабинете xWires: читает сотрудников, их подписки и заказы, подтверждает и отклоняет заказы, ведёт список почт и приглашения, отмечает уволившихся. Об изменениях она узнаёт из ленты событий и вебхуков.

Адрес APIhttps://api.xwires.ai/company/v1
Содержание

01Что умеет API

API для серверов компании, которая подключена к xWires: кадровой системы, учёта, согласований, своих сервисов. Почти всё, что система делает через API, админ компании может и в кабинете («Компания»); подписку по session сотрудника там оформляет оператор xWires:

В кабинетеЧерез API
Проверить ключ и узнать свою компаниюGET /me
«Сотрудники»: кто в компании, их подпискиGET /employeesGET /employees/{id}POST /employees/search
«Уволился» у сотрудника (не у админа)POST /employees/{id}/offboard
«Ждут подтверждения»: «Подтвердить» и «Отклонить»POST /orders/{id}/approvePOST /orders/{id}/reject
Подписка сотруднику по его session — в кабинете её оформляет оператор xWiresPOST /subscriptions
«Последние заказы»GET /ordersGET /orders/{id}
«Добавить почты», вручную или файломPOST /approved-emails
«Почты без заказа» и «Удалить»GET /approved-emailsDELETE /approved-emails/{id}
«Не оплачивать, только пригласить», приглашения и «Отозвать»POST /invitesGET /invitesDELETE /invites/{id}
Что изменилось: заказы, сотрудники, подпискиGET /events

Об изменениях API сообщает лентой событий, а вебхуки присылают их на адреса ваших систем (раздел 13). Только в кабинете остаются ключи API и адреса вебхуков, назначение и снятие админов, отметка «Уволился» у админа, ссылка для сотрудников и номера для подтверждения ChatGPT.

Это не API к моделям. OpenAI-совместимый API к DeepSeek, GLM и Kimi — другой продукт со своими ключами, он описан на странице «API к моделям». Ключ API управления к нему не подходит, и наоборот.

02Быстрый старт

  1. Админ компании открывает «Компания → Интеграции» и в «Ключах API» создаёт ключ: название, например «1С: кадры», и доступ — «Только чтение» или «Чтение и действия». Ключ показывается один раз, скопируйте его сразу. Всем админам компании придёт письмо о новом ключе.
  2. Положите ключ в секреты сервера, который ходит в API, под именем XWIRES_COMPANY_API_KEY. В код, репозиторий, браузер и мобильное приложение ключ не попадает.
  3. Проверьте ключ:
curl
curl -sS https://api.xwires.ai/company/v1/me \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Ответ 200
{
  "key": {
    "id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60",
    "name": "1С: кадры",
    "access": "manage",
    "last4": "a91f",
    "created_at": "2026-10-15T09:12:40.118204+00:00"
  },
  "company": {
    "id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
    "name": "Ромашка"
  }
}

Postman, Insomnia и генераторы клиентов берут описание API по ссылке: Import → Link → https://api.xwires.ai/company/v1/openapi.json. Ключ укажите в Authorization как Bearer Token. Статусы, типы событий и другие значения в ответах в v1 только добавляются, поэтому в описании они перечислены в x-extensible-enum, а не в закрытом enum: генератор делает из них строки, и новое значение не ломает разбор ответа. Событие нового типа подходит под схему UnknownEvent, последнюю в Event и WebhookEvent.

Тот же запрос и список заказов, которые ждут подтверждения, из кода:

Node.js 18+, модуль ES (.mjs)
const BASE = "https://api.xwires.ai/company/v1";

// Один запрос к API: ответ как объект, ошибка как исключение с status, code и request_id.
// error.retryAfter — через сколько секунд повторить, когда повтор поможет: 429 rate_limited, 503,
// сбой сети и ответ не от API (прокси, пока xWires перезапускается).
async function xwires(method, path, body) {
  let response;
  try {
    response = await fetch(BASE + path, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.XWIRES_COMPANY_API_KEY}`,
        ...(body === undefined ? {} : { "Content-Type": "application/json" }),
      },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
  } catch (cause) {
    throw Object.assign(new Error(`xWires не ответил: ${cause.message}`), { retryAfter: 5 });
  }
  if (response.status === 204) return null;
  const data = await response.json().catch(() => null);
  if (response.ok) return data;
  const retryAfter = Number(response.headers.get("Retry-After")) || null;
  if (!data?.error) {
    // Не ответ API: 405 у адреса без такого метода или 502 и 504 прокси, пока xWires перезапускается.
    throw Object.assign(new Error(`${response.status} без ответа API`), {
      status: response.status,
      retryAfter: retryAfter ?? (response.status >= 500 ? 5 : null),
    });
  }
  const { code, message, request_id } = data.error;
  throw Object.assign(new Error(`${response.status} ${code}: ${message} (request_id ${request_id})`), {
    status: response.status,
    code,
    retryAfter,
  });
}

const me = await xwires("GET", "/me");
console.log(me.company.name, me.key.access);

const waiting = await xwires("GET", "/orders?status=awaiting_approval");
for (const order of waiting.data) console.log(order.id, order.plan, order.employee?.email);
Python 3.9+
# pip install requests
import os

import requests

BASE = "https://api.xwires.ai/company/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["XWIRES_COMPANY_API_KEY"]


class XWiresError(Exception):
    # status и code — из ответа API. retry_after — через сколько секунд повторить, когда повтор поможет:
    # 429 rate_limited, 503, сбой сети и ответ не от API (прокси, пока xWires перезапускается).
    def __init__(self, message, status=None, code=None, retry_after=None):
        super().__init__(message)
        self.status = status
        self.code = code
        self.retry_after = retry_after


# Один запрос к API: ответ как словарь, ошибка как XWiresError с кодом и request_id.
def xwires(method, path, body=None, params=None):
    try:
        response = session.request(method, BASE + path, json=body, params=params, timeout=30)
    except requests.RequestException as error:
        raise XWiresError(f"xWires не ответил: {error}", retry_after=5) from error
    if response.status_code == 204:
        return None
    try:
        data = response.json()
    except ValueError:
        data = None
    if response.ok:
        return data
    header = response.headers.get("Retry-After", "")
    retry_after = int(header) if header.isdigit() else None
    if not isinstance(data, dict) or "error" not in data:
        # Не ответ API: 405 у адреса без такого метода или 502 и 504 прокси, пока xWires перезапускается.
        if retry_after is None and response.status_code >= 500:
            retry_after = 5
        raise XWiresError(f"{response.status_code} без ответа API", response.status_code, None, retry_after)
    error = data["error"]
    raise XWiresError(
        f'{response.status_code} {error["code"]}: {error["message"]} (request_id {error["request_id"]})',
        response.status_code,
        error["code"],
        retry_after,
    )


me = xwires("GET", "/me")
print(me["company"]["name"], me["key"]["access"])

waiting = xwires("GET", "/orders", params={"status": "awaiting_approval"})
for order in waiting["data"]:
    print(order["id"], order["plan"], (order["employee"] or {}).get("email"))

03Ключи и доступ

Ключ — xwc_ и 64 шестнадцатеричных символа, ключ песочницы — xwc_test_ и 64 (раздел 11). Передавайте его только в заголовке Authorization: Bearer xwc_…. Без ключа, с испорченным или незнакомым ключом ответ — 401 invalid_api_key.

ДоступЧто можно
Только чтениеВсе запросы на чтение (GET) и поиск сотрудника по почте (POST /employees/search).
Чтение и действияТо же и 9 действий: отметить уволившимся, отключить автопродление, подтвердить заказ, отклонить заказ, оформить подписку сотруднику, добавить почты, удалить почту из списка, пригласить и отозвать приглашение. Ключ только для чтения на них получает 403 api_key_read_only.

Доступ задают при выпуске, изменить его нельзя: выпустите новый ключ.

Ключ принадлежит компании

Ключ действует от имени компании, а не админа, который его выпустил, и работает, пока его не отзовут: и когда этого админа сняли с роли, отметили уволившимся или удалили, и когда у компании не осталось ни одного админа. В заказе, почте и приглашении, которые сделал ключ, записан ключ, а не человек.

Отзыв и смена

  • Отозвать ключ может любой админ компании в «Интеграциях» и оператор xWires, навсегда. Запросы, которые идут в момент отзыва, успеют завершиться, следующие получат 401 api_key_revoked.
  • Отзывая ключ, можно удалить и неиспользованные почты и приглашения, которые он добавил. Снимая человека с роли админа или отмечая уволившимся, админ может сразу отозвать выпущенные им ключи.
  • Сменить ключ: выпустите новый, переключите на него систему, отзовите старый. У компании может быть до 10 действующих ключей, так что старый и новый поработают вместе.
  • Уходит человек, у которого могла остаться копия ключа, — админ, разработчик интеграции, подрядчик: смените ключ.

Только с сервера

Запросы к API — только с ваших серверов: из браузера API не вызвать, заголовков CORS нет и cookies не читаются. В адресе запроса ключ не передавайте: адреса попадают в логи, такой запрос получит 400 api_key_in_url, а ключ стоит отозвать.

Свои действия

У заказа, который подтвердил ключ, approved_via: "api_key" и approved_by_api_key_id; у события, которое вызвал запрос ключа, — actor.api_key_id. Сравнивайте их с key.id из GET /me: так система отличит эхо своих действий от чужих.

В кабинете ключ виден как xwc_…a91f (последние символы — key.last4), с временем последнего успешного запроса, а «Что сделали ключи» показывает последние изменения через API. Чтение не записывается, IP-адреса мы не храним.

04Запросы и ответы

  • Тело запроса — JSON в UTF-8 с Content-Type: application/json, до 256 КБ, и приходит целиком за 10 секунд, иначе 415 unsupported_media_type, 413 payload_too_large или 408 request_timeout. Подтверждение, отказ и увольнение шлите без тела или с {}.
  • Ответ — JSON в UTF-8, кроме 204 без тела у удаления и отзыва. Ответы не кэшируются (Cache-Control: no-store).
  • Идентификаторы — uuid. Время — RFC 3339 (ISO 8601) со смещением +00:00, как его отдаёт база: 2026-10-15T09:30:12.345678+00:00. В доле секунды от 0 до 6 цифр: нули в конце база отбрасывает. Разбирайте время полным разбором RFC 3339 — Date.parse в Node.js, datetime.fromisoformat в Python 3.11+, dateutil.parser.isoparse в более ранних, — а не шаблоном фиксированной длины. Цен в API нет: стоимость подписок xWires согласует с компанией отдельно.
  • Поля, значения и типы событий в v1 только добавляются. Незнакомые поля и типы событий пропускайте, незнакомый статус заказа считайте «в работе».
  • Каждый ответ, и ошибка тоже, несёт заголовок X-Request-Id: назовите его поддержке, если что-то пошло не так. Ответы на запросы с ключом несут и RateLimit-* (раздел 8).
  • Почты в адресах не передаются: поиск сотрудника по почте — POST /employees/search с почтой в теле.

05Ошибки

Ошибка — JSON с кодом. Решения принимайте по code: message написан для человека и может меняться.

Ответ 409
{
  "error": {
    "code": "order_not_awaiting_approval",
    "message": "Заказ не ждёт подтверждения: его уже подтвердили, отклонили или отменили. Прочитайте заказ.",
    "request_id": "d2f6b8a4-9e1c-4a37-85d0-6c3b7e9f1a42"
  }
}

details есть только у invalid_request: пути полей и коды проверки, без присланных значений.

Ответ 400 на GET /orders?state=sent
{
  "error": {
    "code": "invalid_request",
    "message": "Запрос не прошёл проверку: details.issues называет поля и что с ними не так.",
    "request_id": "d2f6b8a4-9e1c-4a37-85d0-6c3b7e9f1a42",
    "details": {
      "issues": [
        {
          "path": "state",
          "issue": "unrecognized_keys"
        }
      ]
    }
  }
}
КодHTTPЧто значит и что делать
invalid_requestHTTP400Что значит и что делатьЗапрос не прошёл проверку: details.issues называет поля и что с ними не так.
invalid_sessionHTTP400Что значит и что делатьSession не подходит, details.issues называет почему: too_large — длиннее 32 КБ, wrong_shape — это не ответ chatgpt.com/api/auth/session (нет user.email, expires или accessToken), email_mismatch — accessToken от другого аккаунта, expired — истёк. Попросите сотрудника войти в свой ChatGPT, открыть chatgpt.com/api/auth/session и прислать ответ целиком.
invalid_cursorHTTP400Что значит и что делатьКурсор не подходит: он испорчен или получен с другими фильтрами. Начните список сначала, без cursor.
api_key_in_urlHTTP400Что значит и что делатьКлюч API передан в адресе запроса, а адреса попадают в логи. Отзовите этот ключ в кабинете и передавайте новый только в заголовке Authorization: Bearer.
invalid_emailHTTP400Что значит и что делатьПроверьте почту: нужен адрес вида name@example.ru.
invalid_planHTTP400Что значит и что делатьУкажите план, который оплачивает компания: plus, pro100, pro200, pro500.
session_token_requiredHTTP400Что значит и что делатьВ session нет sessionToken, а подписку сотрудника продлевают каждый месяц по session: пришлите ответ chatgpt.com/api/auth/session целиком.
session_expires_too_soonHTTP400Что значит и что делатьSession истечёт раньше, чем заказ уйдёт партнёру: продление уходит, когда закончится оплаченный период. Пришлите свежий session ближе к этой дате.
no_emailsHTTP400Что значит и что делатьПришлите хотя бы одну почту.
too_many_emailsHTTP400Что значит и что делатьНе больше 500 почт в одном запросе.
invalid_api_keyHTTP401Что значит и что делатьКлюч API не подходит: передайте его в заголовке Authorization: Bearer xwc_….
api_key_revokedHTTP401Что значит и что делатьКлюч API отозван. Выпустите новый в кабинете: Компания → Интеграции.
api_key_read_onlyHTTP403Что значит и что делатьКлюч API только для чтения: для этого запроса нужен ключ с правом действий.
not_foundHTTP404Что значит и что делатьТакого адреса в API нет: проверьте путь и метод.
order_not_foundHTTP404Что значит и что делатьЗаказа с таким id у компании нет.
employee_not_foundHTTP404Что значит и что делатьСотрудника с таким id в компании нет: возможно, он уже ушёл.
approved_email_not_foundHTTP404Что значит и что делатьПочты с таким id в списке нет: её уже удалили, или она уже подтвердила заказ.
invite_not_foundHTTP404Что значит и что делатьПриглашения с таким id нет: его уже отозвали или приняли.
request_timeoutHTTP408Что значит и что делатьТело запроса не пришло целиком за 10 секунд. Повторите запрос.
order_not_awaiting_approvalHTTP409Что значит и что делатьЗаказ не ждёт подтверждения: его уже подтвердили, отклонили или отменили. Прочитайте заказ.
already_employeeHTTP409Что значит и что делатьЧеловек с этой почтой уже сотрудник компании.
open_order_existsHTTP409Что значит и что делатьУ сотрудника уже есть открытый заказ: следите за ним в GET /orders?employee_id=… или в ленте событий. В песочнице его можно довести до конца (POST /sandbox/orders/{id}/advance) или создать другого сотрудника.
chatgpt_email_takenHTTP409Что значит и что делатьЭтот ChatGPT-аккаунт в xWires уже у другого человека: проверьте, что session от аккаунта сотрудника.
chatgpt_email_mismatchHTTP409Что значит и что делатьПодписка сотрудника — на другой ChatGPT-аккаунт, а продление идёт на тот же: пришлите session того аккаунта (subscription.chatgpt_email в GET /employees/{id}).
plan_mismatchHTTP409Что значит и что делатьПодписка сотрудника — на другой план, а продление идёт на тот же: укажите plan его подписки (GET /employees/{id}).
auto_renewal_not_onHTTP409Что значит и что делатьАвтопродление сотрудника не включено или его оплачивает не компания: отключать нечего.
auto_renewal_onHTTP409Что значит и что делатьПодписку сотрудника каждый месяц продлевает автопродление: новый заказ не нужен. Чтобы продлить вручную, сначала отключите его (stopAutoRenewal).
auto_renewal_closedHTTP409Что значит и что делатьАвтопродление для компании пока закрыто: подтвердите разовую активацию — без renewal или с renewal: "once".
cannot_offboard_adminHTTP409Что значит и что делатьАдмина компании через API уволившимся не отметить: это делают в кабинете.
membership_not_foundHTTP409Что значит и что делатьУ сотрудника нет подписки: заказ создаётся для придуманного сотрудника, POST /sandbox/employees.
sandbox_invalid_transitionHTTP409Что значит и что делатьИз нынешнего статуса заказ в этот не перевести: подтверждает и отклоняет компания, а из succeeded, failed, rejected и cancelled дальше пути нет. Прочитайте заказ.
subscription_not_activeHTTP409Что значит и что делатьПодписка сотрудника не активна: завершить нечего.
events_cursor_expiredHTTP410Что значит и что делатьСобытий сразу после этой позиции нет: мы храним их 30 дней, или ленту компании восстановили из резервной копии. Запросите after=latest и сохраните next_after, затем перечитайте списки и продолжайте с сохранённой позиции.
payload_too_largeHTTP413Что значит и что делатьТело запроса больше 256 КБ.
unsupported_media_typeHTTP415Что значит и что делатьТело запроса должно быть JSON с Content-Type: application/json.
rate_limitedHTTP429Что значит и что делатьСлишком много запросов с этим ключом: не больше 60 в минуту и 2 одновременно. Повторите после Retry-After.
api_email_quota_exceededHTTP429Что значит и что делатьЗа сутки через API прислали 5000 почт и приглашений компании. Повторите позже или добавьте почты в кабинете.
internal_errorHTTP500Что значит и что делатьВнутренняя ошибка xWires. Прочитайте данные GET-запросом и повторите; если ошибка не проходит, напишите в поддержку с request_id.
service_unavailableHTTP503Что значит и что делатьxWires сейчас не может ответить. Повторите через секунду: запрос можно безопасно повторить.
  • 405 без нашего JSON — у адреса нет такого метода.
  • 429: на rate_limited подождите столько секунд, сколько в Retry-After. У api_email_quota_exceeded заголовка нет: суточный лимит освобождается постепенно.
  • 500 — ошибка у нас: сначала прочитайте данные, потом повторяйте. Не проходит — напишите в поддержку с request_id. 503 приходит с Retry-After: 1: повторите через секунду.

06Списки и синхронизация

Страницы

Списки отдают до limit строк (от 1 до 200, по умолчанию 100) и next_cursor. Следующая страница — тот же запрос с cursor=<next_cursor>; null — страниц больше нет. Курсор непрозрачный, до 512 символов, и действует только с теми же фильтрами, иначе 400 invalid_cursor. Незнакомый или повторённый параметр — 400 invalid_request.

Сотрудники идут по id, заказы — по времени последнего изменения (updated_at, затем id), почты и приглашения — по времени добавления.

Как держать данные в актуальном виде

Главный способ узнавать об изменениях — лента событий GET /events (раздел 13): в ней те же события, что приходят на вебхуки.

  1. Запросите GET /events?after=latest и сохраните next_after, затем один раз прочитайте нужные списки целиком. Именно в таком порядке: изменения, сделанные, пока вы читаете списки, придут и в ленте, а обработчик, которому повтор не вредит, примет их ещё раз без последствий.
  2. Раз в 10–60 секунд или на каждый вебхук запрашивайте GET /events?after=<next_after>, обработайте события и сохраните новый next_after. Пока has_more: true, спрашивайте сразу; на 429 rate_limited и 503 service_unavailable подождите Retry-After и повторите.
  3. На 410 events_cursor_expired начните заново, как в первом шаге: сначала after=latest и next_after, потом списки, и продолжайте с сохранённой позиции.
  4. Раз в сутки, например ночью, читайте весь GET /employees для сверки.
Node.js: лента событий
// xwires() — из примера выше. loadAfter, saveAfter, handle и resyncFromLists — ваши:
// храните позицию в своей базе, обрабатывайте событие так, чтобы повтор с тем же id ничего не менял.
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));

let after = await loadAfter(); // null — ленту ещё не читали

for (;;) {
  try {
    if (after === null) {
      // Сначала позиция, потом списки: изменения, сделанные во время чтения, придут и в ленте.
      const { next_after } = await xwires("GET", "/events?after=latest");
      await resyncFromLists();
      after = String(next_after);
      await saveAfter(after);
    }
    const page = await xwires("GET", `/events?after=${after}&limit=200`);
    for (const event of page.data) await handle(event);
    after = String(page.next_after);
    await saveAfter(after);
    if (!page.has_more) await sleep(30);
  } catch (error) {
    if (error.code === "events_cursor_expired") {
      // Событий после позиции уже нет: они старше 30 дней, или базу xWires восстановили из копии.
      after = null;
    } else if (error.retryAfter) {
      // 429 и 503, сбой сети или прокси, пока xWires перезапускается: подождите и повторите.
      await sleep(error.retryAfter);
    } else {
      throw error;
    }
  }
}

Дозагрузить заказы помогает updated_since: берите его на минуту раньше последнего updated_at, который видели, и убирайте повторы по паре id и updated_at. + в смещении времени кодируйте как %2B.

curl
curl -sS "https://api.xwires.ai/company/v1/orders?status=succeeded&updated_since=2026-10-01T00:00:00%2B03:00" \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"

07Повторы

Заголовка Idempotency-Key нет: каждое действие и так безопасно повторить.

ДействиеЧто ответит повтор
POST /orders/{id}/approvePOST /orders/{id}/reject409 order_not_awaiting_approval
POST /approved-emailsПочты из списка и почты, которые уже подтвердили незавершённый заказ, считаются в skipped, но идут в суточный лимит.
POST /invites200 и created: false, письмо второй раз не уходит.
POST /subscriptions409 open_order_exists: заказ уже создан, найдите его в GET /orders?employee_id=…. Повтор после 202 снова ответит 202, письмо второй раз не уходит. Каждый повтор идёт в суточный лимит.
POST /employees/{id}/offboard404 employee_not_found
DELETE /approved-emails/{id}404 approved_email_not_found
DELETE /invites/{id}404 invite_not_found

После таймаута или ответа 5xx не повторяйте вслепую: сначала прочитайте заказ, список или сотрудника, потом решайте. Повтор не навредит, но ответ на него скажет больше, если знать, что уже сделано.

08Лимиты

ЧтоСколько
Запросы с одного ключа60 в минуту и 2 одновременно, лента событий тоже. Сверх — 429 rate_limited с Retry-After.
Тело запросадо 256 КБ, целиком за 10 секунд
Почт в одном запроседо 500
Почт и приглашений через API за сутки5000 у компании: каждая присланная почта, и та, что уже есть в списке, и каждое приглашение. Сверх — 429 api_email_quota_exceeded. Кабинет не считается.
Строк на страницеот 1 до 200, по умолчанию 100
Действующих ключей у компаниидо 10
Хранение событий30 дней
Адресов вебхуков у компаниидо 5
Типов событий у адресавсе, включая будущие, или от 1 до 19 выбранных
Адрес и описаниедо 2048 и до 100 символов
Новые адреса, смены адреса и секретадо 20 за сутки у компании
Тестовые событияне чаще раза в 10 секунд на адрес и до 30 в час у компании
Ответ на вебхук2xx за 10 секунд
Попытки доставки10 примерно за 22 часа; событие, не доставленное за 3 дня с первой попытки, больше не отправляется

RateLimit-Limit — сколько запросов с ключа можно за минуту, RateLimit-Remaining — сколько осталось, RateLimit-Reset — через сколько секунд минута начнётся заново. 429 rate_limited бывает и когда xWires занят запросами других компаний: просто подождите Retry-After.

09Сценарии

Новый сотрудник

Кадровая система добавляет почту нового сотрудника в список с планом, который оплачивает компания. Подходит почта, с которой он входит в xWires, или почта его аккаунта ChatGPT.

curl
curl -sS -X POST https://api.xwires.ai/company/v1/approved-emails \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emails":["ivan.sidorov@example.ru","maria.kuznetsova@example.ru"],"plan":"plus"}'
Ответ 200
{
  "approved": 1,
  "remembered": 1,
  "skipped": 0,
  "invalid": 0,
  "approved_order_ids": [
    "1d6f8a3c-7e2b-4c95-a0d4-6b9e1f3a7c52"
  ],
  "renews_monthly": false
}
  • approved: почта сразу подтвердила заказ, который уже ждал (approved_order_ids); remembered: почта ждёт заказа и подтвердит первый заказ этого плана; skipped: почта уже в списке, уже подтвердила незавершённый заказ этого плана или повторяется в запросе; invalid: не похоже на почту.
  • Почта подтверждает один заказ. Пока он не завершён, та же почта или другая почта того же сотрудника с этим планом пропускается, поэтому повтор запроса лишнего заказа не оплатит. Чтобы оплатить и следующий заказ, добавьте почту, когда этот завершится, или подтвердите следующий заказ, когда он придёт.
  • Если почта не принадлежит сотруднику компании, на неё уходит письмо: компания оплатит подписку, можно входить в кабинет, — и приглашение в компанию.
  • Почта и приглашение удаляются отдельно: DELETE /approved-emails/{id} не отзывает приглашение, для этого есть DELETE /invites/{id}. Принять в компанию, ничего не оплачивая, — POST /invites.
  • Заказ с renewal_mode: monthly почта подтверждает, только если её добавили с renews_monthly: true: это согласие компании, что подписка будет продлеваться каждый месяц за её счёт, пока её не отключат. renews_monthly ответа говорит, записано ли согласие: false при true в запросе значит, что подписки компании сейчас не продлеваются каждый месяц. Какие почты списка его несут, показывает GET /approved-emails.

Согласование в вашей системе

Заказ сотрудника ждёт подтверждения компании. Узнайте о нём из события order.awaiting_approval (вебхук или лента) или спрашивайте список раз в несколько минут:

curl
curl -sS "https://api.xwires.ai/company/v1/orders?status=awaiting_approval" \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"

renewal_mode заказа — что выбрал сотрудник, он есть и в событии: once — разовая активация на месяц, monthly — с автопродлением. Решает компания: без тела заказ подтверждается разовым, что бы ни выбрал сотрудник. {"renewal": "monthly"} — согласие компании, что подписка будет продлеваться каждый месяц за её счёт, пока её не отключат; его можно дать и заказу, где сотрудник выбрал разовую активацию. Автопродление открывает компании xWires; пока оно закрыто, ответ на monthly — 409 auto_renewal_closed. Окончательным режим станет, когда сотрудник пришлёт session: без его согласия на продление заказ будет разовым.

Когда в вашей системе решили, подтвердите или отклоните заказ:

curl: подтвердить
curl -sS -X POST https://api.xwires.ai/company/v1/orders/9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14/approve \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
curl: подтвердить заказ с renewal_mode: monthly
curl -sS -X POST https://api.xwires.ai/company/v1/orders/9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14/approve \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"renewal": "monthly"}'
curl: отклонить
curl -sS -X POST https://api.xwires.ai/company/v1/orders/9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14/reject \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Ответ 200 на подтверждение
{
  "id": "9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14",
  "status": "approved",
  "is_open": true,
  "kind": "renewal",
  "plan": "plus",
  "renewal_mode": "once",
  "origin": "manual",
  "created_at": "2026-10-15T09:28:04.551902+00:00",
  "updated_at": "2026-10-15T09:30:12.345678+00:00",
  "approved_at": "2026-10-15T09:30:12.345678+00:00",
  "approved_by_operator": false,
  "approved_via": "api_key",
  "approved_by_api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60",
  "scheduled_for": null,
  "completed_at": null,
  "employee": {
    "id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
    "email": "anna.petrova@example.ru",
    "name": "Анна Петрова",
    "chatgpt_email": "a.petrova@example.ru"
  }
}

renewal_mode ответа — режим, с которым компания подтвердила заказ. Окончательный режим виден с awaiting_check.

После подтверждения сотрудника просят прислать session ChatGPT, оператор xWires проверяет заказ и отправляет его партнёру. Если заказ тем временем решил админ в кабинете, ответ — 409 order_not_awaiting_approval: прочитайте заказ, чтобы узнать итог.

Подписка по session сотрудника

Сотрудник присылает в вашу систему session своего ChatGPT: войдя в свой аккаунт, открывает chatgpt.com/api/auth/session и копирует ответ целиком. Одним запросом система оформляет ему подписку, как оператор xWires в своём кабинете:

subscription.json
{
  "email": "oleg.smirnov@example.ru",
  "plan": "plus",
  "name": "Олег Смирнов",
  "session": {
    "user": {
      "id": "user-Xq3vT8LmN2",
      "name": "Олег Смирнов",
      "email": "o.smirnov@example.ru"
    },
    "expires": "2027-01-16T10:04:12.000Z",
    "accessToken": "eyJhbGciOiJSUzI1NiIs…",
    "sessionToken": "eyJhbGciOiJkaXIiLCJlbmMiOi…"
  }
}
curl
curl -sS -X POST https://api.xwires.ai/company/v1/subscriptions \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @subscription.json
Ответ 201
{
  "outcome": "ordered",
  "account_created": true,
  "order": {
    "id": "a4c7e1b9-5d2f-4a83-b6e0-2f9c7d1a3e58",
    "status": "awaiting_check",
    "is_open": true,
    "kind": "activation",
    "plan": "plus",
    "renewal_mode": "once",
    "origin": "manual",
    "created_at": "2026-10-18T10:15:31.402811+00:00",
    "updated_at": "2026-10-18T10:15:31.402811+00:00",
    "approved_at": "2026-10-18T10:15:31.402811+00:00",
    "approved_by_operator": false,
    "approved_via": "api_key",
    "approved_by_api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60",
    "scheduled_for": null,
    "completed_at": null,
    "employee": {
      "id": "5e8b2d94-3c7a-4f1e-9b06-8a2d4c6e1f37",
      "email": "oleg.smirnov@example.ru",
      "name": "Олег Смирнов",
      "chatgpt_email": "o.smirnov@example.ru"
    }
  }
}
  • Почты нет в xWires — создаётся учётная запись в компании (account_created: true): сотрудник входит кодом на эту почту и видит подписку. У сотрудника компании активная или истёкшая подписка продлевается на тот же аккаунт ChatGPT и план.
  • Заказ подтверждён ключом и ждёт проверки оператора xWires (awaiting_check), потом уходит партнёру; сотруднику приходит письмо. Почту ChatGPT мы берём из session.
  • Отправляя session, система заверяет, что сотрудник согласился передать его партнёру. Без поля renewal подписка разовая. С "renewal": "monthly" она будет продлеваться каждый месяц за счёт компании: система заверяет и согласие сотрудника на это. Так можно, только пока автопродление для компании открыто — иначе 409 auto_renewal_closed, — и в session нужен sessionToken — 400 session_token_required. Пока автопродление сотрудника включено, заказывать продление не нужно: 409 auto_renewal_on.
  • Если человек с этой почтой не в компании — личный клиент xWires или сотрудник другой компании, — или почта на домене другой компании, ответ 202 с outcome: invited. Ему уходит приглашение, почта ждёт в списке с планом, а session не сохраняется: приняв приглашение, человек сам оформит заказ, и почта его подтвердит.
  • Session не подошёл — 400 invalid_session, причина в details.issues; на expired попросите прислать свежий.

Автопродление

Подписку с автопродлением (заказ с renewal_mode: monthly) каждый месяц продлевает сам OpenAI за счёт компании, пока его не отключат. Состояние — в subscription.auto_renewal сотрудника (GET /employees/{id}): state, дата следующего продления next_renewal_at, почему выключено (stop_reason) и может ли компания его отключить (can_stop). Включается оно, когда заказ с автопродлением выполнен, — событие subscription.auto_renewal_started; отключается — subscription.auto_renewal_stopped с stop_reason.

curl: отключить автопродление
curl -sS -X POST https://api.xwires.ai/company/v1/employees/3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90/auto-renewal/stop \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Ответ 200
{
  "id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
  "email": "anna.petrova@example.ru",
  "name": "Анна Петрова",
  "role": "employee",
  "subscription": {
    "chatgpt_email": "a.petrova@example.ru",
    "plan": "plus",
    "status": "active",
    "expires_at": "2026-11-14T08:03:51.207114+00:00",
    "auto_renewal": {
      "state": "off",
      "next_renewal_at": null,
      "stop_reason": "api_key",
      "can_stop": false
    }
  }
}
  • Оплаченный месяц остаётся у сотрудника до expires_at, потом подписка не продлится. Продление у OpenAI xWires отменит сам, сотруднику и админам компании придёт письмо. Включить снова до expires_at нельзя; новый заказ с renewal: monthly уйдёт партнёру, когда оплаченный месяц кончится.
  • Отключить можно, только пока автопродление включено и платит за него ваша компания (can_stop: true), иначе 409 auto_renewal_not_on; повтор отвечает так же.
  • Отключить его может и сам сотрудник, админ компании в кабинете и оператор xWires: stop_reason скажет, кто. Увольнение отключает его само (stop_reason: offboarded).

Увольнение

Найдите сотрудника по почте и отметьте уволившимся:

curl: найти
curl -sS -X POST https://api.xwires.ai/company/v1/employees/search \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"anna.petrova@example.ru"}'
Ответ 200
{
  "data": [
    {
      "id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
      "email": "anna.petrova@example.ru",
      "name": "Анна Петрова",
      "role": "employee",
      "subscription": {
        "chatgpt_email": "a.petrova@example.ru",
        "plan": "plus",
        "status": "active",
        "expires_at": "2026-11-14T08:03:51.207114+00:00",
        "auto_renewal": {
          "state": "on",
          "next_renewal_at": "2026-11-14T08:03:51.207114+00:00",
          "stop_reason": null,
          "can_stop": true
        }
      }
    }
  ],
  "next_cursor": null
}
curl: отметить уволившимся
curl -sS -X POST https://api.xwires.ai/company/v1/employees/3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90/offboard \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Ответ 200
{
  "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
  "cancelled_order_ids": [
    "9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14"
  ]
}

Человек перестаёт быть сотрудником компании, а его заказы за счёт компании, которые ещё не ушли партнёру, отменяются (cancelled_order_ids). Заказ, который партнёру как раз передают (submitting), остаётся и доходит до итога: если партнёр его так и не получил, он отменится позже, и придёт order.cancelled. Админа так не отметить — 409 cannot_offboard_admin: это делают в кабинете. Автопродление его подписки за счёт компании отключается (subscription.auto_renewal_stopped с stop_reason: offboarded). Вернуть человека можно новым приглашением. Если у ушедшего мог остаться ключ API или секрет вебхука, смените их.

Отчёт для бухгалтерии

Выполненные заказы приходят событиями order.succeeded: подписка на них у адреса вебхука или фильтр ленты. Сумму и план берите из заказа: GET /orders/{id}.

curl
curl -sS "https://api.xwires.ai/company/v1/events?after=1041&types=order.succeeded" \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"

Или списком за период, по времени последнего изменения:

curl
curl -sS "https://api.xwires.ai/company/v1/orders?status=succeeded&updated_since=2026-10-01T00:00:00%2B03:00" \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"

10Статусы заказа и подписки

Заказ

statusВ кабинетеЧто значит
awaiting_approvalВ кабинетеЖдёт подтвержденияЧто значитСотрудник оформил заказ, компания должна подтвердить или отклонить его.
approvedВ кабинетеПодтверждён, ждём sessionЧто значитКомпания или оператор xWires подтвердили заказ; ждём от сотрудника session ChatGPT.
awaiting_checkВ кабинетеПроверяет операторЧто значитSession получен, оператор xWires проверяет заказ перед отправкой партнёру.
scheduledВ кабинетеЗапланированЧто значитЗаказ проверен и уйдёт партнёру в scheduled_for: продление — когда закончится оплаченный период.
submittingВ кабинетеОтправляетсяЧто значитЗаказ передан партнёру, оплата идёт; результата ещё нет.
sentВ кабинетеОтправлен партнёруЧто значитЗаказ у партнёра, ждём результат.
review_requiredВ кабинетеНа разборе у оператораЧто значитПартнёр не принял заказ, его разбирает оператор xWires.
succeededВ кабинетеВыполненЧто значитПодписка пополнена.
failedВ кабинетеНе выполненЧто значитПартнёр не выполнил заказ.
rejectedВ кабинетеОтклонёнЧто значитКомпания или оператор xWires отклонили заказ.
cancelledВ кабинетеОтменёнЧто значитЗаказ отменён: сотрудника отметили уволившимся или оператор xWires отменил заказ.

Статус может вернуться назад:

  • failed → sent: партнёр всё-таки повторил заказ;
  • awaiting_check → approved: оператор попросил у сотрудника новый session;
  • review_required → approved: оператор повторяет заказ, а session уже нет.

Из review_required заказ может уйти и в scheduled или sent, когда снова в работе. Незнакомый статус считайте заказом в работе.

kindВ кабинетеЧто значит
activationВ кабинетеАктивацияЧто значитПервый месяц подписки на этот аккаунт ChatGPT.
renewalВ кабинетеПродлениеЧто значитСледующий месяц той же подписки.
renewal_modeЧто значит
monthlyПодписку xWires продлевает каждый месяц за счёт компании, пока её не отключат. Заказ подтверждается только с согласием компании: renewal: monthly или почтой с renews_monthly: true.
onceОплачивается один месяц; следующий — новым заказом.

Подписка

statusВ кабинетеЧто значит
newВ кабинетеНе активированаЧто значитПлан и аккаунт ChatGPT выбраны, подписку ещё не оплачивали.
processingВ кабинетеОформляетсяЧто значитЗаказ у партнёра.
activeВ кабинетеАктивнаЧто значитПодписка действует до expires_at.
expiredВ кабинетеИстеклаЧто значитСрок прошёл, продления не было.
cancelledВ кабинетеОтмененаЧто значитОт подписки отказались.

Автопродление

auto_renewal.stateВ кабинетеЧто значит
onВ кабинетеВключеноЧто значитВключено: подписка продлится в next_renewal_at за счёт компании.
needs_sessionВ кабинетеНужен новый sessionЧто значитДля следующего продления нужен свежий session сотрудника. Появится вместе с самими продлениями.
in_progressВ кабинетеПродлеваемЧто значитПодписку продлеваем. Появится вместе с самими продлениями.
attentionВ кабинетеНа разбореЧто значитПродление разбирает оператор xWires. Появится вместе с самими продлениями.
offВ кабинетеВыключеноЧто значитВыключено или не включалось; почему выключено — в stop_reason.

Незнакомое состояние считайте включённым.

stop_reasonЧто значит
userОтключил сам сотрудник в кабинете.
company_adminОтключил админ компании в кабинете.
api_keyОтключила система компании: stopAutoRenewal.
operatorОтключил оператор xWires.
offboardedСотрудника отметили уволившимся: компания больше не платит.
payer_changedСменился плательщик: сотрудник перешёл в другую компанию или ушёл из неё.

Планы

planПлан
plusChatGPT Plus
pro100ChatGPT Pro 100
pro200ChatGPT Pro 200
pro500ChatGPT Pro 500

11Песочница

Интеграцию проверяйте в песочнице: это тот же API на тестовых данных, по адресу https://api.sandbox.xwires.ai/company/v1. В ней ничего не оплачивается и никому не пишется: заказы не уходят партнёру, письма не получают ни сотрудники, ни приглашённые. Данные песочницы отдельные и в рабочий xWires не попадают.

  1. Откройте sandbox.xwires.ai и войдите с любой почтой: код придёт на неё. У вас будет своя компания «Песочница», вы — её админ.
  2. В «Интеграциях» песочницы выпустите ключ, он начинается с xwc_test_, и, если нужны вебхуки, добавьте адрес. События приходят на него так же, как в рабочем xWires, подписаны так же и с того же IP.
  3. Направьте систему на адрес песочницы. Все запросы этого руководства работают и там, примеры — тоже: замените адрес и ключ.

Что уже есть в компании

СотрудникПланЗаказ
Анна СмирноваПланChatGPT PlusЗаказawaiting_approval
Борис ИвановПланChatGPT Pro 100Заказawaiting_approval
Вера КозловаПланChatGPT PlusЗаказapproved: ждёт session, автопилот его не трогает
Глеб НовиковПланChatGPT PlusЗаказsucceeded: подписка активна
Дина ОрловаПланChatGPT Pro 200Заказfailed: партнёр отказал

Это придуманные люди: их почты — под .invalid, войти за них нельзя. Подтвердите или отклоните заказы Анны и Бориса, как в рабочем API, и посмотрите, что придёт вашей системе.

Что в песочнице иначе

  • Ключ песочницы в рабочем API и рабочий ключ в песочнице получают 401 invalid_api_key, а в message сказано, куда этот ключ отправлять.
  • Шаги, которые в рабочем xWires делают сотрудник, оператор и партнёр, делает песочница: сама (автопилот) или по вашему запросу. В событиях этих шагов actor — null, как у действий людей, поэтому система, которая отбрасывает эхо своих запросов по actor.api_key_id, их увидит.
  • Лимиты, ошибки и форма ответов — те же, что в рабочем API.

Запросы песочницы

В рабочем API их нет: там они отвечают 404 not_found. Нужен ключ с правом действий. Те же действия есть кнопками в «Интеграциях» песочницы.

ЗапросЧто делает
POST /sandbox/employeesНовый сотрудник компании с подпиской в статусе new на план из тела: событие employee.joined. Его почты под .invalid, войти за него нельзя. Его заказ — POST /sandbox/orders.
POST /sandbox/ordersСотрудник заказывает месяц подписки, как в своём кабинете: заказ awaiting_approval, или сразу approved, если его почта в списке компании. Активация для новой подписки, продление для активной или истёкшей. Подтверждённый заказ доводит до succeeded автопилот, если его не остановить (POST /sandbox/orders/{id}/advance).
POST /sandbox/orders/{id}/advanceЗаказ идёт в статус to шаг за шагом, за сотрудника, оператора и партнёра, как в проде: approved → awaiting_check → scheduled → sent → succeeded или failed; review_required из scheduled, cancelled из awaiting_check и review_required, approved из awaiting_check. Каждый шаг — своё событие, без actor. Подтверждают и отклоняют заказ, как всегда, POST /orders/{id}/approve и /reject. После вызова автопилот этот заказ больше не трогает; to, равный нынешнему статусу, только останавливает его.
POST /sandbox/employees/{id}/expire-subscriptionАктивная подписка сотрудника истекает сейчас: событие subscription.expired. Его следующий заказ — продление. Тело пустое или {}.

Автопилот

Подтверждённый заказ, который вы не переводили вручную, песочница ведёт дальше сама: awaiting_check → scheduled → sent → succeeded, шаг примерно раз в 10 секунд, весь путь — меньше минуты. На каждый шаг — событие order.*, на успех — subscription.activated. Заказ, который вы перевели запросом POST /sandbox/orders/{id}/advance, автопилот больше не трогает. Чтобы он не тронул заказ и после подтверждения, переведите его в нынешний статус, {"to": "awaiting_approval"}, до подтверждения.

Пример: партнёр отказал

Новый сотрудник:

curl
curl -sS -X POST https://api.sandbox.xwires.ai/company/v1/sandbox/employees \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan":"plus"}'
Ответ 201
{
  "id": "5f3a9c21-7d4e-4b86-a1c2-9e8d7f6b5a43",
  "email": "employee-5f3a9c21@employees.sandbox.invalid",
  "name": "Мария Соколова",
  "role": "employee",
  "subscription": {
    "chatgpt_email": "employee-5f3a9c21@chatgpt.sandbox.invalid",
    "plan": "plus",
    "status": "new",
    "expires_at": null,
    "auto_renewal": {
      "state": "off",
      "next_renewal_at": null,
      "stop_reason": null,
      "can_stop": false
    }
  }
}

Его заказ, ваше подтверждение и отказ партнёра:

curl: заказ
curl -sS -X POST https://api.sandbox.xwires.ai/company/v1/sandbox/orders \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"employee_id":"5f3a9c21-7d4e-4b86-a1c2-9e8d7f6b5a43"}'
curl: подтвердить
curl -sS -X POST https://api.sandbox.xwires.ai/company/v1/orders/8e1b4d7a-2c9f-4a63-b5e0-3d7c1f9a2b68/approve \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
curl: отказ партнёра
curl -sS -X POST https://api.sandbox.xwires.ai/company/v1/sandbox/orders/8e1b4d7a-2c9f-4a63-b5e0-3d7c1f9a2b68/advance \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"failed"}'

Ваша система получит order.awaiting_approval, order.approved с actor вашего ключа, затем order.awaiting_check, order.scheduled, order.sent и order.failed без него. Завершить активную подписку и проверить subscription.expired — POST /sandbox/employees/{id}/expire-subscription.

12Справочник

Справочник собран из того же описания, что и openapi.json (OpenAPI 3.0.3), поэтому совпадает с тем, что API делает. Базовый адрес — https://api.xwires.ai/company/v1, у каждого запроса заголовок Authorization: Bearer с ключом.

Ключ

Сотрудники

GET/employees

Сотрудники

Текущие пользователи компании, админы тоже, с подписками, по id. Ушедших в списке нет.

Ключ: Только чтение

ПараметрГдеТипОписание
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница сотрудников. EmployeesPage
POST/employees/search

Найти сотрудника по почте

Ищет по почте входа и по почте ChatGPT-аккаунта, без учёта регистра: 0, 1 или 2 сотрудника. POST, чтобы почта не попадала в адреса и логи.

Ключ: Только чтение

Тело: SearchEmployeesRequest

200
Найденные сотрудники; next_cursor всегда null. EmployeesPage
POST/employees/{id}/offboard

Отметить уволившимся

Человек перестаёт быть сотрудником компании, его заказы за счёт компании, ещё не ушедшие партнёру, отменяются. Заказ, который партнёру как раз передают (submitting), остаётся и доходит до итога; если партнёр его так и не получил, он отменится позже, событием order.cancelled. Админа так не отметить (409 cannot_offboard_admin): это делают в кабинете. Тело пустое или {}. Автопродление его подписки за счёт компании отключается (subscription.auto_renewal_stopped, stop_reason offboarded). Вернуть человека можно новым приглашением.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid сотрудника.

Тело: пустое или {}

200
Сотрудник ушёл. OffboardResult
POST/employees/{id}/auto-renewal/stop

Отключить автопродление

Подписка сотрудника больше не продлевается каждый месяц за счёт компании: оплаченный месяц остаётся до expires_at, после него подписка истекает. Продление у OpenAI xWires отменит сам; сотруднику и админам компании придёт письмо, в ленте — событие subscription.auto_renewal_stopped с stop_reason api_key. Отключить можно, только пока auto_renewal.state сотрудника не off и платит ваша компания (auto_renewal.can_stop), иначе 409 auto_renewal_not_on, повтор тоже. Включить снова до expires_at нельзя; новый заказ с renewal: monthly уйдёт партнёру, когда оплаченный месяц кончится. Тело пустое или {}.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid сотрудника.

Тело: пустое или {}

200
Сотрудник после отключения. Employee

Заказы

GET/orders

Заказы

Заказы, которые оплачивает компания, также ушедших сотрудников (employee: null), по времени последнего изменения. Для синхронизации есть лента событий; updated_since — для дозагрузки: берите запас в минуту и убирайте повторы по id и updated_at. Курсор действует только с теми же фильтрами.

Ключ: Только чтение

ПараметрГдеТипОписание
statusГдев запросеТипstringОписаниеСтатусы через запятую, например awaiting_approval,approved. Значения: awaiting_approval, approved, awaiting_check, scheduled, submitting, sent, review_required, succeeded, failed, rejected, cancelled. (необязательное)
updated_sinceГдев запросеТипstringОписаниеЗаказы, изменённые в этот момент или позже: ISO 8601 со смещением, + кодируйте как %2B. (необязательное)
employee_idГдев запросеТипuuidОписаниеЗаказы одного сотрудника, пока он в компании. (необязательное)
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница заказов. OrdersPage
POST/orders/{id}/approve

Подтвердить заказ

Заказ в статусе awaiting_approval: компания его оплачивает, а сотрудника просят прислать session ChatGPT. В заказе записан ключ: approved_via api_key и approved_by_api_key_id. Тело пустое, {} или с renewal: monthly либо once; без renewal — once: согласия на ежемесячное продление API никогда не даёт само. monthly — согласие компании, что подписка будет продлеваться каждый месяц за её счёт, пока её не отключат; только пока автопродление для компании открыто, иначе 409 auto_renewal_closed. Компания может выбрать monthly и для заказа, где сотрудник выбрал разовую активацию. Окончательным режим становится, когда сотрудник прислал session и согласился на продление: без его согласия заказ разовый. Режим сейчас — renewal_mode ответа. Повтор — 409 order_not_awaiting_approval.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid заказа.

Тело: ApproveOrderRequest (необязательное)

200
Заказ после подтверждения. Order
POST/orders/{id}/reject

Отклонить заказ

Заказ в статусе awaiting_approval отклоняется, сотрудник видит это в кабинете. Тело пустое или {}. Повтор — 409 order_not_awaiting_approval.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid заказа.

Тело: пустое или {}

200
Заказ после отказа. Order
POST/subscriptions

Оформить подписку сотруднику

Месяц подписки за один запрос, как его оформляет оператор xWires: почта входа сотрудника, план и session его ChatGPT-аккаунта. Почты нет в xWires — создаём учётную запись в компании: войти в неё можно кодом на эту почту. Заказ подтверждён ключом (approved_via api_key) и ждёт проверки xWires (awaiting_check), затем уходит партнёру; сотруднику приходит письмо. Новая подписка активируется, активная или истёкшая продлевается на тот же ChatGPT-аккаунт и план. Отправляя session, ключ заверяет, что сотрудник согласился передать его партнёру. Если человек с этой почтой не в компании (личный клиент или сотрудник другой), или почта на домене другой компании — 202 invited: ему уходит приглашение, почта ждёт в списке с планом (с renewal: monthly — как renews_monthly), session не сохраняется. renewal: monthly — подписка с автопродлением, только пока оно открыто для компании: иначе 409 auto_renewal_closed, и ни заказа, ни приглашения. Заказ он сделает сам, приняв приглашение, и почта его подтвердит. Повтор после обрыва — 409 open_order_exists: найдите заказ в GET /orders?employee_id=…. Идёт в суточный лимит (5000 почт и приглашений через API).

Ключ: Чтение и действия

Тело: OrderSubscriptionRequest

201
Заказ создан и ждёт проверки xWires. OrderSubscriptionResult
202
Человек не в компании: приглашение и почта в списке, без заказа. OrderSubscriptionResult

Список почт

GET/approved-emails

Список почт

Почты, которые подтверждают заказы сотрудников, ещё не использованные, по времени добавления.

Ключ: Только чтение

ПараметрГдеТипОписание
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница списка. ApprovedEmailsPage
POST/approved-emails

Добавить почты

От 1 до 500 почт с планом, который оплачивает компания. Заказ этого плана от сотрудника с такой почтой входа или ChatGPT подтверждается сам; заказ, который уже ждёт, подтверждается сразу (approved_order_ids). Человеку, которого ещё нет в компании, уходит письмо. Почта с renews_monthly: true подтверждает заказ в режиме, который выбрал сотрудник, почта без согласия — разовым (renewal_mode once). Согласие записывается, только пока автопродление для компании открыто: записано ли оно, говорит renews_monthly ответа. Повтор безопасен: почты из списка и почты, которые уже подтвердили незавершённый заказ этого плана, считаются в skipped. В суточный лимит (5000 почт и приглашений через API) идёт каждая присланная почта.

Ключ: Чтение и действия

Тело: AddApprovedEmailsRequest

200
Что сделали почты. AddApprovedEmailsResult
DELETE/approved-emails/{id}

Удалить почту из списка

Почта больше не подтверждает заказы. Уже подтверждённые заказы остаются. Повтор — 404.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid почты в списке.
204
Почта удалена.

Приглашения

GET/invites

Приглашения

Приглашения, которые ещё не приняли, по времени. Входил ли приглашённый в xWires, API не говорит.

Ключ: Только чтение

ПараметрГдеТипОписание
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница приглашений. InvitesPage
POST/invites

Пригласить

Приглашает почту входа человека с личной почтой: он станет сотрудником, приняв приглашение в кабинете. Заказ не подтверждает: для этого добавьте почту в список. Новое приглашение — 201 и письмо человеку, существующее — 200 без письма. Идёт в суточный лимит (5000 почт и приглашений через API).

Ключ: Чтение и действия

Тело: CreateInviteRequest

200
Такое приглашение уже было. InviteResult
201
Новое приглашение. InviteResult

События

GET/events

Лента событий

Изменения заказов, сотрудников и подписок компании, те же, что приходят на вебхуки, по position. Храните next_after и спрашивайте after=<next_after> раз в 10–60 секунд или на каждый вебхук. after=0 — с самого старого хранимого события, after=latest — с текущего момента. 410 events_cursor_expired: событий сразу после этой позиции уже нет (они истекли, или базу xWires восстановили из копии): запросите after=latest и сохраните next_after, затем перечитайте списки и продолжайте с сохранённой позиции.

Ключ: Только чтение

ПараметрГдеТипОписание
afterГдев запросеТипstringОписание0 — с самого старого хранимого события, latest — только новые, число — после этой позиции (next_after). (необязательное, по умолчанию "0") Шаблон: ^(latest|0|[1-9][0-9]{0,17})$
typesГдев запросеТипstringОписаниеТипы событий через запятую; без него — все. Значения: order.awaiting_approval, order.approved, order.awaiting_check, order.scheduled, order.submitting, order.sent, order.succeeded, order.failed, order.review_required, order.rejected, order.cancelled, employee.joined, employee.left, employee.role_changed, subscription.activated, subscription.renewed, subscription.expired, subscription.auto_renewal_started, subscription.auto_renewal_stopped. (необязательное)
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
200
Страница ленты. EventsPage

Песочница

POST/sandbox/employees

Придуманный сотрудник

Только в песочнице. Новый сотрудник компании с подпиской в статусе new на план из тела: событие employee.joined. Его почты под .invalid, войти за него нельзя. Его заказ — POST /sandbox/orders.

Ключ: Чтение и действия

Тело: CreateSandboxEmployeeRequest

201
Сотрудник. Employee
POST/sandbox/orders

Заказ сотрудника

Только в песочнице. Сотрудник заказывает месяц подписки, как в своём кабинете: заказ awaiting_approval, или сразу approved, если его почта в списке компании. Активация для новой подписки, продление для активной или истёкшей. Подтверждённый заказ доводит до succeeded автопилот, если его не остановить (POST /sandbox/orders/{id}/advance).

Ключ: Чтение и действия

Тело: CreateSandboxOrderRequest

201
Заказ. Order
POST/sandbox/orders/{id}/advance

Перевести заказ

Только в песочнице. Заказ идёт в статус to шаг за шагом, за сотрудника, оператора и партнёра, как в проде: approved → awaiting_check → scheduled → sent → succeeded или failed; review_required из scheduled, cancelled из awaiting_check и review_required, approved из awaiting_check. Каждый шаг — своё событие, без actor. Подтверждают и отклоняют заказ, как всегда, POST /orders/{id}/approve и /reject. После вызова автопилот этот заказ больше не трогает; to, равный нынешнему статусу, только останавливает его.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid заказа.

Тело: AdvanceSandboxOrderRequest

200
Заказ в новом статусе. Order
POST/sandbox/employees/{id}/expire-subscription

Завершить подписку

Только в песочнице. Активная подписка сотрудника истекает сейчас: событие subscription.expired. Его следующий заказ — продление. Тело пустое или {}.

Ключ: Чтение и действия

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid сотрудника.

Тело: пустое или {}

200
Сотрудник с истёкшей подпиской. Employee

Схемы

Поля ответа обязательны и приходят всегда, иногда со значением null; «необязательное» — поле, которого может не быть. В v1 поля только добавляются.

Me

Ключ, которым сделан запрос, и его компания.

ПолеТипОписание
keyТипobjectОписание
key.idТипuuidОписаниеid ключа: его же несут actor.api_key_id событий и approved_by_api_key_id заказов, которые он подтвердил.
key.nameТипstringОписаниеНазвание, которое дал ключу админ.
key.accessТип"read" | "manage" | …Описаниеread: только чтение; manage: чтение и действия.
key.last4ТипstringОписаниеПоследние 4 символа ключа: в кабинете он показан как xwc_…last4. Шаблон: ^[0-9a-f]{4}$
key.created_atТипdate-timeОписаниеКогда ключ выпустили.
companyТипobjectОписание
company.idТипuuidОписаниеid компании.
company.nameТипstringОписаниеНазвание компании.

AutoRenewal

Автопродление подписки: каждый месяц её продлевает сам OpenAI за счёт плательщика, пока его не отключат.

ПолеТипОписание
stateТип"on" | "needs_session" | "in_progress" | "attention" | "off" | …Описаниеon: включено; off: выключено или не включалось; needs_session, in_progress, attention — появятся вместе с самими продлениями: нужен новый session, продлеваем, на разборе у xWires. Незнакомое значение считайте включённым.
next_renewal_atТипdate-time | nullОписаниеКогда подписка продлится; null, если автопродление выключено.
stop_reasonТип"user" | "company_admin" | "api_key" | "operator" | "offboarded" | "payer_changed" | … | nullОписаниеПочему выключено: user — сотрудник, company_admin — админ компании, api_key — система компании, operator — оператор xWires, offboarded — сотрудник ушёл, payer_changed — сменился плательщик. null, если включено или не включалось.
can_stopТипbooleanОписаниеКомпания может отключить его (stopAutoRenewal): оно включено, и платит она.

Subscription

Подписка ChatGPT сотрудника.

ПолеТипОписание
chatgpt_emailТипstringОписаниеПочта ChatGPT-аккаунта, который пополняется.
planТип"plus" | "pro100" | "pro200" | "pro500" | …ОписаниеПлан подписки.
statusТип"new" | "processing" | "active" | "expired" | "cancelled" | …Описаниеnew: план выбран, ещё не оплачен; processing: заказ у партнёра; active: действует; expired: срок прошёл; cancelled: отказ.
expires_atТипdate-time | nullОписаниеДо какого момента оплачена; null, пока её ни разу не активировали.
auto_renewalТипAutoRenewalОписание

Employee

Текущий пользователь компании, админ тоже.

ПолеТипОписание
idТипuuidОписаниеid сотрудника.
emailТипstringОписаниеПочта, с которой он входит в xWires.
nameТипstring | nullОписаниеИмя, если сотрудник его указал.
roleТип"employee" | "admin" | …Описаниеemployee или admin.
subscriptionТипSubscription | nullОписаниеПодписка; null, пока сотрудник не выбрал план и ChatGPT-аккаунт.

OrderEmployee

Сотрудник, чей это заказ, пока он в компании.

ПолеТипОписание
idТипuuidОписаниеid сотрудника.
emailТипstringОписаниеПочта входа.
nameТипstring | nullОписаниеИмя, если указано.
chatgpt_emailТипstringОписаниеПочта ChatGPT-аккаунта подписки.

Order

Заказ на месяц подписки, который оплачивает компания.

ПолеТипОписание
idТипuuidОписаниеid заказа.
statusТип"awaiting_approval" | "approved" | "awaiting_check" | "scheduled" | "submitting" | "sent" | "review_required" | "succeeded" | "failed" | "rejected" | "cancelled" | …ОписаниеСтатус. Может вернуться назад: failed → sent, awaiting_check → approved, review_required → approved. Незнакомый статус считайте заказом в работе.
is_openТипbooleanОписаниеЗаказ ещё не завершён: у сотрудника не может быть второго открытого.
kindТип"activation" | "renewal" | …Описаниеactivation: первый месяц; renewal: продление.
planТип"plus" | "pro100" | "pro200" | "pro500" | …ОписаниеПлан.
renewal_modeТип"monthly" | "once" | …Описаниеmonthly: подписка будет продлеваться каждый месяц за счёт компании, пока её не отключат; once: оплачивается один месяц. Пока заказ ждёт подтверждения — выбор сотрудника; после подтверждения — выбор компании (renewal в approveOrder); окончательный — с awaiting_check: monthly, только если и сотрудник согласился на продление, присылая session, иначе once.
originТип"manual" | "auto_renewal" | …Описаниеmanual: заказ оформили сотрудник, компания или оператор; auto_renewal: продление по автопродлению.
created_atТипdate-timeОписаниеКогда заказ создан.
updated_atТипdate-timeОписаниеПоследнее изменение: по нему идут список и фильтр updated_since.
approved_atТипdate-time | nullОписаниеКогда подтверждён; null, пока нет.
approved_by_operatorТипbooleanОписаниеПодтвердил оператор xWires за компанию.
approved_viaТип"company_admin" | "operator" | "api_key" | … | nullОписаниеКто подтвердил, кнопкой или почтой из списка: company_admin, operator или api_key; null, пока никто.
approved_by_api_key_idТипuuid | nullОписаниеКлюч, который подтвердил заказ или добавил почту, которая его подтвердила.
scheduled_forТипdate-time | nullОписаниеКогда заказ уйдёт партнёру; null, пока не назначено.
completed_atТипdate-time | nullОписаниеКогда партнёр выполнил заказ (succeeded) или отказал в нём (failed); null у остальных статусов, также у rejected и cancelled. Закрыт ли заказ, показывает is_open.
employeeТипOrderEmployee | nullОписаниеnull, если сотрудник уже ушёл из компании.

ApprovedEmail

Почта из списка компании, ещё не подтвердившая заказ.

ПолеТипОписание
idТипuuidОписаниеid почты в списке.
emailТипstringОписаниеПочта, в нижнем регистре.
planТип"plus" | "pro100" | "pro200" | "pro500" | … | nullОписаниеПлан, заказ которого она подтверждает; null — любой (почты, добавленные до планов).
renews_monthlyТипbooleanОписаниеПочта несёт согласие компании, что подписка будет продлеваться каждый месяц за её счёт: она подтверждает заказ в режиме, который выбрал сотрудник, почта без согласия — разовым.
created_atТипdate-timeОписаниеКогда добавлена.
added_by_operatorТипbooleanОписаниеДобавил оператор xWires.

Invite

Приглашение, которое ещё не приняли.

ПолеТипОписание
idТипuuidОписаниеid приглашения.
emailТипstringОписаниеПриглашённая почта входа, в нижнем регистре.
created_atТипdate-timeОписаниеКогда приглашён.

EmployeesPage

Страница сотрудников, по id.

ПолеТипОписание
dataТипEmployee[]Описание
next_cursorТипstring | nullОписаниеПередайте его в cursor, чтобы получить следующую страницу; null — страниц больше нет.

OrdersPage

Страница заказов, по updated_at и id.

ПолеТипОписание
dataТипOrder[]Описание
next_cursorТипstring | nullОписаниеПередайте его в cursor, чтобы получить следующую страницу; null — страниц больше нет.

ApprovedEmailsPage

Страница списка почт, по created_at и id.

ПолеТипОписание
dataТипApprovedEmail[]Описание
next_cursorТипstring | nullОписаниеПередайте его в cursor, чтобы получить следующую страницу; null — страниц больше нет.

InvitesPage

Страница приглашений, по created_at и id.

ПолеТипОписание
dataТипInvite[]Описание
next_cursorТипstring | nullОписаниеПередайте его в cursor, чтобы получить следующую страницу; null — страниц больше нет.

AddApprovedEmailsResult

Что сделали присланные почты.

ПолеТипОписание
approvedТипintegerОписаниеСколько почт сразу подтвердили ждущий заказ.
rememberedТипintegerОписаниеСколько почт добавлены в список и ждут заказа.
skippedТипintegerОписаниеСколько уже были в списке, уже подтвердили незавершённый заказ этого плана или повторялись в запросе.
invalidТипintegerОписаниеСколько не похожи на почту.
approved_order_idsТипuuid[]ОписаниеЗаказы, которые подтвердились сразу.
renews_monthlyТипbooleanОписаниеПочты запроса несут согласие на ежемесячное продление. false при renews_monthly: true в запросе — автопродление для компании пока закрыто, и согласие не записано.

InviteResult

Приглашение и создано ли оно этим запросом.

ПолеТипОписание
inviteТипInviteОписание
createdТипbooleanОписаниеtrue: новое, человеку ушло письмо; false: такое приглашение уже было.

OrderSubscriptionResult

Что сделал запрос: заказ сотрудника или приглашение человеку вне компании.

ПолеТипОписание
outcomeТип"ordered" | "invited" | …Описаниеordered (201): заказ создан и ждёт проверки xWires; invited (202): человек не в компании, ему ушло приглашение, почта добавлена в список с планом, session не сохранён.
account_createdТипbooleanОписаниеtrue: учётную запись xWires для этой почты создал этот запрос; у invited всегда false.
orderТипOrder | nullОписаниеЗаказ; null у invited.

OffboardResult

Сотрудник ушёл; какие его заказы отменены.

ПолеТипОписание
employee_idТипuuidОписаниеid ушедшего сотрудника.
cancelled_order_idsТипuuid[]ОписаниеЗаказы за счёт компании, отменённые потому, что ещё не ушли партнёру.

EventActor

Ключ компании, чей запрос к API вызвал событие; null — кто угодно другой: админ, сотрудник, xWires. Сравните с key.id из GET /me, чтобы узнать свои действия.

ПолеТипОписание
api_key_idТипuuidОписание

OrderEvent

order.<status>: заказ компании перешёл в статус status.

ПолеТипОписание
idТипuuidОписаниеid события; он же заголовок webhook-id: повторы отбрасывайте по нему.
timestampТипdate-timeОписаниеКогда это случилось.
positionТипintegerОписаниеНомер события в компании, без пропусков по всем типам; в одном изменении номера могут идти не подряд.
company_idТипuuidОписаниеid компании.
actorТипEventActor | nullОписание
typeТип"order.awaiting_approval" | "order.approved" | "order.awaiting_check" | "order.scheduled" | "order.submitting" | "order.sent" | "order.succeeded" | "order.failed" | "order.review_required" | "order.rejected" | "order.cancelled" | …Описание
dataТипobjectОписание
data.order_idТипuuidОписаниеid заказа: подробности — GET /orders/{id}.
data.statusТип"awaiting_approval" | "approved" | "awaiting_check" | "scheduled" | "submitting" | "sent" | "review_required" | "succeeded" | "failed" | "rejected" | "cancelled" | …ОписаниеСтатус после изменения.
data.previous_statusТип"awaiting_approval" | "approved" | "awaiting_check" | "scheduled" | "submitting" | "sent" | "review_required" | "succeeded" | "failed" | "rejected" | "cancelled" | … | nullОписаниеСтатус до; null, если заказ только что стал заказом компании.
data.kindТип"activation" | "renewal" | …Описание
data.planТип"plus" | "pro100" | "pro200" | "pro500" | …Описание
data.renewal_modeТип"monthly" | "once" | …ОписаниеРежим продления после изменения, как renewal_mode в Order.
data.employee_idТипuuid | nullОписаниеСотрудник; null, если он уже ушёл из компании.
data.approved_viaТип"company_admin" | "operator" | "api_key" | … | nullОписаниеКто подтвердил заказ, как в Order.
data.approved_by_api_key_idТипuuid | nullОписаниеКлюч, который подтвердил заказ или добавил подтвердившую его почту.

EmployeeEvent

employee.joined, employee.left, employee.role_changed: человек пришёл в компанию, ушёл или сменил роль.

ПолеТипОписание
idТипuuidОписаниеid события; он же заголовок webhook-id: повторы отбрасывайте по нему.
timestampТипdate-timeОписаниеКогда это случилось.
positionТипintegerОписаниеНомер события в компании, без пропусков по всем типам; в одном изменении номера могут идти не подряд.
company_idТипuuidОписаниеid компании.
actorТипEventActor | nullОписание
typeТип"employee.joined" | "employee.left" | "employee.role_changed" | …Описание
dataТипobjectОписание
data.employee_idТипuuidОписаниеid сотрудника.
data.roleТип"employee" | "admin" | …ОписаниеРоль после изменения; у ушедшего — роль, с которой он ушёл.
data.previous_roleТип"employee" | "admin" | … | nullОписаниеРоль до; только у employee.role_changed. (необязательное)

SubscriptionEvent

subscription.activated, subscription.renewed, subscription.expired, subscription.auto_renewal_started, subscription.auto_renewal_stopped: подписка сотрудника, который сейчас в компании.

ПолеТипОписание
idТипuuidОписаниеid события; он же заголовок webhook-id: повторы отбрасывайте по нему.
timestampТипdate-timeОписаниеКогда это случилось.
positionТипintegerОписаниеНомер события в компании, без пропусков по всем типам; в одном изменении номера могут идти не подряд.
company_idТипuuidОписаниеid компании.
actorТипEventActor | nullОписание
typeТип"subscription.activated" | "subscription.renewed" | "subscription.expired" | "subscription.auto_renewal_started" | "subscription.auto_renewal_stopped" | …Описание
dataТипobjectОписание
data.employee_idТипuuidОписаниеid сотрудника.
data.statusТип"new" | "processing" | "active" | "expired" | "cancelled" | …ОписаниеСтатус подписки после изменения.
data.previous_statusТип"new" | "processing" | "active" | "expired" | "cancelled" | … | nullОписаниеСтатус до.
data.planТип"plus" | "pro100" | "pro200" | "pro500" | …Описание
data.expires_atТипdate-time | nullОписаниеДо какого момента оплачена.
data.stop_reasonТип"user" | "company_admin" | "api_key" | "operator" | "offboarded" | "payer_changed" | …ОписаниеТолько у subscription.auto_renewal_stopped: почему выключено, как stop_reason в AutoRenewal. (необязательное)

UnknownEvent

Событие типа, которого в этом описании ещё нет: в v1 типы только добавляются. Конверт тот же, что у других событий; такое событие пропустите.

ПолеТипОписание
idТипuuidОписаниеid события; он же заголовок webhook-id: повторы отбрасывайте по нему.
timestampТипdate-timeОписаниеКогда это случилось.
positionТипinteger | nullОписаниеНомер события в компании; null — у события только для вебхуков, как у webhook.test.
company_idТипuuidОписаниеid компании.
actorТипEventActor | nullОписание
typeТипstringОписаниеТип события.
dataТипobjectОписаниеДанные события, свои у каждого типа.

Event

Событие компании: в ленте GET /events и в теле вебхука. Незнакомые типы и поля пропускайте.

Одна из схем, по полю type: OrderEvent | EmployeeEvent | SubscriptionEvent | UnknownEvent

WebhookTestEvent

webhook.test: тестовое событие, которое админ отправил из кабинета. Только в вебхуках, не в ленте.

ПолеТипОписание
idТипuuidОписаниеid тестовой отправки; он же webhook-id.
typeТип"webhook.test" | …Описание
timestampТипdate-timeОписаниеКогда отправлено.
positionТип… | nullОписание
company_idТипuuidОписаниеid компании.
actorТип… | nullОписание
dataТипobjectОписание
data.endpoint_idТипuuidОписаниеАдрес вебхука, который проверяли.

WebhookEvent

Тело вебхука: событие компании или тестовое.

Одна из схем, по полю type: OrderEvent | EmployeeEvent | SubscriptionEvent | WebhookTestEvent | UnknownEvent

EventsPage

Страница ленты событий, по position.

ПолеТипОписание
dataТипEvent[]Описание
next_afterТипintegerОписаниеПередайте его в after следующего запроса. Без новых событий он равен последней позиции компании.
has_moreТипbooleanОписаниеtrue: за next_after уже есть события, спросите сразу.

Error

Ошибка. Решения принимайте по code; message — для человека.

ПолеТипОписание
errorТипobjectОписание
error.codeТип"invalid_request" | "invalid_session" | "invalid_cursor" | "api_key_in_url" | "not_found" | "request_timeout" | "payload_too_large" | "unsupported_media_type" | "rate_limited" | "internal_error" | "service_unavailable" | "invalid_api_key" | "api_key_revoked" | "api_key_read_only" | "invalid_email" | "invalid_plan" | "session_token_required" | "session_expires_too_soon" | "no_emails" | "too_many_emails" | "order_not_found" | "employee_not_found" | "approved_email_not_found" | "invite_not_found" | "order_not_awaiting_approval" | "already_employee" | "open_order_exists" | "chatgpt_email_taken" | "chatgpt_email_mismatch" | "plan_mismatch" | "auto_renewal_not_on" | "auto_renewal_on" | "auto_renewal_closed" | "cannot_offboard_admin" | "events_cursor_expired" | "api_email_quota_exceeded" | "membership_not_found" | "sandbox_invalid_transition" | "subscription_not_active" | …Описание
error.messageТипstringОписаниеЧто случилось и что делать, по-русски.
error.request_idТипuuidОписаниеОн же заголовок X-Request-Id: назовите его поддержке.
error.detailsТипobjectОписаниеУ invalid_request: какие поля не прошли проверку; у invalid_session: path session и причина. Значения не повторяются. (необязательное)
error.details.issuesТипobject[]Описание
error.details.issues[].pathТипstringОписаниеПоле, через точку; пусто — весь запрос.
error.details.issues[].issueТипstringОписаниеКод проблемы zod (invalid_type, too_big, unrecognized_keys…) или у invalid_session — причина: too_large, wrong_shape, email_mismatch, expired.

SearchEmployeesRequest

Почта для поиска.

ПолеТипОписание
emailТипstringОписаниеПочта входа или ChatGPT-аккаунта, в любом регистре. (до 320 символов)

AddApprovedEmailsRequest

Почты и план, который оплачивает компания.

ПолеТипОписание
emailsТипstring[]ОписаниеПочты входа или ChatGPT-аккаунтов сотрудников, в любом регистре; повторы считаются в skipped. (от 1 до 500 элементов)
planТип"plus" | "pro100" | "pro200" | "pro500"ОписаниеПлан: почта подтверждает заказ только этого плана.
renews_monthlyТипbooleanОписаниеКомпания согласна, что подписка, которую подтвердит почта, будет продлеваться каждый месяц за её счёт, пока её не отключат. Почта с согласием подтверждает заказ в режиме, который выбрал сотрудник, без него — разовым. Согласие записывается, только пока автопродление для компании открыто: записано ли оно, говорит renews_monthly ответа. (необязательное)

CreateInviteRequest

Почта, которую пригласить.

ПолеТипОписание
emailТипstringОписаниеПочта, с которой человек входит или войдёт в xWires. (до 320 символов)

ApproveOrderRequest

Подтверждение заказа; тело может быть пустым.

ПолеТипОписание
renewalТип"monthly" | "once"Описаниеmonthly — компания согласна, что подписка будет продлеваться каждый месяц за её счёт, пока её не отключат; once — разовая активация. Без поля — once, что бы ни выбрал сотрудник. monthly — только пока автопродление для компании открыто, иначе 409 auto_renewal_closed. Окончательно заказ станет monthly, когда сотрудник согласится на продление, присылая session. (необязательное)

OrderSubscriptionRequest

Сотрудник, план и session его ChatGPT-аккаунта.

ПолеТипОписание
emailТипstringОписаниеПочта, с которой человек входит или войдёт в xWires, в любом регистре. (до 320 символов)
planТип"plus" | "pro100" | "pro200" | "pro500"ОписаниеПлан, который оплачивает компания.
sessionТипobjectОписаниеОтвет https://chatgpt.com/api/auth/session целиком, JSON-объектом: его открывает сотрудник, войдя в свой ChatGPT-аккаунт. Почта аккаунта берётся из него. Мы храним его зашифрованным и передаём только партнёру, который оплачивает подписку.
nameТипstringОписаниеИмя для учётной записи, если её ещё нет; имя существующей не меняется. (необязательное, 1–100 символов)
renewalТип"monthly" | "once"Описаниеmonthly — компания согласна, что подписка будет продлеваться каждый месяц за её счёт, пока её не отключат, а сотрудник — что для этого хранится его session; once — разовая активация. Без поля — once. monthly — только пока автопродление для компании открыто, иначе 409 auto_renewal_closed. (необязательное)

CreateSandboxEmployeeRequest

Придуманный сотрудник песочницы.

ПолеТипОписание
nameТипstringОписаниеИмя; без него — одно из придуманных. (необязательное, 1–100 символов)
planТип"plus" | "pro100" | "pro200" | "pro500"ОписаниеПлан его подписки: его заказы будут на этот план.

CreateSandboxOrderRequest

Чей заказ.

ПолеТипОписание
employee_idТипuuidОписаниеid сотрудника с подпиской и без открытого заказа.

AdvanceSandboxOrderRequest

Куда перевести заказ.

ПолеТипОписание
toТип"awaiting_approval" | "approved" | "awaiting_check" | "scheduled" | "submitting" | "sent" | "review_required" | "succeeded" | "failed" | "rejected" | "cancelled"ОписаниеСтатус, в который заказ придёт шаг за шагом: approved, awaiting_check, scheduled, sent, review_required, succeeded, failed или cancelled. Нынешний статус только останавливает автопилот.

Заголовки ответа

ЗаголовокЧто значит
X-Request-Idid запроса: назовите его поддержке.
RateLimit-LimitСколько запросов с ключа можно за минуту.
RateLimit-RemainingСколько из них осталось в этой минуте.
RateLimit-ResetЧерез сколько секунд минута начнётся заново.
Retry-AfterЧерез сколько секунд повторить.

13Лента событий и вебхуки

События — это изменения заказов, сотрудников и подписок компании. Их можно читать лентой GET /events и получать вебхуками на адреса HTTPS ваших систем. В событии только идентификаторы, статусы, план, вид заказа, роль, время и ключ, если действовал он; подробности система берёт через API.

Типы

typeЧто случилось
order.awaiting_approvalЗаказ ждёт подтверждения
order.approvedЗаказ подтверждён
order.awaiting_checkЗаказ проверяет оператор
order.scheduledЗаказ запланирован
order.submittingЗаказ отправляется партнёру
order.sentЗаказ отправлен партнёру
order.succeededЗаказ выполнен
order.failedЗаказ не выполнен
order.review_requiredЗаказ на разборе у оператора
order.rejectedЗаказ отклонён
order.cancelledЗаказ отменён
employee.joinedСотрудник присоединился
employee.leftСотрудник ушёл
employee.role_changedРоль сотрудника изменилась
subscription.activatedПодписка активирована
subscription.renewedПодписка продлена
subscription.expiredПодписка истекла
subscription.auto_renewal_startedАвтопродление включено
subscription.auto_renewal_stoppedАвтопродление выключено
webhook.testТестовое событие из кабинета, только в вебхуках
  • order.<status> — заказ, который оплачивает компания, перешёл в этот статус. previous_status: null — заказ только что стал заказом компании. После ухода сотрудника события его заказа приходят с employee_id: null.
  • employee.joined — человек стал сотрудником: вошёл с почтой домена компании, принял приглашение, открыл ссылку для сотрудников или его добавил оператор. employee.left — его отметили уволившимся, он перешёл в другую компанию или удалён. employee.role_changed — стал админом или перестал им быть (previous_role).
  • subscription.activated, subscription.renewed, subscription.expired — подписка сотрудника заработала, продлена (новый expires_at) или истекла. Приходят компании, в которой человек сейчас.
  • subscription.auto_renewal_started — заказ с автопродлением выполнен, подписка будет продлеваться сама; subscription.auto_renewal_stopped — автопродление выключили, stop_reason говорит, кто или почему. Приходят компании, в которой человек сейчас.
  • Личные заказы и подписки людей вне компании в события не попадают никогда.

Событие

Конверт одинаков у всех типов: id, type, timestamp (когда это случилось), position, company_id, actor и data своей группы (схемы Event и WebhookEvent). actor.api_key_id — ключ компании, чей запрос к API вызвал событие; null — кто угодно другой: админ в кабинете, сотрудник, xWires. Заказ, который почта, добавленная ключом, подтвердит позже, когда сотрудник его оформит, придёт с actor: null и approved_via: "api_key". Заказ, который подтвердил сам запрос POST /approved-emails (он есть в approved_order_ids), придёт с actor.api_key_id этого ключа.

order.approved
{
  "id": "e5b1a7c3-2d9f-4e60-8b4a-1c7e3f9d2a85",
  "type": "order.approved",
  "timestamp": "2026-10-15T09:30:12.345678+00:00",
  "position": 1042,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": {
    "api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60"
  },
  "data": {
    "order_id": "9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14",
    "status": "approved",
    "previous_status": "awaiting_approval",
    "kind": "renewal",
    "plan": "plus",
    "renewal_mode": "once",
    "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
    "approved_via": "api_key",
    "approved_by_api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60"
  }
}
employee.role_changed
{
  "id": "4f2a9c61-0b8e-4d37-9e5c-3a1d7b6f2e08",
  "type": "employee.role_changed",
  "timestamp": "2026-10-15T11:02:47.902316+00:00",
  "position": 1043,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "data": {
    "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
    "role": "admin",
    "previous_role": "employee"
  }
}
subscription.renewed
{
  "id": "b8e3d1f7-6c2a-4b90-8f15-2e7c9a4d6b31",
  "type": "subscription.renewed",
  "timestamp": "2026-10-15T12:41:09.664051+00:00",
  "position": 1044,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "data": {
    "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
    "status": "active",
    "previous_status": "active",
    "plan": "plus",
    "expires_at": "2026-12-14T08:03:51.207114+00:00"
  }
}
subscription.auto_renewal_stopped
{
  "id": "2c9f4e7a-1d3b-4a68-9e05-7b1c3d5f8a24",
  "type": "subscription.auto_renewal_stopped",
  "timestamp": "2026-10-19T10:15:32.402917+00:00",
  "position": 1051,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": {
    "api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60"
  },
  "data": {
    "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
    "status": "active",
    "previous_status": null,
    "plan": "plus",
    "expires_at": "2026-11-14T08:03:51.207114+00:00",
    "stop_reason": "api_key"
  }
}

Позиция

position — номер события в компании: по всем типам, без пропусков, 1, 2, 3… События одного изменения могут получить номера не подряд, если в то же время менялось что-то ещё. После восстановления базы xWires из резервной копии позиции уходят скачком вперёд, а на позицию, которой больше нет, лента отвечает 410 events_cursor_expired — с фильтром types и без, при первом же запросе: перечитайте списки, как на эту ошибку. С фильтром types позиции, конечно, идут с пропусками.

Лента

after=0 — с самого старого хранимого события (мы храним их 30 дней), after=latest — только новые, число — после этой позиции. types — типы через запятую, limit — до 200. Лента работает независимо от вебхуков, также когда они выключены.

curl
curl -sS "https://api.xwires.ai/company/v1/events?after=1041" \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Ответ 200
{
  "data": [
    {
      "id": "e5b1a7c3-2d9f-4e60-8b4a-1c7e3f9d2a85",
      "type": "order.approved",
      "timestamp": "2026-10-15T09:30:12.345678+00:00",
      "position": 1042,
      "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
      "actor": {
        "api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60"
      },
      "data": {
        "order_id": "9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14",
        "status": "approved",
        "previous_status": "awaiting_approval",
        "kind": "renewal",
        "plan": "plus",
        "renewal_mode": "once",
        "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
        "approved_via": "api_key",
        "approved_by_api_key_id": "0b7d9e2a-5c13-4f6e-8a21-9d4c3b7e1f60"
      }
    },
    {
      "id": "4f2a9c61-0b8e-4d37-9e5c-3a1d7b6f2e08",
      "type": "employee.role_changed",
      "timestamp": "2026-10-15T11:02:47.902316+00:00",
      "position": 1043,
      "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
      "actor": null,
      "data": {
        "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
        "role": "admin",
        "previous_role": "employee"
      }
    },
    {
      "id": "b8e3d1f7-6c2a-4b90-8f15-2e7c9a4d6b31",
      "type": "subscription.renewed",
      "timestamp": "2026-10-15T12:41:09.664051+00:00",
      "position": 1044,
      "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
      "actor": null,
      "data": {
        "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
        "status": "active",
        "previous_status": "active",
        "plan": "plus",
        "expires_at": "2026-12-14T08:03:51.207114+00:00"
      }
    }
  ],
  "next_after": 1044,
  "has_more": false
}

410 events_cursor_expired: событий сразу после этой позиции уже нет — они старше 30 дней, или базу xWires восстановили из резервной копии и они потерялись. Запросите after=latest и сохраните next_after, перечитайте списки и продолжайте с сохранённой позиции.

Вебхуки

Адреса добавляет админ компании: «Компания → Интеграции → Вебхуки». У адреса — URL, описание и типы событий: все, включая будущие, или выбранные. Секрет whsec_… показывается один раз. О каждом новом адресе и смене адреса письмом узнают все админы компании, история изменений хранится. Ключ API адресами не управляет.

  • Адрес — https:// на порту 443, имя с A-записью (IPv4) и сертификатом из публично доверенных; сертификат от Russian Trusted Root CA (Минцифры) не подойдёт. Перенаправления не выполняются: ответ 3xx — неудача. Адреса в локальных и частных сетях и на доменах xWires не принимаются.
  • Запросы приходят с IP 135.106.174.63: разрешите его на своей стороне. Смену адреса объявим за 30 дней.
  • Ответьте 2xx за 10 секунд: примите событие, ответьте, а обработайте потом, например в своей очереди.
  • Доставка «хотя бы раз»: событие может прийти дважды, повторы отбрасывайте по webhook-id. На один адрес запросы идут по одному, обычно в течение минуты после события, первые попытки — по порядку позиций. Повторы, тесты и «Повторить» в кабинете порядок меняют, поэтому действуйте по данным API: статус заказа берите из GET /orders/{id}, а не из порядка событий.
  • Не получилось — повторим с паузами в 1, 5, 15, 30, 60, 120, 240, 360 и 480 минут: до 10 попыток, примерно за 22 часа. После 3 неудач подряд адрес «не принимает события»: туда идут только пробные запросы по тому же расписанию, а как только пробный пройдёт, уйдёт и всё накопленное.
  • Через 1 час неудач админам придёт письмо. Событие, которое не удалось доставить за 3 дня с первой попытки, мы больше не отправляем. События, накопившиеся, пока адрес не принимал запросы или оператор xWires останавливал вебхуки компании, придут позже, по порядку, и могут быть старше 3 дней: когда это случилось, смотрите в timestamp. Через 5 дней без успешной доставки адрес выключается, и админам снова придёт письмо.
  • Включённый снова адрес не получит событий, случившихся, пока он был выключен: заберите их из ленты. Событие, которое не удалось доставить до выключения, после включения мы ещё попробуем отправить, если с первой попытки прошло меньше 3 дней.

Заголовки и подпись

Подпись — по Standard Webhooks: подходят его библиотеки (standardwebhooks в npm и PyPI) или код ниже.

ЗаголовокЧто значит
webhook-idid события, одинаковый во всех попытках: повторы отбрасывайте по нему.
webhook-timestampВремя попытки в секундах Unix. Отклоняйте запросы, которые расходятся с вашими часами больше чем на 300 секунд.
webhook-signaturev1, и base64 от HMAC-SHA256 строки <webhook-id>.<webhook-timestamp>.<тело> на байтах секрета (base64 после whsec_). После смены секрета 24 часа подписей две, через пробел: подходит любая.
content-typeapplication/json
user-agentxWires-Webhooks/1 (+https://xwires.ai/docs/company-api#webhooks)

Проверяйте подпись по телу запроса как оно пришло, до разбора JSON: тот же JSON, записанный заново, не сойдётся. Секрет храните на сервере, как ключ API, например в XWIRES_WEBHOOK_SECRET.

Node.js 18+
import { createHmac, timingSafeEqual } from "node:crypto";

// secret: whsec_… из «Компания → Интеграции»; headers: заголовки запроса, имена в нижнем
// регистре, как их даёт Node; body: тело запроса как пришло (строка или Buffer), до JSON.parse.
export function verifyWebhook(secret, headers, body) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signatures = headers["webhook-signature"];
  if (!id || !signatures || !/^\d{1,12}$/.test(timestamp ?? "")) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const key = Buffer.from(secret.slice("whsec_".length), "base64");
  const expected = createHmac("sha256", key).update(`${id}.${timestamp}.`).update(body).digest();
  return signatures.split(" ").some((entry) => {
    const [version, signature] = entry.split(",");
    const given = Buffer.from(signature ?? "", "base64");
    return version === "v1" && given.length === expected.length && timingSafeEqual(given, expected);
  });
}
Python 3.9+
import base64
import hashlib
import hmac
import time


# secret: whsec_… из «Компания → Интеграции»; headers: заголовки запроса (во Flask и Django
# имена без учёта регистра); body: тело запроса как пришло, в байтах, до json.loads.
def verify_webhook(secret: str, headers, body: bytes) -> bool:
    msg_id = headers.get("webhook-id") or ""
    timestamp = headers.get("webhook-timestamp") or ""
    signatures = headers.get("webhook-signature") or ""
    if not msg_id or not signatures:
        return False
    if not (timestamp.isascii() and timestamp.isdigit() and len(timestamp) <= 12):
        return False
    if abs(time.time() - int(timestamp)) > 300:
        return False

    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f"{msg_id}.{timestamp}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest())
    for entry in signatures.split(" "):
        version, _, signature = entry.partition(",")
        if version == "v1" and hmac.compare_digest(signature.encode(), expected):
            return True
    return False

Смена секрета

«Выпустить новый секрет» в кабинете: прежний подписывает ещё 24 часа, и в это время у запроса две подписи. Перенесите новый секрет в свою систему. Вторая смена за эти сутки выводит самый старый секрет сразу. Смените секрет, когда уходит человек, который мог его видеть: админ, разработчик, подрядчик.

Тестовое событие

«Отправить тестовое событие» в кабинете присылает webhook.test: без позиции и ключа, в data — адрес, который проверяли. Оно уходит и на адрес, который не принимает события. У теста одна попытка, и не доставленный за 10 минут тест считается неудачным. В ленте тестовых событий нет.

webhook.test
{
  "id": "7a3c5e91-2f4b-4d68-b0a7-9c1e3f5d8b26",
  "type": "webhook.test",
  "timestamp": "2026-10-15T10:05:00.000+00:00",
  "position": null,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "data": {
    "endpoint_id": "c4d8f2a6-1b3e-4a75-9c0d-5e2f7a1b3c69"
  }
}

Что и когда ушло на каждый адрес, видно в кабинете в «Журнале доставки»; там же неудачную доставку можно повторить. Оператор xWires может остановить вебхуки компании, тогда в кабинете об этом будет написано.

14Безопасность и персональные данные

  • Компания — оператор персональных данных своих сотрудников, xWires обрабатывает их по её поручению (152-ФЗ, ст. 6 ч. 3). Ключ и адрес вебхука, которые создал админ компании, — поручение передавать эти данные в системы компании.
  • Если ваша система или адрес вебхука за пределами России, это трансграничная передача, и оформляет её компания.
  • Ответы API — персональные данные: почты и имена сотрудников. Храните их и логи интеграции так же, как другие данные сотрудников. Ключи и секреты — только на сервере, в хранилище секретов.
  • В событиях почт, имён и цен нет: только идентификаторы, статусы, план, вид заказа, роль, время и ключ, если действовал он.
  • У нас тела запросов и ответов в логи не пишутся, почты не попадают в адреса, IP-адреса не хранятся. Ключ хранится только как отпечаток, секрет вебхука — зашифрованным. Адрес вебхука не попадает в логи и письма, только имя сервера; на какие серверы и с какого времени уходили события компании, хранится в истории адресов.
  • Session сотрудника — доступ к его ChatGPT. Принимайте его только от самого сотрудника, передавайте только в теле POST /subscriptions, не храните и не пишите в логи. У нас сайт сразу шифрует его так, что расшифровать может только сервис, который передаёт заказ партнёру; в ответах, событиях и логах его нет.
  • Письма тем, чьи почты добавила система, уходят постепенно: на большой список — несколько минут. Суточный лимит в 5000 почт и приглашений не даёт утёкшему ключу превратить xWires в рассылку.
  • Уходит человек, который мог сохранить ключ или секрет, — смените их: ключ, секрет.

15Изменения

ВерсияЧто нового
v1, октябрь 2026Первая версия: 16 запросов, лента событий и вебхуки.
v1, 17 октября 2026Новый адрес API — https://api.xwires.ai/company/v1. Прежний, https://xwires.ai/api/company/v1, отвечает так же: менять его в работающих системах не обязательно.
v1, 17 октября 2026Песочница для проверки интеграций: https://api.sandbox.xwires.ai/company/v1, ключи xwc_test_… и 4 запроса раздела «Песочница» (раздел 11). В рабочем API их нет.
v1, 18 октября 2026У заказа больше нет полей price и currency: стоимость подписок xWires согласует с компанией отдельно, вне API. Это единственное исключение из правила, что v1 только растёт.
v1, 18 октября 2026Подписку некоторых заказов xWires продлевает сам каждый месяц за счёт компании, пока её не отключат. Такой заказ POST /orders/{id}/approve подтверждает только с {"renewal": "monthly"}, иначе ответ 409 renewal_consent_required. Остальные заказы подтверждаются и без поля: по умолчанию renewal — once. Почта из POST /approved-emails подтверждает его, только если её добавили с renews_monthly: true. Согласие API никогда не даёт само.
v1, 18 октября 2026У заказа и в данных событий order.* — renewal_mode, monthly или once: видно заранее, нужен ли для подтверждения {"renewal": "monthly"}, и с каким режимом заказ подтверждён. У почты списка и в ответе POST /approved-emails — renews_monthly: несёт ли почта согласие на ежемесячное продление.
v1, 18 октября 2026Почта в POST /approved-emails, которая уже подтвердила заказ, пока он не завершён, считается в skipped. Раньше повтор запроса запоминал её снова, и она подтверждала следующий заказ сотрудника. Чтобы оплатить и следующий, добавьте почту, когда этот завершится.
v1, 18 октября 2026POST /subscriptions: подписка сотруднику по его session одним запросом, с учётной записью, если её нет. Человеку вне компании — приглашение и почта в списке (202). Новые коды 400 invalid_session, 400 session_token_required, 400 session_expires_too_soon, 409 chatgpt_email_taken, 409 chatgpt_email_mismatch, 409 plan_mismatch; 409 open_order_exists теперь и вне песочницы.
v1, 18 октября 2026Режим заказа выбирает компания. POST /orders/{id}/approve без renewal подтверждает любой заказ разовым и больше не отвечает 409 renewal_consent_required; {"renewal": "monthly"} — согласие на автопродление, и его можно дать любому заказу, пока автопродление для компании открыто, иначе 409 auto_renewal_closed. Так же renewal в POST /subscriptions. Почта без renews_monthly подтверждает заказ разовым, с ним — в режиме сотрудника. Окончательный renewal_mode — с awaiting_check. У заказа новое поле origin.
v1, 19 октября 2026Автопродление сотрудника: subscription.auto_renewal у сотрудника, его отключение POST /employees/{id}/auto-renewal/stop (409 auto_renewal_not_on), события subscription.auto_renewal_started и subscription.auto_renewal_stopped с stop_reason. POST /subscriptions отвечает 409 auto_renewal_on, пока автопродление включено. Увольнение отключает автопродление за счёт компании.

В v1 изменения только добавляются: новые поля, значения статусов и типы событий. Несовместимые изменения выйдут как v2 по новому адресу, а v1 проработает рядом не меньше 6 месяцев. Смену IP-адреса вебхуков объявим за 30 дней.