Перейти к содержимому
Для систем компании

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

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

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

01Что умеет API

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

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

Ещё система выпускает виртуальные карты компании для подписок и сервисов, пополняет их и выводит с них деньги, замораживает, закрывает и получает их реквизиты (раздел 11).

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

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

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

  1. Админ компании открывает «Кабинет компании → Интеграции» и в «Ключах API» создаёт ключ: название, например «1С: кадры», и доступ — «Только чтение» или «Чтение и действия». Ключ показывается один раз, скопируйте его сразу. Всем админам компании придёт письмо о новом ключе. Ключ можно и попросить у оператора xWires: он выпустит его и передаст вам, а у ключа в «Интеграциях» будет видно, что его выпустил оператор.
  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",
    "cards": true
  },
  "company": {
    "id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
    "name": "Ромашка",
    "cards_enabled": true
  }
}

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 (раздел 12). Передавайте его только в заголовке Authorization: Bearer xwc_…. Без ключа, с испорченным или незнакомым ключом ответ — 401 invalid_api_key.

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

Картам компании (раздел 11) нужно ещё право «Карты»: его дают при выпуске ключа, у ключей, выпущенных раньше, его нет. Без него все запросы карт отвечают 403 api_key_no_cards. С правом и доступом «Только чтение» ключ читает карты, их операции и транзакции, с «Чтение и действия» — ещё выпускает, пополняет, выводит, замораживает, размораживает, закрывает карты и видит их реквизиты. Есть ли право у ключа, говорит key.cards в GET /me.

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

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

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

Отзыв и смена

  • Отозвать ключ может любой админ компании в «Интеграциях» и оператор 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 согласует с компанией отдельно. Суммы есть только у карт компании: деньги на карте и в её операциях — строкой в долларах с двумя знаками, "48.20", суммы транзакций — строкой в их валюте. Суммы в запросах — тоже строки, а не числа.
  • Поля, значения и типы событий в v1 только добавляются. Незнакомые поля и типы событий пропускайте, незнакомый статус заказа считайте «в работе».
  • Каждый ответ, и ошибка тоже, несёт заголовок X-Request-Id: назовите его поддержке (support@xwires.ai), если что-то пошло не так. Ответы на запросы с ключом несут и 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: пути полей и коды проверки, без присланных значений, и где есть — нарушенное правило: minimum и maximum (длина, число элементов, значение), expected (тип), format, allowed (допустимые значения). Тело, которое не разобрать как JSON, — issue: invalid_json с пустым path. У 400 invalid_session в details — path: session и причина. У 400 invalid_amount, когда сумма за границами вида карты, — path: amount, minimum и maximum.

Ответ 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 КБ без пробелов и поля marketingEligibility (его мы не передаём), 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.
invalid_amountHTTP400Что значит и что делатьСумма не подходит. Это строка в долларах США, с точкой и не больше чем двумя знаками после неё, например "50.00". Выпуск и пополнение — от минимума до максимума вида карты, вывод — от "0.01" или "all"; details.issues называет границы.
session_token_requiredHTTP400Что значит и что делатьВ session нет sessionToken, а без него заказ с автопродлением (renewal: monthly) партнёру не передать: пришлите ответ 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 только для чтения: для этого запроса нужен ключ с правом действий.
api_key_no_cardsHTTP403Что значит и что делатьУ ключа API нет права «Карты». Право даётся при выпуске ключа: выпустите новый, Кабинет компании → Интеграции.
not_foundHTTP404Что значит и что делатьТакого адреса в API нет: проверьте путь и метод.
order_not_foundHTTP404Что значит и что делатьЗаказа с таким id у компании нет.
employee_not_foundHTTP404Что значит и что делатьСотрудника с таким id в компании нет: возможно, он уже ушёл.
approved_email_not_foundHTTP404Что значит и что делатьПочты с таким id в списке нет: её уже удалили, или она уже подтвердила заказ.
invite_not_foundHTTP404Что значит и что делатьПриглашения с таким id нет: его уже отозвали или приняли.
card_not_foundHTTP404Что значит и что делатьКарты с таким id у компании нет.
card_operation_not_foundHTTP404Что значит и что делатьОперации с таким id у карт компании нет.
request_timeoutHTTP408Что значит и что делатьТело запроса не пришло целиком за 10 секунд. Повторите запрос.
order_not_awaiting_approvalHTTP409Что значит и что делатьЗаказ не ждёт подтверждения: его уже подтвердили, отклонили или отменили. Прочитайте заказ.
already_employeeHTTP409Что значит и что делатьЧеловек с этой почтой уже сотрудник компании.
open_order_existsHTTP409Что значит и что делатьУ сотрудника уже есть открытый заказ: следите за ним в GET /orders?employee_id=… или в ленте событий.
chatgpt_email_takenHTTP409Что значит и что делатьЭтот ChatGPT-аккаунт в xWires уже у другого человека: проверьте, что session от аккаунта сотрудника. Компании с white-label аккаунт личного клиента xWires или другого её клиента перейдёт, когда подписка на него закончится.
chatgpt_email_mismatchHTTP409Что значит и что делатьSession от другого ChatGPT-аккаунта: продление идёт на аккаунт подписки сотрудника, а session к заказу — на аккаунт заказа. Пришлите session того аккаунта (chatgpt_email заказа или subscription.chatgpt_email в GET /employees/{id}).
order_not_awaiting_sessionHTTP409Что значит и что делатьЗаказ не ждёт session: его ещё не подтвердили, session уже прислан или заказ закрыт. Session принимает заказ в статусе approved. Прочитайте заказ.
plan_mismatchHTTP409Что значит и что делатьПодписка сотрудника — на другой план, а продление идёт на тот же: укажите plan его подписки (GET /employees/{id}).
auto_renewal_not_onHTTP409Что значит и что делатьАвтопродление сотрудника не включено или его оплачивает не компания: отключать нечего, а renewal в песочнице применяется только к подписке с включённым автопродлением.
auto_renewal_onHTTP409Что значит и что делатьПодписку сотрудника каждый месяц продлевает автопродление: новый заказ не нужен. Чтобы продлить вручную, сначала отключите его (stopAutoRenewal).
auto_renewal_closedHTTP409Что значит и что делатьАвтопродление для компании пока закрыто: подтвердите разовую активацию — без renewal или с renewal: "once".
cannot_offboard_adminHTTP409Что значит и что делатьАдмина компании через API уволившимся не отметить: это делают в кабинете.
cards_not_enabledHTTP409Что значит и что делатьВыпуск и пополнение карт для компании выключены: их включает xWires. Карты, что уже есть, можно читать, выводить с них деньги, замораживать, закрывать и смотреть их реквизиты.
card_operation_pendingHTTP409Что значит и что делатьС картой уже идёт операция: дождитесь её итога (событие card.* или GET /card-operations/{id}, id — в pending_operation карты) и повторите.
card_not_activeHTTP409Что значит и что делатьКарта не активна: она ещё выпускается или заморожена. Пополнять, выводить и замораживать можно только активную карту. Прочитайте карту.
card_not_frozenHTTP409Что значит и что делатьКарта не заморожена: размораживать нечего. Прочитайте карту.
card_closedHTTP409Что значит и что делатьКарта закрыта или так и не выпущена: с ней больше ничего не сделать.
reference_conflictHTTP409Что значит и что делатьЭтот reference уже занят другим запросом: у операции карты или оформления подписки с ним было другое тело. Новому запросу нужен новый reference, а повтор того же — тот же reference и то же тело.
insufficient_fundsHTTP409Что значит и что делатьНа балансе компании не хватает денег на эту сумму с комиссией: попросите xWires зачислить оплату по счёту. Баланс общий для карт и запросов к моделям, которые оплачиваются с него. Поставщику ничего не ушло, повторите после оплаты.
membership_not_foundHTTP409Что значит и что делатьУ сотрудника нет подписки: заказ создаётся для придуманного сотрудника, POST /sandbox/employees.
sandbox_invalid_transitionHTTP409Что значит и что делатьИз нынешнего статуса заказ в этот не перевести: подтверждает и отклоняет компания, из succeeded, failed, rejected и cancelled дальше пути нет, а из scheduled заказ уходит не раньше scheduled_for: продление активной подписки ждёт конца оплаченного месяца, а закончить его сейчас можно запросом POST /sandbox/employees/{id}/expire-subscription. Прочитайте заказ.
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Что значит и что делатьСлишком много запросов с этим ключом: не больше 300 в минуту и 4 одновременно. Повторите после Retry-After.
api_email_quota_exceededHTTP429Что значит и что делатьЗа сутки через API прислали 5000 почт и приглашений компании. Повторите позже или добавьте почты в кабинете.
card_details_rate_limitedHTTP429Что значит и что делатьРеквизиты карт ключ может открыть не больше 10 раз в час. Повторите после Retry-After; храните реквизиты у себя, а не запрашивайте их каждый раз.
internal_errorHTTP500Что значит и что делатьВнутренняя ошибка xWires. Прочитайте данные GET-запросом и повторите; если ошибка не проходит, напишите на support@xwires.ai с request_id.
service_unavailableHTTP503Что значит и что делатьxWires сейчас не может ответить. Повторите через секунду: запрос можно безопасно повторить.
  • 405 без нашего JSON — у адреса нет такого метода.
  • 429: на rate_limited и card_details_rate_limited подождите столько секунд, сколько в Retry-After. У api_email_quota_exceeded заголовка нет: суточный лимит освобождается постепенно.
  • 500 — ошибка у нас: сначала прочитайте данные, потом повторяйте. Не проходит — напишите на support@xwires.ai с 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 (раздел 14): в ней те же события, что приходят на вебхуки.

  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 нет: каждое действие и так безопасно повторить. Выпуск, пополнение и вывод с карты двигают деньги, поэтому у них в теле reference — id операции в вашей системе (раздел 11). Его же можно прислать в POST /subscriptions: заказ подписки тоже стоит денег.

