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Быстрый старт
- Админ компании открывает «Кабинет компании → Интеграции» и в «Ключах API» создаёт ключ: название, например «1С: кадры», и доступ — «Только чтение» или «Чтение и действия». Ключ показывается один раз, скопируйте его сразу. Всем админам компании придёт письмо о новом ключе. Ключ можно и попросить у оператора xWires: он выпустит его и передаст вам, а у ключа в «Интеграциях» будет видно, что его выпустил оператор.
- Положите ключ в секреты сервера, который ходит в API, под именем
XWIRES_COMPANY_API_KEY. В код, репозиторий, браузер и мобильное приложение ключ не попадает. - Проверьте ключ:
curl -sS https://api.xwires.ai/company/v1/me \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
{
"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.
Тот же запрос и список заказов, которые ждут подтверждения, из кода:
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);# 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 написан для человека и может меняться.
{
"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.
{
"error": {
"code": "invalid_request",
"message": "Запрос не прошёл проверку: details.issues называет поля и что с ними не так.",
"request_id": "d2f6b8a4-9e1c-4a37-85d0-6c3b7e9f1a42",
"details": {
"issues": [
{
"path": "state",
"issue": "unrecognized_keys"
}
]
}
}
}| Код | HTTP | Что значит и что делать |
|---|---|---|
invalid_request | HTTP400 | Что значит и что делатьЗапрос не прошёл проверку: details.issues называет поля и что с ними не так. |
invalid_session | HTTP400 | Что значит и что делать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_cursor | HTTP400 | Что значит и что делатьКурсор не подходит: он испорчен или получен с другими фильтрами. Начните список сначала, без cursor. |
api_key_in_url | HTTP400 | Что значит и что делатьКлюч API передан в адресе запроса, а адреса попадают в логи. Отзовите этот ключ в кабинете и передавайте новый только в заголовке Authorization: Bearer. |
invalid_email | HTTP400 | Что значит и что делатьПроверьте почту: нужен адрес вида name@example.ru. |
invalid_plan | HTTP400 | Что значит и что делатьУкажите план, который оплачивает компания: plus, pro100, pro200, pro500. |
invalid_amount | HTTP400 | Что значит и что делатьСумма не подходит. Это строка в долларах США, с точкой и не больше чем двумя знаками после неё, например "50.00". Выпуск и пополнение — от минимума до максимума вида карты, вывод — от "0.01" или "all"; details.issues называет границы. |
session_token_required | HTTP400 | Что значит и что делатьВ session нет sessionToken, а без него заказ с автопродлением (renewal: monthly) партнёру не передать: пришлите ответ chatgpt.com/api/auth/session целиком. |
session_expires_too_soon | HTTP400 | Что значит и что делатьSession истечёт раньше, чем заказ уйдёт партнёру: продление уходит, когда закончится оплаченный период. Пришлите свежий session ближе к этой дате. |
no_emails | HTTP400 | Что значит и что делатьПришлите хотя бы одну почту. |
too_many_emails | HTTP400 | Что значит и что делатьНе больше 500 почт в одном запросе. |
invalid_api_key | HTTP401 | Что значит и что делатьКлюч API не подходит: передайте его в заголовке Authorization: Bearer xwc_…. |
api_key_revoked | HTTP401 | Что значит и что делатьКлюч API отозван. Выпустите новый: Кабинет компании → Интеграции. |
api_key_read_only | HTTP403 | Что значит и что делатьКлюч API только для чтения: для этого запроса нужен ключ с правом действий. |
api_key_no_cards | HTTP403 | Что значит и что делатьУ ключа API нет права «Карты». Право даётся при выпуске ключа: выпустите новый, Кабинет компании → Интеграции. |
not_found | HTTP404 | Что значит и что делатьТакого адреса в API нет: проверьте путь и метод. |
order_not_found | HTTP404 | Что значит и что делатьЗаказа с таким id у компании нет. |
employee_not_found | HTTP404 | Что значит и что делатьСотрудника с таким id в компании нет: возможно, он уже ушёл. |
approved_email_not_found | HTTP404 | Что значит и что делатьПочты с таким id в списке нет: её уже удалили, или она уже подтвердила заказ. |
invite_not_found | HTTP404 | Что значит и что делатьПриглашения с таким id нет: его уже отозвали или приняли. |
card_not_found | HTTP404 | Что значит и что делатьКарты с таким id у компании нет. |
card_operation_not_found | HTTP404 | Что значит и что делатьОперации с таким id у карт компании нет. |
request_timeout | HTTP408 | Что значит и что делатьТело запроса не пришло целиком за 10 секунд. Повторите запрос. |
order_not_awaiting_approval | HTTP409 | Что значит и что делатьЗаказ не ждёт подтверждения: его уже подтвердили, отклонили или отменили. Прочитайте заказ. |
already_employee | HTTP409 | Что значит и что делатьЧеловек с этой почтой уже сотрудник компании. |
open_order_exists | HTTP409 | Что значит и что делатьУ сотрудника уже есть открытый заказ: следите за ним в GET /orders?employee_id=… или в ленте событий. |
chatgpt_email_taken | HTTP409 | Что значит и что делатьЭтот ChatGPT-аккаунт в xWires уже у другого человека: проверьте, что session от аккаунта сотрудника. Компании с white-label аккаунт личного клиента xWires или другого её клиента перейдёт, когда подписка на него закончится. |
chatgpt_email_mismatch | HTTP409 | Что значит и что делатьSession от другого ChatGPT-аккаунта: продление идёт на аккаунт подписки сотрудника, а session к заказу — на аккаунт заказа. Пришлите session того аккаунта (chatgpt_email заказа или subscription.chatgpt_email в GET /employees/{id}). |
order_not_awaiting_session | HTTP409 | Что значит и что делатьЗаказ не ждёт session: его ещё не подтвердили, session уже прислан или заказ закрыт. Session принимает заказ в статусе approved. Прочитайте заказ. |
plan_mismatch | HTTP409 | Что значит и что делатьПодписка сотрудника — на другой план, а продление идёт на тот же: укажите plan его подписки (GET /employees/{id}). |
auto_renewal_not_on | HTTP409 | Что значит и что делатьАвтопродление сотрудника не включено или его оплачивает не компания: отключать нечего, а renewal в песочнице применяется только к подписке с включённым автопродлением. |
auto_renewal_on | HTTP409 | Что значит и что делатьПодписку сотрудника каждый месяц продлевает автопродление: новый заказ не нужен. Чтобы продлить вручную, сначала отключите его (stopAutoRenewal). |
auto_renewal_closed | HTTP409 | Что значит и что делатьАвтопродление для компании пока закрыто: подтвердите разовую активацию — без renewal или с renewal: "once". |
cannot_offboard_admin | HTTP409 | Что значит и что делатьАдмина компании через API уволившимся не отметить: это делают в кабинете. |
cards_not_enabled | HTTP409 | Что значит и что делатьВыпуск и пополнение карт для компании выключены: их включает xWires. Карты, что уже есть, можно читать, выводить с них деньги, замораживать, закрывать и смотреть их реквизиты. |
card_operation_pending | HTTP409 | Что значит и что делатьС картой уже идёт операция: дождитесь её итога (событие card.* или GET /card-operations/{id}, id — в pending_operation карты) и повторите. |
card_not_active | HTTP409 | Что значит и что делатьКарта не активна: она ещё выпускается или заморожена. Пополнять, выводить и замораживать можно только активную карту. Прочитайте карту. |
card_not_frozen | HTTP409 | Что значит и что делатьКарта не заморожена: размораживать нечего. Прочитайте карту. |
card_closed | HTTP409 | Что значит и что делатьКарта закрыта или так и не выпущена: с ней больше ничего не сделать. |
reference_conflict | HTTP409 | Что значит и что делатьЭтот reference уже занят другим запросом: у операции карты или оформления подписки с ним было другое тело. Новому запросу нужен новый reference, а повтор того же — тот же reference и то же тело. |
insufficient_funds | HTTP409 | Что значит и что делатьНа балансе компании не хватает денег на эту сумму с комиссией: попросите xWires зачислить оплату по счёту. Баланс общий для карт и запросов к моделям, которые оплачиваются с него. Поставщику ничего не ушло, повторите после оплаты. |
membership_not_found | HTTP409 | Что значит и что делатьУ сотрудника нет подписки: заказ создаётся для придуманного сотрудника, POST /sandbox/employees. |
sandbox_invalid_transition | HTTP409 | Что значит и что делатьИз нынешнего статуса заказ в этот не перевести: подтверждает и отклоняет компания, из succeeded, failed, rejected и cancelled дальше пути нет, а из scheduled заказ уходит не раньше scheduled_for: продление активной подписки ждёт конца оплаченного месяца, а закончить его сейчас можно запросом POST /sandbox/employees/{id}/expire-subscription. Прочитайте заказ. |
subscription_not_active | HTTP409 | Что значит и что делатьПодписка сотрудника не активна: завершить нечего. |
events_cursor_expired | HTTP410 | Что значит и что делатьСобытий сразу после этой позиции нет: мы храним их 30 дней, или ленту компании восстановили из резервной копии. Запросите after=latest и сохраните next_after, затем перечитайте списки и продолжайте с сохранённой позиции. |
payload_too_large | HTTP413 | Что значит и что делатьТело запроса больше 256 КБ. |
unsupported_media_type | HTTP415 | Что значит и что делатьТело запроса должно быть JSON с Content-Type: application/json. |
rate_limited | HTTP429 | Что значит и что делатьСлишком много запросов с этим ключом: не больше 300 в минуту и 4 одновременно. Повторите после Retry-After. |
api_email_quota_exceeded | HTTP429 | Что значит и что делатьЗа сутки через API прислали 5000 почт и приглашений компании. Повторите позже или добавьте почты в кабинете. |
card_details_rate_limited | HTTP429 | Что значит и что делатьРеквизиты карт ключ может открыть не больше 10 раз в час. Повторите после Retry-After; храните реквизиты у себя, а не запрашивайте их каждый раз. |
internal_error | HTTP500 | Что значит и что делатьВнутренняя ошибка xWires. Прочитайте данные GET-запросом и повторите; если ошибка не проходит, напишите на support@xwires.ai с request_id. |
service_unavailable | HTTP503 | Что значит и что делать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): в ней те же события, что приходят на вебхуки.
- Запросите
GET /events?after=latestи сохранитеnext_after, затем один раз прочитайте нужные списки целиком. Именно в таком порядке: изменения, сделанные, пока вы читаете списки, придут и в ленте, а обработчик, которому повтор не вредит, примет их ещё раз без последствий. - Раз в 10–60 секунд или на каждый вебхук запрашивайте
GET /events?after=<next_after>, обработайте события и сохраните новыйnext_after. Покаhas_more: true, спрашивайте сразу; на 429 rate_limited и 503 service_unavailable подождитеRetry-Afterи повторите. - На 410 events_cursor_expired начните заново, как в первом шаге: сначала
after=latestиnext_after, потом списки, и продолжайте с сохранённой позиции. - Раз в сутки, например ночью, читайте весь GET /employees для сверки.
// 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 -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 -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"}'{
"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 -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 -sS -X POST https://api.xwires.ai/company/v1/orders/9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14/approve \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
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 -sS -X POST https://api.xwires.ai/company/v1/orders/9c2e4b71-6a3f-4d58-b1e0-7f3a2c9d5e14/reject \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
{
"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 в своём кабинете:
{
"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 -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
{
"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": {
"user": {
"id": "user-Xq3vT8LmN2",
"name": "Олег Смирнов",
"email": "o.smirnov@example.ru"
},
"expires": "2027-01-18T09:41:07.000Z",
"accessToken": "eyJhbGciOiJSUzI1NiIs…",
"sessionToken": "eyJhbGciOiJkaXIiLCJlbmMiOi…"
}
}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
{
"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.
{
"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 -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"
{
"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 -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"}'{
"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 -sS -X POST https://api.xwires.ai/company/v1/employees/3a9e5c71-8b2d-4f04-a6c9-1e7b2d5f8a90/offboard \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
{
"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 -sS "https://api.xwires.ai/company/v1/events?after=1041&types=order.succeeded" \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
Или списком за период, по времени последнего изменения:
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_codeinsufficient_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 | Нужен доступТолько чтение | Что делаетЖурнал показов реквизитов |
Выпустить карту и оплатить подписку
- Выпустите карту: вид, сумма и
reference— id операции в вашей системе. Подпись и сотрудник — по желанию.
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"}'{
"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
}
}- Дождитесь
card.issued: картаactive, у неё последние 4 цифры, сеть, срок и остаток. Не пришло событие — спросите операцию: GET /card-operations/{id}. Все операции карты, с итогами, — GET /cards/{id}/operations. Выпуск не прошёл —card.operation_failed, картаfailed, деньги вернулись на баланс.
{
"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
}
}- Прочитайте реквизиты и введите их в форме оплаты сервиса. Имя держателя у всех карт одно, кода 3-D Secure нет: карта платит без него.
curl -sS https://api.xwires.ai/company/v1/cards/8d3f6a1c-4e2b-4f97-a5c8-1b9e7d2f4a60/details \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
{
"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"
}
}- Списание сервиса придёт событием
card.transaction(раздел 14); магазин и сумма в его валюте — в списке транзакций карты.
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 -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"}'{
"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 -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 -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 — новая. Прочитайте реквизиты новой карты и введите их в сервисах заново. Без замены остаток закрытой карты вернётся на баланс, если банк его отдаст.
{
"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 не попадают.
- Откройте sandbox.xwires.ai и войдите с любой почтой: код придёт на неё. У вас будет своя компания «Песочница», вы — её админ.
- В «Интеграциях» песочницы выпустите ключ, он начинается с
xwc_test_, и, если нужны вебхуки, добавьте адрес. События приходят на него так же, как в рабочем xWires, подписаны так же и с того же IP. Адрес нужен публичный, с HTTPS: localhost и внутренние адреса не подойдут, для машины разработчика нужен туннель с публичным адресом. - Направьте систему на адрес песочницы. Все запросы этого руководства работают и там: замените адрес и ключ. Если клиент берёт адрес из
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_reasonuser,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 -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"}'{
"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 -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 -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 -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и CVV123: им нигде не заплатить. Показ записывается в журнал и идёт в лимит, как в рабочем API. - Сумму с центами
.13— выпуск, пополнение или вывод"10.13"— банк отклоняет:card.operation_failedсfailure_codeprovider_declined, деньги вернулись на баланс. Вывод больше, чем можно, —insufficient_available, как в рабочем xWires. - Покупку, холд, отказ или возврат по карте изображает POST /sandbox/cards/{id}/transactions: приходит
card.transaction, транзакция видна в GET /cards/{id}/transactions, остаток карты меняется. Четвёртый отказ по карте закрывает её, как банк:card.closedсclosed_reasonissuer, а при тарифе «риски на нас» —card.replacedи карта-замена с тем же остатком. - В «Интеграциях» песочницы можно сменить тариф карт: без тарифа выпуск и пополнение отвечают 409 cards_not_enabled. Кнопка «Добавить $10 000» пополняет баланс; когда денег мало, выпуск и пополнение отвечают 409 insufficient_funds.
Отказ по карте, как его записал бы банк:
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"}'{
"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 с ключом.
Ключ
/meКлюч и компания
Проверяет, что ключ действует, и называет его доступ и компанию. key.id сравнивайте с actor.api_key_id событий, чтобы узнавать свои действия.
Ключ: Только чтение
- 200
- Ключ и его компания. Me
- 429
- rate_limited
Сотрудники
/employeesСотрудники
Текущие пользователи компании, админы тоже, с подписками, по id. Ушедших в списке нет.
Ключ: Только чтение
| Параметр | Где | Тип | Описание |
|---|---|---|---|
limit | Гдев запросе | Типinteger | ОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100) |
cursor | Гдев запросе | Типstring | Описаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов) |
- 200
- Страница сотрудников. EmployeesPage
- 429
- rate_limited
/employees/searchНайти сотрудника по почте
Ищет по почте входа и по почте ChatGPT-аккаунта, без учёта регистра: 0, 1 или 2 сотрудника. POST, чтобы почта не попадала в адреса и логи.
Ключ: Только чтение
Тело: SearchEmployeesRequest
- 200
- Найденные сотрудники; next_cursor всегда null. EmployeesPage
- 429
- rate_limited
/employees/{id}Сотрудник
Текущий пользователь компании с подпиской. Ушедший или чужой — 404.
Ключ: Только чтение
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid сотрудника. |
- 200
- Сотрудник. Employee
- 429
- rate_limited
/employees/{id}/offboardОтметить уволившимся
Человек перестаёт быть сотрудником компании, его заказы за счёт компании, ещё не ушедшие партнёру, отменяются. Заказ, который партнёру как раз передают (submitting), остаётся и доходит до итога; если партнёр его так и не получил, он отменится позже, событием order.cancelled. Админа так не отметить (409 cannot_offboard_admin): это делают в кабинете. Тело пустое или {}. Автопродление его подписки за счёт компании отключается (subscription.auto_renewal_stopped, stop_reason offboarded). Вернуть человека можно новым приглашением.
Ключ: Чтение и действия
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid сотрудника. |
Тело: пустое или {}
- 200
- Сотрудник ушёл. OffboardResult
- 429
- rate_limited
/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
- 429
- rate_limited
Заказы
/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
- 429
- rate_limited
/orders/{id}Заказ
Заказ компании.
Ключ: Только чтение
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid заказа. |
- 200
- Заказ. Order
- 429
- rate_limited
/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
- 429
- rate_limited
/orders/{id}/rejectОтклонить заказ
Заказ в статусе awaiting_approval отклоняется, сотрудник видит это в кабинете. Тело пустое или {}. Повтор — 409 order_not_awaiting_approval.
Ключ: Чтение и действия
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid заказа. |
Тело: пустое или {}
- 200
- Заказ после отказа. Order
- 429
- rate_limited
/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
- 400
- invalid_requestapi_key_in_urlinvalid_emailinvalid_planinvalid_sessionsession_token_requiredsession_expires_too_soon
/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
- 429
- rate_limited
Список почт
/approved-emailsСписок почт
Почты, которые подтверждают заказы сотрудников, ещё не использованные, по времени добавления.
Ключ: Только чтение
| Параметр | Где | Тип | Описание |
|---|---|---|---|
limit | Гдев запросе | Типinteger | ОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100) |
cursor | Гдев запросе | Типstring | Описаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов) |
- 200
- Страница списка. ApprovedEmailsPage
- 429
- rate_limited
/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
/approved-emails/{id}Удалить почту из списка
Почта больше не подтверждает заказы. Уже подтверждённые заказы остаются. Повтор — 404.
Ключ: Чтение и действия
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid почты в списке. |
- 204
- Почта удалена.
- 429
- rate_limited
Приглашения
/invitesПриглашения
Приглашения, которые ещё не приняли, по времени. Входил ли приглашённый в xWires, API не говорит.
Ключ: Только чтение
| Параметр | Где | Тип | Описание |
|---|---|---|---|
limit | Гдев запросе | Типinteger | ОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100) |
cursor | Гдев запросе | Типstring | Описаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов) |
- 200
- Страница приглашений. InvitesPage
- 429
- rate_limited
/invitesПригласить
Приглашает почту входа человека с личной почтой: он станет сотрудником, приняв приглашение в кабинете. Заказ не подтверждает: для этого добавьте почту в список. Новое приглашение — 201 и письмо человеку, существующее — 200 без письма. Идёт в суточный лимит (5000 почт и приглашений через API).
Ключ: Чтение и действия
Тело: CreateInviteRequest
- 200
- Такое приглашение уже было. InviteResult
- 201
- Новое приглашение. InviteResult
/invites/{id}Отозвать приглашение
Приглашение больше не действует. Повтор — 404.
Ключ: Чтение и действия
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid приглашения. |
- 204
- Приглашение отозвано.
- 429
- rate_limited
Карты
/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
- 429
- rate_limited
/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
- 429
- rate_limited
/cards/{id}Карта
Карта компании: статус, остаток, операция, которая идёт.
Ключ: Только чтение и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
- 200
- Карта. Card
- 429
- rate_limited
/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
- 409
- cards_not_enabledcard_not_activecard_closedcard_operation_pendinginsufficient_fundsreference_conflict
- 429
- rate_limited
/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
- 429
- rate_limited
/cards/{id}/freezeЗаморозить карту
Активная карта отклоняет списания, пока её не разморозят; деньги ждут на ней. Итог — card.frozen. Тело пустое или {}. Повтор — 409 card_not_active.
Ключ: Чтение и действия и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
Тело: пустое или {}
- 202
- Заморозка принята. CardOperation
- 429
- rate_limited
/cards/{id}/unfreezeРазморозить карту
Замороженная карта снова платит. Итог — card.unfrozen. Тело пустое или {}. Повтор — 409 card_not_frozen.
Ключ: Чтение и действия и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
Тело: пустое или {}
- 202
- Разморозка принята. CardOperation
- 429
- rate_limited
/cards/{id}/closeЗакрыть карту
Навсегда: активная или замороженная карта закрывается, её остаток возвращается на баланс компании, когда поставщик подтвердит закрытие (card.closed, amount — сколько ушло с карты). Подписки, которые она оплачивала, отмените у магазинов заранее. Тело пустое или {}. Повтор — 409 card_closed.
Ключ: Чтение и действия и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
Тело: пустое или {}
- 202
- Закрытие принято. CardOperation
- 429
- rate_limited
/card-operations/{id}Операция с картой
Операция и её итог: на случай, если событие не дошло.
Ключ: Только чтение и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid операции. |
- 200
- Операция. CardOperation
- 429
- rate_limited
/cards/{id}/transactionsТранзакции карты
Покупки, холды, отказы и возвраты по карте, сначала новые, с магазином и суммой в его валюте.
Ключ: Только чтение и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
limit | Гдев запросе | Типinteger | ОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100) |
cursor | Гдев запросе | Типstring | Описаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов) |
- 200
- Страница транзакций. CardTransactionsPage
- 429
- rate_limited
/cards/{id}/operationsОперации карты
Выпуск, пополнения, выводы, заморозки и закрытие карты, сначала новые, с их итогом; у карты-замены первой будет replace. Каждая — как в GET /card-operations/{id}: так можно сверить свои операции, если события не дошли.
Ключ: Только чтение и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
limit | Гдев запросе | Типinteger | ОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100) |
cursor | Гдев запросе | Типstring | Описаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов) |
- 200
- Страница операций. CardOperationsPage
- 429
- rate_limited
/cards/{id}/details-revealsЖурнал показов реквизитов
Кто и когда открывал реквизиты карты, сначала новые: админ компании в кабинете или ключ через API. Тот же журнал видят админы компании и xWires; изменить его нельзя, а чтение журнала — не показ.
Ключ: Только чтение и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
limit | Гдев запросе | Типinteger | ОписаниеСколько строк на странице, от 1 до 200. (необязательное, по умолчанию 100) |
cursor | Гдев запросе | Типstring | Описаниеnext_cursor предыдущей страницы, с теми же фильтрами. (необязательное, 1–512 символов) |
- 200
- Страница журнала. CardDetailsRevealsPage
- 429
- rate_limited
/cards/{id}/detailsРеквизиты карты
Номер, CVV, срок и адрес плательщика активной или замороженной карты, чтобы оплатить ею подписку. xWires реквизиты не хранит и читает у банка; каждый показ записан в журнал, который видят админы компании. Не больше 10 показов в час на ключ: 429 card_details_rate_limited с Retry-After. Ответ не пишите в логи; храните реквизиты так, как требует PCI DSS, или не храните. Нужно право действий: это не просто чтение. Банк не ответил — 503 service_unavailable.
Ключ: Чтение и действия и право Карты
| Параметр | Где | Тип | Описание |
|---|---|---|---|
id | Гдев пути | Типuuid | Описаниеid карты. |
- 200
- Реквизиты. CardDetails
События
/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
- 429
- rate_limited
Песочница
/sandbox/employeesПридуманный сотрудник
Только в песочнице. Новый сотрудник компании с подпиской в статусе new на план из тела: событие employee.joined. Его почты под .invalid, войти за него нельзя. Его заказ — POST /sandbox/orders.
Ключ: Чтение и действия
Тело: CreateSandboxEmployeeRequest
- 201
- Сотрудник. Employee
- 404
- not_found
- 429
- rate_limited
/sandbox/ordersЗаказ сотрудника
Только в песочнице. Сотрудник заказывает месяц подписки, как в своём кабинете: заказ awaiting_approval, или сразу approved, если его почта в списке компании. Активация для новой подписки, продление для активной или истёкшей. Подтверждённый заказ доводит до succeeded автопилот, если его не остановить (POST /sandbox/orders/{id}/advance); продление активной подписки он, как рабочий xWires, отправляет партнёру в scheduled_for, когда кончится оплаченный месяц. Пока автопродление сотрудника включено, заказ не нужен: auto_renewal_on.
Ключ: Чтение и действия
Тело: CreateSandboxOrderRequest
- 201
- Заказ. Order
- 429
- rate_limited
/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
- 429
- rate_limited
/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
- 429
- rate_limited
/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
- 429
- rate_limited
Схемы
Поля ответа обязательны и приходят всегда, иногда со значением 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_statusnew. Если он подключит подписку снова, статус вернётся в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 этого ключа.
{
"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"
}
}{
"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"
}
}{
"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"
}
}{
"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 -sS "https://api.xwires.ai/company/v1/events?after=1041" \ -H "Authorization: Bearer $XWIRES_COMPANY_API_KEY"
{
"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.
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);
});
}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 минут тест считается неудачным. В ленте тестовых событий нет.
{
"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 дней.