ДействиеЧто ответит повтор
POST /orders/{id}/approvePOST /orders/{id}/rejectЧто ответит повтор409 order_not_awaiting_approval
POST /approved-emailsЧто ответит повторПочты из списка и почты, которые уже подтвердили незавершённый заказ, считаются в skipped, но идут в суточный лимит.
POST /invitesЧто ответит повтор200 и created: false, письмо второй раз не уходит.
POST /subscriptionsЧто ответит повторС reference: тот же reference и то же тело — тот же заказ или приглашение и 200, даже после того, как заказ выполнен, без писем и суточного лимита; с другим телом — 409 reference_conflict. Без него: 409 open_order_exists, пока заказ открыт, — найдите его в GET /orders?employee_id=…; после выполненного заказа тот же запрос оформит продление. Повтор после 202 снова ответит 202, письмо второй раз не уходит, а повтор идёт в суточный лимит.
POST /orders/{id}/sessionЧто ответит повтор409 order_not_awaiting_session: session уже у заказа, прочитайте заказ.
POST /employees/{id}/auto-renewal/stopЧто ответит повтор409 auto_renewal_not_on
POST /cardsPOST /cards/{id}/top-upsPOST /cards/{id}/withdrawalsЧто ответит повторТот же reference и то же тело — та же операция и 200 вместо 202, деньги второй раз не списываются; тот же reference с другим телом — 409 reference_conflict.
POST /cards/{id}/freezePOST /cards/{id}/unfreezePOST /cards/{id}/closeЧто ответит повтор409 card_not_active, 409 card_not_frozen и 409 card_closed по статусу карты, а пока первая ещё идёт, — 409 card_operation_pending.
POST /employees/{id}/offboardЧто ответит повтор404 employee_not_found
DELETE /approved-emails/{id}Что ответит повтор404 approved_email_not_found
DELETE /invites/{id}Что ответит повтор404 invite_not_found

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

08Лимиты

ЧтоСколько
Запросы с одного ключаСколько300 в минуту и 4 одновременно, лента событий тоже. Сверх — 429 rate_limited с Retry-After.
Тело запросаСколькодо 256 КБ, целиком за 10 секунд
Почт в одном запросеСколькодо 500
Почт и приглашений через API за суткиСколько5000 у компании: каждая присланная почта, и та, что уже есть в списке, каждое приглашение и каждый успешный запрос POST /subscriptions. Сверх — 429 api_email_quota_exceeded. Кабинет не считается.
Строк на страницеСколькоот 1 до 200, по умолчанию 100
Действующих ключей у компанииСколькодо 10
Реквизиты картСколько10 показов в час на ключ. Сверх — 429 card_details_rate_limited с Retry-After. Кабинет не считается.
Карт у компанииСколькобез своего лимита: выпуск ограничивает баланс компании
Хранение событийСколько30 дней
Адресов вебхуков у компанииСколькодо 5
Типов событий у адресаСкольковсе, включая будущие, или от 1 до 30 выбранных
Адрес и описаниеСколькодо 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,
  "invalid_emails": [],
  "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": "Олег Смирнов",
  "reference": "hr-2026-1018-oleg",
  "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.
  • Если почта сотрудника с этим планом уже ждёт в списке, заказ её использует, и из GET /approved-emails она уходит: следующий заказ сотрудника она не подтвердит.
  • Отправляя session, система заверяет, что сотрудник согласился передать его партнёру. Без поля renewal подписка разовая. С "renewal": "monthly" она будет продлеваться каждый месяц за счёт компании: система заверяет и согласие сотрудника на это. Так можно, только пока автопродление для компании открыто — иначе 409 auto_renewal_closed, — и в session нужен sessionToken — 400 session_token_required. Пока автопродление сотрудника включено, заказывать продление не нужно: 409 auto_renewal_on.
  • Если человек с этой почтой не в компании — личный клиент xWires или сотрудник другой компании, — или почта на домене другой компании, ответ 202 с outcome: invited. Ему уходит приглашение, почта ждёт в списке с планом, а session не сохраняется: приняв приглашение, человек сам оформит заказ, и почта его подтвердит. У компании с white-label приглашений нет (ниже).
  • Session не подошёл — 400 invalid_session, причина в details.issues; на expired попросите прислать свежий. Такой session мы не храним, а причину отказа и почту сотрудника запоминаем: поддержка xWires видит, что не так, и может подсказать.
  • reference — id запроса в вашей системе, по желанию, как у операций с картой. Повтор с тем же reference и тем же телом отвечает 200 тем же заказом в его нынешнем состоянии или тем же приглашением, без нового заказа и писем, даже если заказ уже выполнен; session при сравнении не учитывается. С другим телом — 409 reference_conflict. Без reference повтор, пока заказ открыт, получит 409 open_order_exists, а после выполненного заказа тот же запрос оформит продление.

Новый session к заказу

Session нужен заказу в статусе approved: компания его подтвердила, а сотрудник session ещё не прислал, или оператор xWires при проверке попросил новый — тогда приходит order.approved с previous_status: awaiting_check. Обычно сотрудник вставляет session в своём кабинете, но ваша система может прислать его сама, взяв у сотрудника свежий:

session.json
{
  "session": {
    "user": {
      "id": "user-Xq3vT8LmN2",
      "name": "Олег Смирнов",
      "email": "o.smirnov@example.ru"
    },
    "expires": "2027-01-18T09:41:07.000Z",
    "accessToken": "eyJhbGciOiJSUzI1NiIs…",
    "sessionToken": "eyJhbGciOiJkaXIiLCJlbmMiOi…"
  }
}
curl
curl -sS -X POST https://api.xwires.ai/company/v1/orders/a4c7e1b9-5d2f-4a83-b6e0-2f9c7d1a3e58/session \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @session.json
Ответ 200
{
  "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-18T11:02:44.918305+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"
  }
}
  • Session — от ChatGPT-аккаунта заказа (employee.chatgpt_email), иначе 409 chatgpt_email_mismatch. Заказ снова ждёт проверки оператора (awaiting_check), потом уходит партнёру.
  • Отправляя session, система заверяет, что сотрудник согласился передать его партнёру. Заказ, который компания подтвердила с автопродлением, станет monthly только с "renewal": "monthly" — согласием сотрудника на ежемесячные продления, — и тогда в session нужен sessionToken; без поля заказ разовый.
  • Заказ не ждёт session — 409 order_not_awaiting_session: его ещё не подтвердили, session уже прислан или заказ закрыт. Session не подошёл — 400 invalid_session, как в POST /subscriptions.

White-label: подписки для ваших клиентов

Если вы перепродаёте подписки ChatGPT своим клиентам, xWires может оставаться для них невидимым. White-label включает xWires по договору с компанией. Тогда:

  • Клиент вставляет session на вашей странице, а ваша система присылает его POST /subscriptions с почтой клиента и планом. Клиент — человек вашей компании в xWires: так его видят GET /employees, заказы и события.
  • Людям компании xWires не пишет ничего: ни о заказах и подписках, ни приглашений. О ходе заказа вы узнаёте из событий и вебхуков (раздел 14) и сообщаете клиенту сами. Письма xWires получают только админы компании.
  • Почта — ваш клиент и только ваш: если с той же почтой в xWires есть кто-то ещё — наш личный клиент или сотрудник другой компании, — это другой человек, он вам не мешает, и никто никого не приглашает. В ответах и в GET /employees клиент — с той почтой, что прислали вы, и POST /employees/search находит его по ней. Писем на неё xWires не шлёт.
  • Если оператор xWires попросил новый session, придёт order.approved с previous_status: awaiting_check: возьмите у клиента свежий и пришлите POST /orders/{id}/session.
  • Один аккаунт ChatGPT — у одного человека. Если на аккаунт клиента в xWires уже есть подписка другого человека, ответ 409 chatgpt_email_taken. Когда подписка нашего личного клиента или другого вашего клиента на этот аккаунт закончилась, аккаунт переходит к вашему клиенту, и заказ проходит.
  • Подписка разовая, на месяц. На следующий месяц пришлите POST /subscriptions с новым session того же аккаунта: заказ продлит подписку.

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

Подписку с автопродлением (заказ с 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.

За 3 дня до next_renewal_at появляется заказ продления: origin: auto_renewal, сразу в scheduled, без подтверждения и без session (order.scheduled), а state становится in_progress. Оплату в дату продления списывает сам OpenAI с карты, которую xWires держит для подписки. Списал — заказ выполнен (order.succeeded), подписка продлена на календарный месяц (subscription.renewed), state снова on. Если OpenAI получил отказ, xWires ждёт его повтора до недели; если не пытался списать — до суток. Не дождался или карта оплаты не годится — продление не прошло: order.failed, subscription.renewal_failed с failure_reason и subscription.auto_renewal_stopped (stop_reason failed или openai_off). Подписка действует до expires_at, дальше её продлевают новым заказом. Пока продление разбирает оператор xWires, state — attention.

subscription.renewal_failed
{
  "id": "8d4b2f61-7a3e-4c95-b1d8-6e2a9f4c7b13",
  "type": "subscription.renewal_failed",
  "timestamp": "2026-11-15T08:04:12.538206+00:00",
  "position": 1188,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "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",
    "order_id": "f3a8c2d6-9b1e-4f74-a5c0-3d7e1b9f6a28",
    "failure_reason": "not_charged"
  }
}
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, потом подписка не продлится. Карту оплаты xWires сразу удалит, и OpenAI больше не спишет; сотруднику и админам компании придёт письмо. Включить снова до expires_at нельзя; новый заказ с renewal: monthly уйдёт партнёру, когда оплаченный месяц кончится.
  • Отключить можно, только пока автопродление включено и платит за него ваша компания (can_stop: true), иначе 409 auto_renewal_not_on; повтор отвечает так же. Заказ продления, который ещё ждёт оплаты OpenAI, отменяется вместе с ним (order.cancelled).
  • Отключить его может и сам сотрудник, админ компании в кабинете и оператор 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"
  ]
}

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

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

Выполненные заказы приходят событиями order.succeeded: подписка на них у адреса вебхука или фильтр ленты. План, вид заказа и время выполнения (completed_at) берите из заказа: GET /orders/{id}. Сумм за подписки в API нет: их стоимость xWires согласует с компанией отдельно.

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В кабинетеОтменёнЧто значитЗаказ отменён: сотрудник отменил его сам, пока заказ ждал подтверждения или session, сотрудника отметили уволившимся или оператор xWires отменил заказ.

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

  • failed → sent: партнёр всё-таки повторил заказ;
  • awaiting_check → approved: оператор попросил у сотрудника новый session;
  • review_required → approved: оператор повторяет заказ, а session уже нет;
  • submitting → scheduled: передать заказ партнёру не вышло, и до партнёра ничего не дошло; заказ уйдёт следующей попыткой.
  • sent → scheduled: заказ без автопродления не прошёл у партнёра, деньги не списаны; заказ уходит другому партнёру, и подписка остаётся в работе.

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

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

Подписка

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

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

auto_renewal.stateВ кабинетеЧто значит
onВ кабинетеВключеноЧто значитВключено: подписка продлится в next_renewal_at за счёт компании.
needs_sessionВ кабинетеНужен новый sessionЧто значитНе приходит: session сотрудника для продлений не нужен. Значение осталось в перечне v1.
in_progressВ кабинетеПродлеваемЧто значитПодписку продлеваем: за 3 дня до next_renewal_at появился заказ продления (origin: auto_renewal), и он ждёт оплаты OpenAI.
attentionВ кабинетеНа разбореЧто значитПродление разбирает оператор xWires, например цена вышла выше обычной; потом оно пойдёт дальше или кончится неудачей.
offВ кабинетеВыключеноЧто значитВыключено или не включалось; почему выключено — в stop_reason.

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

stop_reasonЧто значит
userЧто значитОтключил сам сотрудник в кабинете.
company_adminЧто значитОтключил админ компании в кабинете.
api_keyЧто значитОтключила система компании: stopAutoRenewal.
operatorЧто значитОтключил оператор xWires.
offboardedЧто значитСотрудника отметили уволившимся: компания больше не платит.
payer_changedЧто значитСменился плательщик: сотрудник перешёл в другую компанию или ушёл из неё.
openai_offЧто значитПродление выключено у OpenAI, и сам он подписку не продлит: так ответил партнёр, когда заказ выполнили, или OpenAI не пытался списать оплату в дату продления (тогда пришло и subscription.renewal_failed).
plan_changedЧто значитПлан подписки сменился, а согласие было на прежний.
failedЧто значитПродление не прошло: OpenAI получил отказ и не списал оплату за неделю, или карта оплаты не годится; пришло subscription.renewal_failed.

Планы

planПлан
plusПланChatGPT Plus
pro100ПланChatGPT Pro 100
pro200ПланChatGPT Pro 200
pro500ПланChatGPT Pro 500

11Карты

Компания, которой xWires включил карты, выпускает виртуальные карты для подписок и сервисов: чтобы оплатить ChatGPT сотрудника или другой сервис. Деньги на карты идут с баланса компании в xWires: его пополняют по счёту, а выпуск и пополнение списывают с него сумму и комиссию. Тот же баланс тратят запросы к моделям, которые оплачиваются с него. Баланса, комиссий и условий компании в API нет — только деньги на картах. Карта принадлежит компании; сотрудника у неё можно указать как пометку.

  • Нужен ключ с правом «Карты» (раздел 3), без него — 403 api_key_no_cards. Есть ли право, говорит key.cards в GET /me, включены ли карты компании — company.cards_enabled. Пока они выключены, выпуск и пополнение отвечают 409 cards_not_enabled, а карты, что уже есть, можно читать, выводить с них деньги, замораживать и закрывать.
  • Своих лимитов на число карт нет: выпуск и пополнение ограничивает баланс компании. Не хватает — 409 insufficient_funds, банку ничего не уходит. Если деньги успели уйти на запросы к моделям, пока операция ждала очереди, она не пройдёт: card.operation_failed с failure_code insufficient_funds, деньги вернутся на баланс.
  • Операция принимается сразу (202), а итог приходит через секунды или минуты: событием card.* и в самой операции (GET /card-operations/{id}). На карте одна операция за раз, она видна в pending_operation карты; вторая отвечает 409 card_operation_pending.
  • Суммы — строки в долларах США, с точкой и не больше чем двумя знаками: "50.00" или "50". Число вместо строки, третий знак и сумма за границами вида — 400 invalid_amount.
kindДля чегоВыпуск и пополнение, USDОстаётся после вывода, USD
chatgptДля чегоТолько для OpenAI: другие магазины карта отклоняет.Выпуск и пополнение, USDот 10.00 до 30000.00Остаётся после вывода, USD1.00
generalДля чегоДля подписок и сервисов.Выпуск и пополнение, USDот 10.00 до 50000.00Остаётся после вывода, USD0.01
ЗапросНужен доступЧто делает
POST /cardsНужен доступЧтение и действияЧто делаетВыпустить карту
GET /cards/{id}Нужен доступТолько чтениеЧто делаетКарта
GET /cards/{id}/detailsНужен доступЧтение и действияЧто делаетРеквизиты карты
POST /cards/{id}/top-upsНужен доступЧтение и действияЧто делаетПополнить карту
POST /cards/{id}/withdrawalsНужен доступЧтение и действияЧто делаетВывести деньги с карты
POST /cards/{id}/freezeНужен доступЧтение и действияЧто делаетЗаморозить карту
POST /cards/{id}/unfreezeНужен доступЧтение и действияЧто делаетРазморозить карту
POST /cards/{id}/closeНужен доступЧтение и действияЧто делаетЗакрыть карту
GET /card-operations/{id}Нужен доступТолько чтениеЧто делаетОперация с картой
GET /cardsНужен доступТолько чтениеЧто делаетКарты
GET /cards/{id}/operationsНужен доступТолько чтениеЧто делаетОперации карты
GET /cards/{id}/transactionsНужен доступТолько чтениеЧто делаетТранзакции карты
GET /cards/{id}/details-revealsНужен доступТолько чтениеЧто делаетЖурнал показов реквизитов

Выпустить карту и оплатить подписку

  1. Выпустите карту: вид, сумма и reference — id операции в вашей системе. Подпись и сотрудник — по желанию.
curl
curl -sS -X POST https://api.xwires.ai/company/v1/cards \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"chatgpt","amount":"50.00","reference":"1c:card-0042","label":"ChatGPT Plus, Анна","employee_id":"3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90"}'
Ответ 202
{
  "card": {
    "id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
    "kind": "chatgpt",
    "status": "issuing",
    "network": null,
    "last4": null,
    "expires": null,
    "available": null,
    "label": "ChatGPT Plus, Анна",
    "employee_id": "3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90",
    "pending_operation": {
      "id": "2b7e9c4f-1a3d-4e85-9f06-5c8a2d1e7b39",
      "type": "issue"
    },
    "closed_reason": null,
    "replaced_by": null,
    "created_at": "2026-10-21T10:04:17.302518+00:00",
    "updated_at": "2026-10-21T10:04:17.302518+00:00"
  },
  "operation": {
    "id": "2b7e9c4f-1a3d-4e85-9f06-5c8a2d1e7b39",
    "card_id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
    "type": "issue",
    "amount": "50.00",
    "reference": "1c:card-0042",
    "status": "pending",
    "failure_code": null,
    "created_at": "2026-10-21T10:04:17.302518+00:00",
    "completed_at": null
  }
}
  1. Дождитесь card.issued: карта active, у неё последние 4 цифры, сеть, срок и остаток. Не пришло событие — спросите операцию: GET /card-operations/{id}. Все операции карты, с итогами, — GET /cards/{id}/operations. Выпуск не прошёл — card.operation_failed, карта failed, деньги вернулись на баланс.
card.issued
{
  "id": "3c8f1a6d-9b2e-4e75-8a41-6d2c9f5b1e07",
  "type": "card.issued",
  "timestamp": "2026-10-21T10:04:29.811604+00:00",
  "position": 1060,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "data": {
    "card_id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
    "operation_id": "2b7e9c4f-1a3d-4e85-9f06-5c8a2d1e7b39",
    "type": "issue",
    "reference": "1c:card-0042",
    "amount": "50.00",
    "available": "50.00",
    "closed_reason": null,
    "new_card_id": null,
    "failure_code": null
  }
}
  1. Прочитайте реквизиты и введите их в форме оплаты сервиса. Имя держателя у всех карт одно, кода 3-D Secure нет: карта платит без него.
curl
curl -sS https://api.xwires.ai/company/v1/cards/8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60/details \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Ответ 200
{
  "card_id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
  "number": "4000000000000002",
  "cvv": "123",
  "expires": "2028-10",
  "holder_name": "GPT Payment",
  "billing_address": {
    "line1": "1209 Orange St",
    "line2": "",
    "city": "Wilmington",
    "state": "DE",
    "country": "US",
    "postal_code": "19801"
  }
}
  1. Списание сервиса придёт событием card.transaction (раздел 14); магазин и сумма в его валюте — в списке транзакций карты.
curl
curl -sS "https://api.xwires.ai/company/v1/cards/8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60/transactions?limit=20" \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"

Повтор: reference

Выпуск, пополнение и вывод сам запрос двигает деньги, поэтому «прочитать, потом повторить» здесь не спасает от двойного списания. В теле у них reference — id операции в вашей системе: 1–64 латинских букв, цифр и знаков . _ : -, уникальный в компании. Тот же reference с тем же телом — та же операция в её нынешнем состоянии и 200 вместо 202; с другим телом — 409 reference_conflict. После таймаута или 5xx повторяйте сразу с тем же reference. Заморозка, разморозка и закрытие без reference: повтор отвечает 409 по статусу карты.

Пополнить к продлению

Лимит карты — её остаток (available). Перед продлением подписки пополните карту на нужную сумму: итог — card.topped_up с новым остатком. Пополнять можно только активную карту.

curl
curl -sS -X POST https://api.xwires.ai/company/v1/cards/8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60/top-ups \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":"20.00","reference":"1c:topup-0107"}'
Ответ 202
{
  "id": "c1a8e5d2-7f4b-4c39-8e60-9d2b5f1a3c74",
  "card_id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
  "type": "top_up",
  "amount": "20.00",
  "reference": "1c:topup-0107",
  "status": "pending",
  "failure_code": null,
  "created_at": "2026-11-12T09:00:04.118302+00:00",
  "completed_at": null
}

Снизить лимит

Вывод возвращает деньги с карты на баланс компании: сумму или "all" — всё, что можно. На карте остаётся минимум вида, деньги под холдом не выводятся. Итог — card.withdrawn, его amount — сколько ушло с карты. Попросили больше, чем можно, — card.operation_failed с insufficient_available. Комиссию пополнения вывод не возвращает.

curl
curl -sS -X POST https://api.xwires.ai/company/v1/cards/8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60/withdrawals \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":"all","reference":"1c:refund-0042"}'

Заморозить и закрыть

POST /cards/{id}/freeze — карта отклоняет списания, деньги ждут на ней; POST /cards/{id}/unfreeze — снова платит. Закрытие — навсегда: остаток вернётся на баланс компании, когда банк подтвердит закрытие (card.closed, amount — сколько ушло с карты). Закрыть можно и замороженную карту. Подписки, которые она оплачивала, отмените у сервисов заранее.

curl
curl -sS -X POST https://api.xwires.ai/company/v1/cards/8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60/close \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"

Карту заменили

Банк может закрыть карту сам: после нескольких отказов подряд, по блокировке BIN или риск-контролю. Тогда придёт card.closed без operation_id и с closed_reason: issuer. Если условия компании это предусматривают, xWires за свой счёт выпускает замену того же вида, с тем же остатком, подписью и сотрудником, и присылает card.replaced: в card_id — закрытая карта, в new_card_id — новая. Прочитайте реквизиты новой карты и введите их в сервисах заново. Без замены остаток закрытой карты вернётся на баланс, если банк его отдаст.

card.replaced
{
  "id": "1f6c3e9a-2d8b-4b57-9e13-4a7d2c6f8b91",
  "type": "card.replaced",
  "timestamp": "2026-11-20T16:41:55.902137+00:00",
  "position": 1094,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "data": {
    "card_id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
    "operation_id": "a7d3f1c9-5e2b-4b68-9c04-1d8e6a3f7b25",
    "type": "replace",
    "reference": null,
    "amount": "30.00",
    "available": "30.00",
    "closed_reason": "issuer",
    "new_card_id": "6e9a2c5f-8b1d-4d37-a4e8-2f7c1b9d5a06",
    "failure_code": null
  }
}

Реквизиты

  • Номер, CVV, срок и адрес плательщика читает только ключ с правом «Карты» и доступом «Чтение и действия», и только у карты active или frozen: у выпускаемой — 409 card_not_active, у закрытой — 409 card_closed.
  • xWires реквизиты не хранит: каждый показ читает их у банка и записывает в журнал — кто открыл, админ в кабинете или ключ, и когда. Журнал видят админы компании, а ключ с правом «Карты» читает его через GET /cards/{id}/details-reveals; чтение журнала — не показ. Ключу — до 10 показов в час, сверх — 429 card_details_rate_limited с Retry-After. Банк не ответил — 503 service_unavailable: повторите позже, показ уже засчитан.
  • На своей стороне не пишите реквизиты в логи и не держите в браузере, отдавайте только тому сервису, который ими платит. Хранить их можно только так, как требует PCI DSS; проще не хранить и читать заново.

Состояния

statusЧто значит
issuingЧто значитВыпуск принят, карту открывает банк. Реквизитов ещё нет.
activeЧто значитКарта работает: платит, её можно пополнить, вывести с неё деньги, заморозить и закрыть.
frozenЧто значитЗаморожена: списания отклоняются, деньги ждут на карте. Её можно разморозить или закрыть.
closedЧто значитЗакрыта навсегда: компанией (closed_reason: company) или банком (closed_reason: issuer).
failedЧто значитВыпуск не прошёл, сумма и комиссия вернулись на баланс компании.

Незнакомый статус карты считайте картой, которая не платит.

typeЧто делаетСобытие успеха
issueЧто делаетВыпуск карты с суммой на ней.Событие успехаcard.issued
top_upЧто делаетПополнение: поднимает лимит карты.Событие успехаcard.topped_up
withdrawalЧто делаетВывод на баланс компании: снижает лимит карты.Событие успехаcard.withdrawn
freezeЧто делаетЗаморозка: карта перестаёт платить.Событие успехаcard.frozen
unfreezeЧто делаетРазморозка.Событие успехаcard.unfrozen
closeЧто делаетЗакрытие навсегда, остаток — на баланс компании.Событие успехаcard.closed
replaceЧто делаетЗамена карты, которую закрыл банк: её делает xWires, не компания.Событие успехаcard.replaced
statusЧто значит
pendingЧто значитИдёт: обычно секунды, иногда несколько минут. Другая операция с этой картой ждёт её итога.
succeededЧто значитПрошла: событие её типа, у карты новый остаток.
failedЧто значитНе прошла, деньги не двигались: событие card.operation_failed с failure_code.

failure_code не прошедшей операции: insufficient_available — на карте меньше, чем просили вывести; card_closed_by_issuer — банк закрыл карту; provider_declined — банк отказал; provider_unavailable — банк не выполнил операцию за сутки; insufficient_funds — выпуску или пополнению не хватило баланса, его успели потратить запросы к моделям. Перечень растёт: незнакомый код — просто отказ.

12Песочница

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

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

Почты у всех компаний песочницы общие, как в рабочем xWires. Почту, которую уже взяла другая компания, ваша не получит: в POST /subscriptions будет 202 с приглашением или 409 chatgpt_email_taken. Поэтому в своих запросах вместо почт из примеров берите свои, например под .invalid. Session песочница проверяет, как рабочий API, но не хранит и никуда не передаёт. Пришлите придуманный той же формы, как subscription.json выше, со своей почтой ChatGPT и expires в будущем. Настоящий session сотрудника не нужен.

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

СотрудникПланЗаказ
Анна СмирноваПлан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, их увидит.
  • Автопродление открыто каждой компании песочницы: {"renewal": "monthly"} не получает 409 auto_renewal_closed. Заказ с автопродлением выполняется, как в рабочем xWires: оно включается, приходит subscription.auto_renewal_started, его можно отключить. Продление от OpenAI песочница показывает, когда вы заканчиваете оплаченный месяц запросом POST /sandbox/employees/{id}/expire-subscription: подписка продлевается (subscription.renewed), а с renewal в теле продление не проходит (subscription.renewal_failed).
  • Чего в песочнице не увидеть: статуса submitting (заказ с автопродлением идёт из scheduled сразу в sent), отмены заказа самим сотрудником, продлений по расписанию (месяц кончается только по вашему запросу) и stop_reason user, operator, payer_changed, plan_changed и openai_off: за придуманных сотрудников и оператора никто не действует. Обработку этих событий и значений проверяйте по их описанию в руководстве.
  • Карты компании песочницы включены с первого входа, вместо банка — подставной (ниже).
  • Лимиты, ошибки и форма ответов — те же, что в рабочем 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); продление активной подписки он, как рабочий xWires, отправляет партнёру в scheduled_for, когда кончится оплаченный месяц. Пока автопродление сотрудника включено, заказ не нужен: auto_renewal_on.
POST /sandbox/orders/{id}/advanceЧто делаетЗаказ идёт в статус to шаг за шагом, за сотрудника, оператора и партнёра, как в проде: approved → awaiting_check → scheduled → sent → succeeded или failed; review_required из scheduled, cancelled из awaiting_check и review_required, approved из awaiting_check. Из scheduled — не раньше scheduled_for, как в проде: продление активной подписки ждёт конца оплаченного месяца. Заказ с renewal_mode monthly выполняется с автопродлением: событие subscription.auto_renewal_started. Каждый шаг — своё событие, без actor. Подтверждают и отклоняют заказ, как всегда, POST /orders/{id}/approve и /reject. После вызова автопилот этот заказ больше не трогает; to, равный нынешнему статусу, только останавливает его.
POST /sandbox/employees/{id}/expire-subscriptionЧто делаетОплаченный месяц активной подписки кончается сейчас, и дальше всё как в проде в этот момент. С включённым автопродлением подписку продлевает OpenAI: появляется заказ продления с origin auto_renewal, он выполняется, приходят order.scheduled, order.sent, order.succeeded и subscription.renewed, expires_at — через календарный месяц; renewal в теле показывает неудачу продления. Без автопродления: если продление ждёт конца месяца (scheduled), оно сразу уходит партнёру, подписка остаётся active, а после выполнения — subscription.renewed и expires_at через месяц; продление, которое вы двигаете сами, ждёт вашего перевода. Иначе подписка истекает: subscription.expired, следующий заказ сотрудника — продление. Тело пустое, {} или с renewal.
POST /sandbox/cards/{id}/transactionsЧто делаетМагазин провёл по карте покупку, холд, отказ или возврат, и банк записал это, как в проде: транзакция появляется в GET /cards/{id}/transactions, приходит card.transaction без actor. Покупка и холд уменьшают available карты, возврат увеличивает, отказ не трогает. Покупку и холд, которые карта не оплатит (заморожена или на ней меньше суммы), банк отклоняет. Четвёртый отказ по карте закрывает её, как банк: card.closed с closed_reason issuer, а при тарифе «риски на нас» xWires выпускает замену (card.replaced). Карта ещё не выпущена — 409 card_not_active, закрыта или не выпустилась — 409 card_closed.

Автопилот

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

Продление активной подписки, как в рабочем xWires, уходит партнёру в scheduled_for, когда кончится оплаченный месяц: до тех пор заказ стоит в scheduled, и дальше его не перевести (409 sandbox_invalid_transition). Чтобы не ждать, закончите месяц сейчас запросом POST /sandbox/employees/{id}/expire-subscription: продление сразу уйдёт партнёру, подписка останется active, а после выполнения придёт subscription.renewed с expires_at через месяц. Без ждущего продления тот же запрос завершает подписку: subscription.expired.

Подписку с включённым автопродлением тот же запрос продлевает так, как это делает OpenAI: оплаченный месяц кончается сейчас, заказ продления с origin: auto_renewal выполняется, приходят order.scheduled, order.sent, order.succeeded и subscription.renewed, а expires_at — через календарный месяц от этого момента. Чтобы увидеть неудачу продления, пришлите {"renewal": "declined"}, not_charged или card_unusable: заказ не выполнится, придут subscription.renewal_failed с этим failure_reason, subscription.auto_renewal_stopped и subscription.expired.

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

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

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 или, с ждущим продлением, subscription.renewed — POST /sandbox/employees/{id}/expire-subscription.

Карты

У компании песочницы карты включены с первого входа: тариф «риски на нас» и $10 000 тестовых денег на балансе. Ключу нужно право «Карты», как в рабочем API. Вместо банка — подставной: операцию с картой он заканчивает сам, когда её подхватит автопилот, обычно меньше чем за полторы минуты, и приходит то же событие card.*, что в рабочем xWires.

  • Выпущенная карта — Visa с последними цифрами 0002 на 24 месяца. Её реквизиты (GET /cards/{id}/details) — тестовый номер 4000000000000002 и CVV 123: им нигде не заплатить. Показ записывается в журнал и идёт в лимит, как в рабочем API.
  • Сумму с центами .13 — выпуск, пополнение или вывод "10.13" — банк отклоняет: card.operation_failed с failure_code provider_declined, деньги вернулись на баланс. Вывод больше, чем можно, — insufficient_available, как в рабочем xWires.
  • Покупку, холд, отказ или возврат по карте изображает POST /sandbox/cards/{id}/transactions: приходит card.transaction, транзакция видна в GET /cards/{id}/transactions, остаток карты меняется. Четвёртый отказ по карте закрывает её, как банк: card.closed с closed_reason issuer, а при тарифе «риски на нас» — card.replaced и карта-замена с тем же остатком.
  • В «Интеграциях» песочницы можно сменить тариф карт: без тарифа выпуск и пополнение отвечают 409 cards_not_enabled. Кнопка «Добавить $10 000» пополняет баланс; когда денег мало, выпуск и пополнение отвечают 409 insufficient_funds.

Отказ по карте, как его записал бы банк:

curl
curl -sS -X POST https://api.sandbox.xwires.ai/company/v1/sandbox/cards/b3e7a1d5-9c2f-4e68-a0b4-6d1c8f3e5a27/transactions \
  -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"decline","amount":"20.00"}'
Ответ 201
{
  "id": "4a9d2c6e-1f7b-4b35-8e90-3c5a7d1f9b62",
  "type": "purchase",
  "status": "declined",
  "amount": "20.00",
  "currency": "USD",
  "merchant_amount": "20.00",
  "merchant_currency": "USD",
  "merchant_name": "OPENAI *CHATGPT SUBSCR",
  "occurred_at": "2026-10-25T09:14:03.518204+00:00"
}

13Справочник

Справочник собран из того же описания, что и 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, после него подписка истекает. Карту оплаты xWires сразу удалит, и OpenAI больше не спишет; сотруднику и админам компании придёт письмо, в ленте — событие 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 не сохраняется. У компании с white-label (его включает xWires) почта — клиент этой компании и только её: кто-то другой с той же почтой в xWires не мешает, приглашений нет, а её людям мы не пишем. Такой клиент без своей подписки забирает ChatGPT-аккаунт, чья подписка у личного клиента xWires или у другого клиента компании закончилась; пока она действует — 409 chatgpt_email_taken. renewal: monthly — подписка с автопродлением, только пока оно открыто для компании: иначе 409 auto_renewal_closed, и ни заказа, ни приглашения. Заказ он сделает сам, приняв приглашение, и почта его подтвердит. Повтор после обрыва безопасен с reference: тот же reference и то же тело — тот же заказ или приглашение и 200, даже если заказ уже выполнен, без нового заказа и писем. Без reference повтор защищает только 409 open_order_exists, пока заказ открыт (найдите его в GET /orders?employee_id=…), а после выполненного заказа тот же запрос оформит продление. Идёт в суточный лимит (5000 почт и приглашений через API); повтор с reference в него не идёт.

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

Тело: OrderSubscriptionRequest

200
Повтор с тем же reference и телом: тот же заказ в его нынешнем состоянии или то же приглашение. OrderSubscriptionResult
201
Заказ создан и ждёт проверки xWires. OrderSubscriptionResult
202
Человек не в компании: приглашение и почта в списке, без заказа. OrderSubscriptionResult
POST/orders/{id}/session

Прислать session к заказу

Session к заказу, который его ждёт (status approved): компания подтвердила заказ, а сотрудник session ещё не прислал, или xWires при проверке попросил новый (событие order.approved с previous_status awaiting_check). Так система компании присылает session сама, не отправляя сотрудника в кабинет. Session — от ChatGPT-аккаунта заказа (chatgpt_email), иначе 409 chatgpt_email_mismatch. Отправляя его, ключ заверяет, что сотрудник согласился передать session партнёру, а с renewal: monthly — и на ежемесячные продления. Заказ ждёт проверки xWires (awaiting_check), затем уходит партнёру. Повтор — 409 order_not_awaiting_session: session уже у заказа.

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

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

Тело: SubmitOrderSessionRequest

200
Заказ ждёт проверки xWires. Order

Список почт

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. Строки, которые не похожи на почту, считаются в invalid и перечислены в invalid_emails. В суточный лимит (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/cards

Карты

Карты компании по времени выпуска, со всеми статусами: и закрытые, и не выпущенные. Курсор действует только с теми же фильтрами.

Ключ: Только чтение и право Карты

ПараметрГдеТипОписание
statusГдев запросеТипstringОписаниеСтатусы через запятую, например active,frozen. Значения: issuing, active, frozen, closed, failed. (необязательное)
employee_idГдев запросеТипuuidОписаниеКарты одного сотрудника. (необязательное)
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница карт. CardsPage
POST/cards

Выпустить карту

Новая виртуальная карта вида kind с суммой amount. С баланса компании сразу списываются сумма и комиссия, карта видна со статусом issuing, а итог приходит событием: card.issued — карта active, реквизиты можно читать; card.operation_failed — карта failed, деньги вернулись на баланс. Обычно за секунды, иногда до нескольких минут. Своих лимитов на число карт нет: их ограничивает баланс (409 insufficient_funds, поставщику ничего не уходит). Баланс общий с запросами к моделям, которые оплачиваются с него: если они потратили деньги, пока выпуск ждал очереди, итог — card.operation_failed с failure_code insufficient_funds, деньги вернулись на баланс, поставщику ничего не ушло. Повтор после обрыва безопасен: тот же reference и то же тело — та же карта и 200.

Ключ: Чтение и действия и право Карты

Тело: IssueCardRequest

200
Повтор с тем же reference и телом: та же карта и операция, в их нынешнем состоянии. IssueCardResult
202
Выпуск принят: карта issuing и операция. IssueCardResult
POST/cards/{id}/top-ups

Пополнить карту

Поднимает лимит активной карты: с баланса компании сразу списываются сумма и комиссия, итог — card.topped_up с новым остатком или card.operation_failed, и тогда деньги вернулись на баланс. Баланс общий с запросами к моделям, которые оплачиваются с него: если они успели его потратить, итог — card.operation_failed с failure_code insufficient_funds, поставщику ничего не ушло. Повтор с тем же reference и телом — та же операция и 200.

Ключ: Чтение и действия и право Карты

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

Тело: TopUpCardRequest

200
Повтор с тем же reference и телом: та же операция, в её нынешнем состоянии. CardOperation
202
Операция принята; итог придёт событием card.*. CardOperation
POST/cards/{id}/withdrawals

Вывести деньги с карты

Снижает лимит активной карты: деньги уходят с карты на баланс компании, когда поставщик это подтвердит (card.withdrawn, amount — сколько ушло). "all" выводит всё, что можно: минимум вида остаётся на карте, деньги под холдом не выводятся. Больше, чем можно, — card.operation_failed с insufficient_available. Комиссию пополнения вывод не возвращает. Работает и после того, как xWires выключил компании карты. Повтор с тем же reference и телом — та же операция и 200.

Ключ: Чтение и действия и право Карты

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

Тело: WithdrawFromCardRequest

200
Повтор с тем же reference и телом: та же операция, в её нынешнем состоянии. CardOperation
202
Операция принята; итог придёт событием card.*. CardOperation
POST/cards/{id}/freeze

Заморозить карту

Активная карта отклоняет списания, пока её не разморозят; деньги ждут на ней. Итог — card.frozen. Тело пустое или {}. Повтор — 409 card_not_active.

Ключ: Чтение и действия и право Карты

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

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

202
Заморозка принята. CardOperation
POST/cards/{id}/unfreeze

Разморозить карту

Замороженная карта снова платит. Итог — card.unfrozen. Тело пустое или {}. Повтор — 409 card_not_frozen.

Ключ: Чтение и действия и право Карты

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

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

202
Разморозка принята. CardOperation
POST/cards/{id}/close

Закрыть карту

Навсегда: активная или замороженная карта закрывается, её остаток возвращается на баланс компании, когда поставщик подтвердит закрытие (card.closed, amount — сколько ушло с карты). Подписки, которые она оплачивала, отмените у магазинов заранее. Тело пустое или {}. Повтор — 409 card_closed.

Ключ: Чтение и действия и право Карты

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

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

202
Закрытие принято. CardOperation
GET/card-operations/{id}

Операция с картой

Операция и её итог: на случай, если событие не дошло.

Ключ: Только чтение и право Карты

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid операции.
200
Операция. CardOperation
GET/cards/{id}/transactions

Транзакции карты

Покупки, холды, отказы и возвраты по карте, сначала новые, с магазином и суммой в его валюте.

Ключ: Только чтение и право Карты

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid карты.
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница транзакций. CardTransactionsPage
GET/cards/{id}/operations

Операции карты

Выпуск, пополнения, выводы, заморозки и закрытие карты, сначала новые, с их итогом; у карты-замены первой будет replace. Каждая — как в GET /card-operations/{id}: так можно сверить свои операции, если события не дошли.

Ключ: Только чтение и право Карты

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid карты.
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница операций. CardOperationsPage
GET/cards/{id}/details-reveals

Журнал показов реквизитов

Кто и когда открывал реквизиты карты, сначала новые: админ компании в кабинете или ключ через API. Тот же журнал видят админы компании и xWires; изменить его нельзя, а чтение журнала — не показ.

Ключ: Только чтение и право Карты

ПараметрГдеТипОписание
idГдев путиТипuuidОписаниеid карты.
limitГдев запросеТипintegerОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100)
cursorГдев запросеТипstringОписаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов)
200
Страница журнала. CardDetailsRevealsPage
GET/cards/{id}/details

Реквизиты карты

Номер, CVV, срок и адрес плательщика активной или замороженной карты, чтобы оплатить ею подписку. xWires реквизиты не хранит и читает у банка; каждый показ записан в журнал, который видят админы компании. Не больше 10 показов в час на ключ: 429 card_details_rate_limited с Retry-After. Ответ не пишите в логи; храните реквизиты так, как требует PCI DSS, или не храните. Нужно право действий: это не просто чтение. Банк не ответил — 503 service_unavailable.

Ключ: Чтение и действия и право Карты

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

События

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, subscription.renewal_failed, subscription.cancelled, card.issued, card.topped_up, card.withdrawn, card.frozen, card.unfrozen, card.closed, card.replaced, card.operation_failed, card.transaction. (необязательное)
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); продление активной подписки он, как рабочий xWires, отправляет партнёру в scheduled_for, когда кончится оплаченный месяц. Пока автопродление сотрудника включено, заказ не нужен: auto_renewal_on.

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

Тело: 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. Из scheduled — не раньше scheduled_for, как в проде: продление активной подписки ждёт конца оплаченного месяца. Заказ с renewal_mode monthly выполняется с автопродлением: событие subscription.auto_renewal_started. Каждый шаг — своё событие, без actor. Подтверждают и отклоняют заказ, как всегда, POST /orders/{id}/approve и /reject. После вызова автопилот этот заказ больше не трогает; to, равный нынешнему статусу, только останавливает его.

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

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

Тело: AdvanceSandboxOrderRequest

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

Закончить оплаченный месяц

Только в песочнице. Оплаченный месяц активной подписки кончается сейчас, и дальше всё как в проде в этот момент. С включённым автопродлением подписку продлевает OpenAI: появляется заказ продления с origin auto_renewal, он выполняется, приходят order.scheduled, order.sent, order.succeeded и subscription.renewed, expires_at — через календарный месяц; renewal в теле показывает неудачу продления. Без автопродления: если продление ждёт конца месяца (scheduled), оно сразу уходит партнёру, подписка остаётся active, а после выполнения — subscription.renewed и expires_at через месяц; продление, которое вы двигаете сами, ждёт вашего перевода. Иначе подписка истекает: subscription.expired, следующий заказ сотрудника — продление. Тело пустое, {} или с renewal.

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

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

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

200
Сотрудник: подписка продлена, истекла или, если продление ушло партнёру, ещё активна. Employee
POST/sandbox/cards/{id}/transactions

Транзакция по карте

Только в песочнице. Магазин провёл по карте покупку, холд, отказ или возврат, и банк записал это, как в проде: транзакция появляется в GET /cards/{id}/transactions, приходит card.transaction без actor. Покупка и холд уменьшают available карты, возврат увеличивает, отказ не трогает. Покупку и холд, которые карта не оплатит (заморожена или на ней меньше суммы), банк отклоняет. Четвёртый отказ по карте закрывает её, как банк: card.closed с closed_reason issuer, а при тарифе «риски на нас» xWires выпускает замену (card.replaced). Карта ещё не выпущена — 409 card_not_active, закрыта или не выпустилась — 409 card_closed.

Ключ: Чтение и действия и право Карты

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

Тело: CreateSandboxCardTransactionRequest

201
Транзакция, как её покажет список транзакций карты. CardTransaction

Схемы

Поля ответа обязательны и приходят всегда, иногда со значением 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, в песочнице — xwc_test_…last4. Шаблон: ^[0-9a-f]{4}$
key.created_atТипdate-timeОписаниеКогда ключ выпустили.
key.cardsТипbooleanОписаниеПраво «Карты»: ключ работает с картами компании. Его дают при выпуске ключа; у ключей, выпущенных раньше, его нет.
companyТипobjectОписание
company.idТипuuidОписаниеid компании.
company.nameТипstringОписаниеНазвание компании.
company.cards_enabledТипbooleanОписаниеxWires включил компании карты: можно выпускать и пополнять. false — выпуск и пополнение отвечают 409 cards_not_enabled, а карты, что уже есть, можно читать, выводить с них деньги, замораживать и закрывать.

AutoRenewal

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

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

Subscription

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

ПолеТипОписание
chatgpt_emailТипstringОписаниеПочта ChatGPT-аккаунта, который пополняется.
planТип"plus" | "pro100" | "pro200" | "pro500" | …ОписаниеПлан подписки.
statusТип"new" | "processing" | "active" | "expired" | "cancelled" | …Описаниеnew: план выбран, подписка не действует — ещё не оплачена или заказ на истёкшую не выполнен (тогда expires_at в прошлом); 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, submitting → scheduled. Незнакомый статус считайте заказом в работе.
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ОписаниеКогда приглашён.

CardOperation

Операция с картой: принимается сразу, итог приходит событием card.* и виден здесь же. На карте одна операция за раз.

ПолеТипОписание
idТипuuidОписаниеid операции: её же несут события card.* в operation_id.
card_idТипuuidОписаниеКарта операции; у replace — новая карта, которая заменяет закрытую банком.
typeТип"issue" | "top_up" | "withdrawal" | "freeze" | "unfreeze" | "close" | "replace" | …Описаниеissue: выпуск; top_up: пополнение; withdrawal: вывод на баланс компании; freeze, unfreeze: заморозка и разморозка; close: закрытие; replace: замена карты, которую закрыл банк, — её делает xWires.
amountТипstring | nullОписаниеСумма в USD: у выпуска, пополнения и замены — что кладётся на карту; у вывода — запрошенная, а когда он прошёл, — сколько ушло с карты (у вывода всего остатка до итога null); у закрытия — сколько ушло с карты, когда оно прошло; у заморозки и разморозки null. Шаблон: ^[0-9]+\.[0-9]{2}$
referenceТипstring | nullОписаниеreference выпуска, пополнения или вывода, как его прислали; у остальных null.
statusТип"pending" | "succeeded" | "failed" | …Описаниеpending: идёт; succeeded: прошла; failed: не прошла, деньги не двигались.
failure_codeТипstring | nullОписаниеПочему не прошла; null у остальных. insufficient_available — на карте меньше, чем просили вывести; card_closed_by_issuer — банк закрыл карту; provider_declined — поставщик отказал; provider_unavailable — поставщик не выполнил её за сутки; insufficient_funds — выпуску или пополнению не хватило баланса: его успели потратить запросы к моделям, которые оплачиваются с него. Перечень растёт: незнакомый код — просто отказ.
created_atТипdate-timeОписаниеКогда принята.
completed_atТипdate-time | nullОписаниеКогда пришёл итог; null, пока идёт.

Card

Виртуальная карта компании. Деньги на неё идут с баланса компании в xWires; баланса и комиссий в API нет, только деньги на картах.

ПолеТипОписание
idТипuuidОписаниеid карты.
kindТип"chatgpt" | "general" | …Описаниеchatgpt: платит только OpenAI; general: подписки и сервисы.
statusТип"issuing" | "active" | "frozen" | "closed" | "failed" | …Описаниеissuing: выпускается; active: работает; frozen: заморожена, списания отклоняются; closed: закрыта навсегда; failed: не выпущена, деньги вернулись на баланс. Незнакомый статус считайте картой, которая не платит.
networkТип"visa" | "mastercard" | … | nullОписаниеПлатёжная система; null, пока карта выпускается.
last4Типstring | nullОписаниеПоследние 4 цифры номера; null, пока карта выпускается. Шаблон: ^[0-9]{4}$
expiresТипstring | nullОписаниеСрок действия, год и месяц, например 2028-10; null, пока карта выпускается. Шаблон: ^[0-9]{4}-(0[1-9]|1[0-2])$
availableТипstring | nullОписаниеСколько можно потратить с карты, USD, после последней операции; холды и покупки между операциями его не меняют. null, пока карта выпускается. Шаблон: ^[0-9]+\.[0-9]{2}$
labelТипstring | nullОписаниеПодпись, которую дала компания, до 60 символов.
employee_idТипuuid | nullОписаниеСотрудник, для которого карта; null, если не указан или ушёл из компании.
pending_operationТипobject | nullОписаниеОперация, которая идёт; null — никакой. Пока она идёт, другие отвечают 409 card_operation_pending.
pending_operation.idТипuuidОписаниеid операции: GET /card-operations/{id}.
pending_operation.typeТип"issue" | "top_up" | "withdrawal" | "freeze" | "unfreeze" | "close" | "replace" | …ОписаниеВид операции, как type в CardOperation.
closed_reasonТип"company" | "issuer" | … | nullОписаниеКто закрыл: company — компания, issuer — банк; null, пока карта не закрыта.
replaced_byТипuuid | nullОписаниеКарта, которая заменила эту, закрытую банком (card.replaced); null — замены нет.
created_atТипdate-timeОписаниеКогда выпуск принят.
updated_atТипdate-timeОписаниеПоследнее изменение.

CardTransaction

Транзакция по карте: покупка, холд, отказ или возврат, как её провёл банк.

ПолеТипОписание
idТипuuidОписаниеid транзакции: его же несёт card.transaction в transaction_id.
typeТип"purchase" | "refund" | "reversal" | "other" | …Описаниеpurchase: покупка; refund: возврат от магазина; reversal: отмена холда; other: остальное.
statusТип"pending" | "settled" | "declined" | …Описаниеpending: холд, деньги заблокированы; settled: прошла; declined: отклонена, денег не взяли.
amountТипstringОписаниеСумма в валюте списания, без знака: куда ушли деньги, говорит type. Шаблон: ^[0-9]+\.[0-9]{2,4}$
currencyТипstringОписаниеВалюта списания, ISO 4217. Шаблон: ^[A-Z]{3}$
merchant_amountТипstring | nullОписаниеСумма, которую запросил магазин, в его валюте; null, если банк её не дал. Шаблон: ^[0-9]+\.[0-9]{2,4}$
merchant_currencyТипstring | nullОписаниеВалюта магазина. Шаблон: ^[A-Z]{3}$
merchant_nameТипstring | nullОписаниеМагазин, как его назвал банк.
occurred_atТипdate-timeОписаниеКогда магазин провёл её.

CardDetailsReveal

Показ реквизитов карты: кто и когда их открыл — админ компании в кабинете или ключ через API. Реквизитов здесь нет.

ПолеТипОписание
idТипuuidОписаниеid показа.
revealed_atТипdate-timeОписаниеКогда открыли.
user_idТипuuid | nullОписаниеАдмин компании, который открыл реквизиты в кабинете, — как id в GET /employees; null — открыл ключ.
api_key_idТипuuid | nullОписаниеКлюч, который открыл реквизиты через API, — как key.id в GET /me; null — открыл админ.

IssueCardResult

Карта, которая выпускается, и её операция выпуска.

ПолеТипОписание
cardТипCardОписание
operationТипCardOperationОписание

CardDetails

Реквизиты карты: номер, CVV, срок и адрес плательщика. xWires их не хранит и каждый раз читает у банка; показ записан.

ПолеТипОписание
card_idТипuuidОписаниеid карты.
numberТипstringОписаниеПолный номер карты, без пробелов. Шаблон: ^[0-9]{12,19}$
cvvТипstringОписаниеCVV: не храните его. Шаблон: ^[0-9]{3,4}$
expiresТипstringОписаниеСрок действия, год и месяц: 2028-10 — это 10/28. Шаблон: ^[0-9]{4}-(0[1-9]|1[0-2])$
holder_nameТипstringОписаниеИмя держателя: у всех карт одно.
billing_addressТипobject | nullОписаниеАдрес плательщика для форм оплаты, как его дал банк; null, если банк его не дал.
billing_address.line1ТипstringОписание
billing_address.line2ТипstringОписание
billing_address.cityТипstringОписание
billing_address.stateТипstringОписание
billing_address.countryТипstringОписание
billing_address.postal_codeТипstringОписание

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 — страниц больше нет.

CardsPage

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

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

CardTransactionsPage

Страница транзакций карты, сначала новые: по occurred_at и id, по убыванию.

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

CardOperationsPage

Страница операций карты, сначала новые: по created_at и id, по убыванию.

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

CardDetailsRevealsPage

Страница журнала показов реквизитов карты, сначала новые: по revealed_at и id, по убыванию.

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

AddApprovedEmailsResult

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

ПолеТипОписание
approvedТипintegerОписаниеСколько почт сразу подтвердили ждущий заказ.
rememberedТипintegerОписаниеСколько почт добавлены в список и ждут заказа.
skippedТипintegerОписаниеСколько уже были в списке, уже подтвердили незавершённый заказ этого плана или повторялись в запросе.
invalidТипintegerОписаниеСколько не похожи на почту.
invalid_emailsТипstring[]ОписаниеПрисланные строки, которые не похожи на почту, как они пришли, без повторов: их и надо исправить.
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 не сохранён. Повтор с тем же reference — тот же outcome с ответом 200.
account_createdТипbooleanОписаниеtrue: учётную запись xWires для этой почты создал этот запрос (у повтора — первый запрос с этим reference); у invited всегда false.
orderТипOrder | nullОписаниеЗаказ; null у invited.

OffboardResult

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

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

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, subscription.renewal_failed, subscription.cancelled: подписка сотрудника, который сейчас в компании. subscription.renewal_failed — автопродление за счёт компании не продлило подписку: рядом приходят order.failed с origin auto_renewal и subscription.auto_renewal_stopped. subscription.cancelled — сотрудник отказался от активации, пока заказа не было: подписка не действует, заказывать её не нужно; если он подключит её снова, статус вернётся в new без события.

ПолеТипОписание
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" | "subscription.renewal_failed" | "subscription.cancelled" | …Описание
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" | "openai_off" | "plan_changed" | "failed" | …ОписаниеТолько у subscription.auto_renewal_stopped: почему выключено, как stop_reason в AutoRenewal. (необязательное)
data.order_idТипuuidОписаниеТолько у subscription.renewal_failed: заказ продления, GET /orders/{id}. (необязательное)
data.failure_reasonТип"declined" | "not_charged" | "card_unusable" | …ОписаниеТолько у subscription.renewal_failed: declined — OpenAI получил отказ и за неделю не списал оплату; not_charged — OpenAI не пытался списать, скорее всего продление выключено в ChatGPT; card_unusable — карта оплаты не годится. Подписка действует до expires_at, дальше её продлевают новым заказом. Незнакомое значение — тоже неудача. (необязательное)

CardEvent

card.issued, card.topped_up, card.withdrawn, card.frozen, card.unfrozen, card.closed, card.replaced, card.operation_failed: операция с картой прошла или не прошла, банк закрыл карту, xWires её заменил. actor у них null: итог приходит от банка.

ПолеТипОписание
idТипuuidОписаниеid события; он же заголовок webhook-id: повторы отбрасывайте по нему.
timestampТипdate-timeОписаниеКогда это случилось.
positionТипintegerОписаниеНомер события в компании, без пропусков по всем типам; в одном изменении номера могут идти не подряд.
company_idТипuuidОписаниеid компании.
actorТипEventActor | nullОписание
typeТип"card.issued" | "card.topped_up" | "card.withdrawn" | "card.frozen" | "card.unfrozen" | "card.closed" | "card.replaced" | "card.operation_failed" | …Описание
dataТипobjectОписание
data.card_idТипuuidОписаниеКарта: подробности — GET /cards/{id}. У card.replaced — карта, которую закрыл банк.
data.operation_idТипuuid | nullОписаниеОперация; null у card.closed, если карту закрыл банк.
data.typeТип"issue" | "top_up" | "withdrawal" | "freeze" | "unfreeze" | "close" | "replace" | … | nullОписаниеВид операции, как type в CardOperation; null, если её нет.
data.referenceТипstring | nullОписаниеreference операции, как его прислали; null у заморозки, разморозки, закрытия и замены.
data.amountТипstring | nullОписаниеСумма операции, как amount в CardOperation. Шаблон: ^[0-9]+\.[0-9]{2}$
data.availableТипstring | nullОписаниеСколько можно потратить с карты после этого, USD; у card.replaced — с новой карты; null, пока карта не выпущена. Шаблон: ^[0-9]+\.[0-9]{2}$
data.closed_reasonТип"company" | "issuer" | … | nullОписаниеКто закрыл карту: company или issuer; null, если она не закрыта.
data.new_card_idТипuuid | nullОписаниеТолько у card.replaced: новая карта, её реквизиты — GET /cards/{id}/details.
data.failure_codeТипstring | nullОписаниеТолько у card.operation_failed: почему, как failure_code в CardOperation.

CardTransactionEvent

card.transaction: по карте новая покупка, холд, отказ или возврат, или у транзакции сменились статус или сумма. Магазин — в GET /cards/{id}/transactions.

ПолеТипОписание
idТипuuidОписаниеid события; он же заголовок webhook-id: повторы отбрасывайте по нему.
timestampТипdate-timeОписаниеКогда это случилось.
positionТипintegerОписаниеНомер события в компании, без пропусков по всем типам; в одном изменении номера могут идти не подряд.
company_idТипuuidОписаниеid компании.
actorТипEventActor | nullОписание
typeТип"card.transaction" | …Описание
dataТипobjectОписание
data.card_idТипuuidОписаниеКарта.
data.transaction_idТипuuidОписаниеТранзакция, как id в CardTransaction.
data.typeТип"purchase" | "refund" | "reversal" | "other" | …ОписаниеКак type в CardTransaction.
data.statusТип"pending" | "settled" | "declined" | …ОписаниеСтатус после изменения, как в CardTransaction.
data.amountТипstringОписаниеСумма в валюте списания, без знака. Шаблон: ^[0-9]+\.[0-9]{2,4}$
data.currencyТипstringОписаниеВалюта списания. Шаблон: ^[A-Z]{3}$

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 | CardEvent | CardTransactionEvent | 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 | CardEvent | CardTransactionEvent | 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" | "api_key_no_cards" | "invalid_email" | "invalid_plan" | "invalid_amount" | "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" | "card_not_found" | "card_operation_not_found" | "order_not_awaiting_approval" | "already_employee" | "open_order_exists" | "chatgpt_email_taken" | "chatgpt_email_mismatch" | "order_not_awaiting_session" | "plan_mismatch" | "auto_renewal_not_on" | "auto_renewal_on" | "auto_renewal_closed" | "cannot_offboard_admin" | "cards_not_enabled" | "card_operation_pending" | "card_not_active" | "card_not_frozen" | "card_closed" | "reference_conflict" | "insufficient_funds" | "events_cursor_expired" | "api_email_quota_exceeded" | "card_details_rate_limited" | "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 и причина; у invalid_amount, когда сумма за границами вида карты: path amount, minimum и maximum. Значения не повторяются. (необязательное)
error.details.issuesТипobject[]Описание
error.details.issues[].pathТипstringОписаниеПоле, через точку; пусто — весь запрос.
error.details.issues[].issueТипstringОписаниеКод проблемы zod (invalid_type, too_big, unrecognized_keys…); invalid_json с пустым path — тело не разобрать как JSON; у invalid_session — причина: too_large, wrong_shape, email_mismatch, expired; у invalid_amount — too_small или too_big.
error.details.issues[].minimumТипnumberОписаниеУ too_small: наименьшая длина, число элементов или значение; у invalid_amount — наименьшая сумма. (необязательное)
error.details.issues[].maximumТипnumberОписаниеУ too_big: наибольшая длина, число элементов или значение; у invalid_amount — наибольшая сумма. (необязательное)
error.details.issues[].expectedТипstringОписаниеУ invalid_type: какой тип ждали (string, number, boolean, array, object). (необязательное)
error.details.issues[].formatТипstringОписаниеУ invalid_format: какой формат ждали (uuid, datetime, email…). (необязательное)
error.details.issues[].allowedТипstring[]ОписаниеУ invalid_value: допустимые значения. (необязательное)

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 — компания согласна, что подписка будет продлеваться каждый месяц за её счёт, пока её не отключат, а сотрудник — на эти продления; once — разовая активация. Без поля — once. monthly — только пока автопродление для компании открыто, иначе 409 auto_renewal_closed. (необязательное)
referenceТипstringОписаниеid запроса в вашей системе: 1–64 латинских букв, цифр и знаков . _ : -, уникальный в компании среди оформлений подписок (с reference карт он не пересекается). Повтор с тем же reference и тем же телом — тот же заказ в его нынешнем состоянии или то же приглашение, с ответом 200, без нового заказа и писем, в том числе после того, как заказ выполнен; session при сравнении не учитывается. С другим телом — 409 reference_conflict. Запрос, на который пришла ошибка, reference не занимает. Без reference повтор защищает только 409 open_order_exists, пока заказ открыт. (необязательное) Шаблон: ^[A-Za-z0-9._:-]{1,64}$

SubmitOrderSessionRequest

Session ChatGPT-аккаунта заказа.

ПолеТипОписание
sessionТипobjectОписаниеОтвет https://chatgpt.com/api/auth/session целиком, JSON-объектом, от ChatGPT-аккаунта заказа (chatgpt_email). Мы храним его зашифрованным, передаём только партнёру, который оплачивает подписку, и удаляем, как только партнёр принял заказ.
renewalТип"monthly" | "once"Описаниеmonthly — сотрудник согласен на ежемесячные продления: заказ станет monthly, если компания подтвердила его так и автопродление для неё открыто, и тогда в session нужен sessionToken. once или без поля — разовая активация. (необязательное)

IssueCardRequest

Новая карта: вид, сумма на ней и reference.

ПолеТипОписание
kindТип"chatgpt" | "general"Описаниеchatgpt: платит только OpenAI; general: подписки и сервисы.
amountТипstringОписаниеСколько положить на карту, USD, строкой: "50.00". От минимума до максимума вида; с баланса компании спишется она и комиссия. Шаблон: ^(0|[1-9][0-9]{0,7})(\.[0-9]{1,2})?$
referenceТипstringОписаниеid операции в вашей системе: 1–64 латинских букв, цифр и знаков . _ : -, уникальный в компании среди выпусков, пополнений и выводов. Повтор с тем же reference и тем же телом — та же операция (200), с другим телом — 409 reference_conflict. Шаблон: ^[A-Za-z0-9._:-]{1,64}$
labelТипstringОписаниеПодпись карты, до 60 символов. (необязательное, 1–60 символов)
employee_idТипuuidОписаниеСотрудник компании, для которого карта: только пометка, карта принадлежит компании. (необязательное)

TopUpCardRequest

Пополнение: сумма и reference.

ПолеТипОписание
amountТипstringОписаниеСколько добавить, USD, строкой: "20.00". От минимума до максимума вида карты. Шаблон: ^(0|[1-9][0-9]{0,7})(\.[0-9]{1,2})?$
referenceТипstringОписаниеid операции в вашей системе: 1–64 латинских букв, цифр и знаков . _ : -, уникальный в компании среди выпусков, пополнений и выводов. Повтор с тем же reference и тем же телом — та же операция (200), с другим телом — 409 reference_conflict. Шаблон: ^[A-Za-z0-9._:-]{1,64}$

WithdrawFromCardRequest

Вывод на баланс компании: сумма или всё, и reference.

ПолеТипОписание
amountТип"all" | stringОписаниеСколько вывести, USD, строкой, от "0.01", или "all" — всё, что можно: на карте останется минимум вида, деньги под холдом не выводятся.
referenceТипstringОписаниеid операции в вашей системе: 1–64 латинских букв, цифр и знаков . _ : -, уникальный в компании среди выпусков, пополнений и выводов. Повтор с тем же reference и тем же телом — та же операция (200), с другим телом — 409 reference_conflict. Шаблон: ^[A-Za-z0-9._:-]{1,64}$

CreateSandboxEmployeeRequest

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

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

CreateSandboxOrderRequest

Чей заказ.

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

ExpireSandboxSubscriptionRequest

Чем кончится оплаченный месяц подписки с автопродлением; тело может быть пустым.

ПолеТипОписание
renewalТип"renewed" | "declined" | "not_charged" | "card_unusable"ОписаниеТолько для подписки с включённым автопродлением, иначе 409 auto_renewal_not_on. renewed или без поля — OpenAI продлил её: order.succeeded у заказа с origin auto_renewal и subscription.renewed. declined, not_charged, card_unusable — продление не прошло по этой причине, как в проде: order.failed, subscription.renewal_failed с этим failure_reason, subscription.auto_renewal_stopped и subscription.expired. (необязательное)

CreateSandboxCardTransactionRequest

Что магазин сделал с картой песочницы.

ПолеТипОписание
typeТип"purchase" | "hold" | "decline" | "refund"Описаниеpurchase — покупка прошла (settled); hold — холд, деньги заблокированы (pending); decline — банк отказал (declined); refund — возврат магазина на карту (settled). Покупку и холд, которые карта не оплатит (заморожена или денег на ней меньше), банк отклоняет: declined. Четвёртый отказ по карте закрывает её, как банк: card.closed с closed_reason issuer.
amountТипstringОписаниеСумма, USD, строкой: "12.50". От 0.01 до максимума вида карты. Шаблон: ^(0|[1-9][0-9]{0,7})(\.[0-9]{1,2})?$
merchant_nameТипstringОписаниеМагазин, как его назовёт банк; без поля — OPENAI *CHATGPT SUBSCR. (необязательное, 1–200 символов)

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-IdЧто значитid запроса: назовите его поддержке.
RateLimit-LimitЧто значитСколько запросов с ключа можно за минуту.
RateLimit-RemainingЧто значитСколько из них осталось в этой минуте.
RateLimit-ResetЧто значитЧерез сколько секунд минута начнётся заново.
Retry-AfterЧто значитЧерез сколько секунд повторить.

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

События — это изменения заказов, сотрудников, подписок и карт компании. Их можно читать лентой GET /events и получать вебхуками на адреса HTTPS ваших систем. В событии только идентификаторы, статусы, план, вид заказа, роль, время, ключ, если действовал он, а у карт — суммы на карте и reference; подробности система берёт через 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Что случилосьАвтопродление выключено
subscription.renewal_failedЧто случилосьПродление не прошло
subscription.cancelledЧто случилосьОтказ от подписки
card.issuedЧто случилосьКарта выпущена
card.topped_upЧто случилосьКарта пополнена
card.withdrawnЧто случилосьДеньги выведены с карты
card.frozenЧто случилосьКарта заморожена
card.unfrozenЧто случилосьКарта разморожена
card.closedЧто случилосьКарта закрыта
card.replacedЧто случилосьКарта заменена
card.operation_failedЧто случилосьОперация с картой не прошла
card.transactionЧто случилосьТранзакция по карте
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 говорит, кто или почему; subscription.renewal_failed — автопродление не продлило подписку: заказ продления в order_id, причина в failure_reason. Приходят компании, в которой человек сейчас.
  • subscription.cancelled — сотрудник отказался от активации, пока заказа не было: previous_status new. Если он подключит подписку снова, статус вернётся в new без события, а следующий заказ придёт своими order.<status>.
  • card.issued, card.topped_up, card.withdrawn, card.frozen, card.unfrozen, card.closed — операция с картой прошла; card.closed без operation_id — карту закрыл банк; card.replaced — xWires заменил закрытую банком карту (new_card_id); card.operation_failed — операция не прошла, деньги не двигались; card.transaction — покупка, холд, отказ или возврат по карте. У событий карт actor — null: итог приходит от банка, а не от вашего запроса, поэтому система, которая отбрасывает эхо своих действий, их получит. Свою операцию узнавайте по operation_id и reference (раздел 11).
  • Личные заказы и подписки людей вне компании в события не попадают никогда.

Событие

Конверт одинаков у всех типов: 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"
  }
}
subscription.renewal_failed
{
  "id": "8d4b2f61-7a3e-4c95-b1d8-6e2a9f4c7b13",
  "type": "subscription.renewal_failed",
  "timestamp": "2026-11-15T08:04:12.538206+00:00",
  "position": 1188,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "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",
    "order_id": "f3a8c2d6-9b1e-4f74-a5c0-3d7e1b9f6a28",
    "failure_reason": "not_charged"
  }
}
card.transaction
{
  "id": "d9e4b7a2-6c1f-4a38-b5d9-8e3a1c7f2b40",
  "type": "card.transaction",
  "timestamp": "2026-10-21T10:12:03.447192+00:00",
  "position": 1061,
  "company_id": "6f1c2b9e-3d4a-4c71-9a5e-2b8f0d1c7a43",
  "actor": null,
  "data": {
    "card_id": "8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60",
    "transaction_id": "f4b2d8a6-3c1e-4a75-b9d0-7e5a2c8f1b63",
    "type": "purchase",
    "status": "settled",
    "amount": "20.00",
    "currency": "USD"
  }
}

Позиция

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-idЧто значитid события, одинаковый во всех попытках: повторы отбрасывайте по нему.
webhook-timestampЧто значитВремя попытки в секундах Unix. Отклоняйте запросы, которые расходятся с вашими часами больше чем на 300 секунд.
webhook-signatureЧто значитv1, и base64 от HMAC-SHA256 строки <webhook-id>.<webhook-timestamp>.<тело> на байтах секрета (base64 после whsec_). После смены секрета 24 часа подписей две, через пробел: подходит любая.
content-typeЧто значитapplication/json
user-agentЧто значитxWires-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 может остановить вебхуки компании, тогда в кабинете об этом будет написано.

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

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

16Изменения

ВерсияЧто нового
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_… и 5 запросов раздела «Песочница» (раздел 12). В рабочем 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 октября 2026Что новогоPOST /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, 19 октября 2026Что новогоПесочница. Автопродление открыто каждой её компании: заказ с renewal: monthly включает его, а POST /employees/{id}/auto-renewal/stop отключает. Продление активной подписки уходит партнёру в scheduled_for, как в рабочем xWires, а не сразу: POST /sandbox/orders/{id}/advance не переводит заказ из scheduled раньше (409 sandbox_invalid_transition). POST /sandbox/employees/{id}/expire-subscription заканчивает оплаченный месяц: ждущее продление уходит сразу, и подписка не истекает, а продлевается (subscription.renewed). 409 auto_renewal_on у POST /sandbox/orders и POST /sandbox/employees/{id}/expire-subscription, пока автопродление сотрудника включено.
v1, 19 октября 2026Что новогоДля продлений session сотрудника не нужен: auto_renewal.state не бывает needs_session, значение осталось в перечне. Новая причина stop_reason — openai_off: продление выключено у OpenAI, и автопродление выключается сразу, как только заказ выполнен. Отключение больше не отменяет продление у OpenAI, а удаляет карту оплаты: так OpenAI тоже не спишет.
v1, 19 октября 2026Что новогоСотрудник может сам отменить свой заказ, пока тот ждёт подтверждения или session: придёт order.cancelled с previous_status awaiting_approval или approved. Почта из списка, подтвердившая такой заказ, снова в списке и подтвердит следующий заказ своего плана. В ответе POST /approved-emails — invalid_emails, строки, которые не похожи на почту. В details.issues ошибки 400 invalid_request — нарушенное правило: minimum, maximum, expected, format, allowed.
v1, 21 октября 2026Что новогоКарты компании (раздел 11): 11 запросов с тегом «Карты» и право ключа «Карты», которое дают при выпуске (403 api_key_no_cards). В GET /me — key.cards и company.cards_enabled. События card.issued, card.topped_up, card.withdrawn, card.frozen, card.unfrozen, card.closed, card.replaced, card.operation_failed и card.transaction; фильтр адреса вебхука берёт до 28 типов. Первое поле идемпотентности, reference, и первые суммы в API — деньги на картах. У 400 invalid_amount в details — границы суммы.
v1, 24 октября 2026Что новогоИстория карты: GET /cards/{id}/operations — её операции с итогами, сначала новые, и GET /cards/{id}/details-reveals — журнал показов её реквизитов: кто открыл, админ в кабинете или ключ, и когда. Обоим хватает доступа для чтения и права «Карты».
v1, 24 октября 2026Что новогоПродления по автопродлению: заказ с origin: auto_renewal за 3 дня до next_renewal_at, auto_renewal.state in_progress и attention, новые причины stop_reason — plan_changed и failed. Событие subscription.renewal_failed с order_id и failure_reason: declined, not_charged или card_unusable; фильтр адреса вебхука берёт до 29 типов. Песочница: POST /sandbox/employees/{id}/expire-subscription продлевает подписку с автопродлением и по renewal показывает неудачу продления; 409 auto_renewal_on он больше не отвечает. POST /employees/{id}/offboard возвращает в cancelled_order_ids и отменённое продление.
v1, 25 октября 2026Что новогоПесочница: карты (раздел 12). У её компании они включены с первого входа, с $10 000 тестовых денег; операции заканчивает подставной банк, реквизиты — тестовые. Новый запрос POST /sandbox/cards/{id}/transactions изображает покупку, холд, отказ или возврат; четвёртый отказ закрывает карту, как банк.
v1, 26 октября 2026Что новогоБаланс компании общий для карт и запросов к моделям, которые оплачиваются с него. Новый failure_code у card.operation_failed — insufficient_funds: деньги выпуска или пополнения успели уйти на запросы к моделям.
v1, 28 октября 2026Что новогоPOST /subscriptions использует почту сотрудника с этим планом, которая ждёт в списке, как и заказ, оформленный оператором xWires. Раньше она оставалась в GET /approved-emails и подтверждала следующий заказ сотрудника.
v1, 2 ноября 2026Что новогоСотрудник может отказаться от активации, пока заказа не было: подписка переходит в cancelled, приходит новое событие subscription.cancelled; фильтр адреса вебхука берёт до 30 типов. Если он подключит подписку снова, она вернётся в new. POST /subscriptions оформляет отказанную подписку как новую.
v1, 5 ноября 2026Что новогоPOST /orders/{id}/session: session к заказу, который его ждёт, без кабинета сотрудника. White-label для компаний, которые перепродают подписки своим клиентам: xWires не пишет людям компании, а POST /subscriptions оформляет заказ клиенту компании с её почтой, даже если такая почта уже есть в xWires, и никого не приглашает. У остальных компаний ничего не меняется.
v1, 6 ноября 2026Что новогоPOST /subscriptions принимает reference, как операции с картой: повтор с тем же reference и телом — тот же заказ или приглашение и 200, даже после того, как заказ выполнен, с другим телом — 409 reference_conflict. Поле необязательное: запрос без него работает как раньше.
v1, 6 ноября 2026Что новогоС одного ключа — 300 запросов в минуту вместо 60 и 4 одновременно вместо 2, лента событий тоже (раздел 8).

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