Все ошибки API приходят в одном формате. Ветвитесь по полю errorCode: оно есть в каждом ответе с ошибкой и не меняется, а текст message может уточняться.
{
"statusCode": 400,
"error": "Bad Request",
"errorCode": "QR_INVALID",
"message": "QR код недействителен (код: RQ00010)",
"details": { "nspkCode": "RQ00010" },
"path": "/orders/prepare",
"timestamp": "2026-10-03T10:15:42.120Z",
"requestId": "439436bd-201c-4084-9063-58cfa2cd40ad"
}| Поле | Описание |
|---|---|
statusCode | HTTP-код ответа |
error | Текстовое название HTTP-кода (Bad Request, Not Found, …). У ошибки прав — PERMISSION_DENIED |
errorCode | Машиночитаемый код ошибки — по нему и стройте логику |
message | Описание для человека. У ошибок валидации тела и query — массив строк |
details | Необязательный объект с подробностями: недостающее право, границы суммы, код отказа НСПК и т.п. |
path | Путь запроса вместе с query-строкой |
timestamp | Время ошибки (ISO 8601) |
requestId | Идентификатор запроса — указывайте его в обращениях в поддержку |
Ошибка валидации тела или параметров:
{
"statusCode": 400,
"error": "Bad Request",
"errorCode": "VALIDATION_ERROR",
"message": [
"qrCode must be a string",
"qrCode should not be empty"
],
"path": "/orders/prepare",
"timestamp": "2026-10-03T10:15:42.120Z",
"requestId": "6b0b7c2e-4f5a-4d7e-9f10-2a3b4c5d6e7f"
}Лишние поля в теле запроса не вызывают ошибку — они просто игнорируются.
Несколько ошибок делят один errorCode, а причину уточняют в начале message, до двоеточия:
errorCode | Начало message | Что случилось |
|---|---|---|
BAD_REQUEST | INSUFFICIENT_FUNDS: | Не хватает средств на резерв под выпуск карты |
CARD_RECHARGE_INVALID | INSUFFICIENT_FUNDS: | Не хватает средств на пополнение карты |
CARD_RECHARGE_INVALID | MIN_RECHARGE_AMOUNT: | Сумма пополнения меньше минимальной для компании |
CARD_RECHARGE_INVALID | CARD_RETIRED: | Карта закрывается или закрыта |
CARD_RECHARGE_INVALID | CARD_FAILED: | Карта не была выпущена |
CARD_NOT_ACTIVE | CARD_NOT_ACTIVE: | Операция доступна только для карты в статусе assigned |
CARD_NOT_ACTIVE | CARD_NOT_CLOSEABLE: | Карту в этом статусе нельзя закрыть |
BAD_REQUEST | WEBHOOK_URL_NOT_CONFIGURED: | Тестовый вебхук, а webhookUrl не задан |
Каждый ответ с ошибкой содержит requestId — и в теле, и в заголовке X-Request-Id. Если вы сами передали заголовок X-Request-Id, вернётся ваше значение: так запрос удобно связать со своими логами. Иначе мы генерируем UUID.
| Код | Когда | Что делать |
|---|---|---|
| 200 / 201 | Успех. POST отвечает 201, если в описании эндпоинта не указано иное | — |
| 400 | Ошибка в запросе или бизнес-правило не пускает операцию | Смотрите errorCode, исправьте запрос |
| 401 | Нет ключа, ключ неверный, сессия истекла, компания отключена | Проверьте ключ, см. Аутентификация |
| 403 | У ключа нет нужного права или функция не подключена компании | Смотрите errorCode и required |
| 404 | Объект не найден у вашей компании | Проверьте идентификатор |
| 409 | Конфликт: значение уже занято | Смотрите errorCode; у CARD_EXTERNAL_ID_TAKEN в details — занявший объект |
| 422 | Эквайер карт отклонил операцию или карта ещё не готова | Причина — в message; отказ эквайера начинается с bridge 400: |
| 500 | Внутренняя ошибка | Повторите позже; при повторении — в поддержку с requestId |
| 502 | Внешний сервис не ответил или ответил ошибкой | См. ниже — исход зависит от операции |
| 503 | Сервис временно недоступен | Повторите через несколько секунд |
Собственного ограничения частоты запросов у API нет. Код 429 возможен только как ответ внешнего сервиса, который API передаёт как есть, — в операциях с выплатами и кошельками пользователей. Повторите запрос позже.
Часть операций выполняют внешние сервисы: эквайер карт, сервис кошельков пользователей, сервис выплат. Их ошибки API передаёт с тем же HTTP-кодом и общим errorCode (UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, UNPROCESSABLE, BAD_GATEWAY), а источник виден по началу message: bridge 400: … — эквайер карт, p2p 409: … — сервис выплат, Wallet backend error: 404 — сервис кошельков. Такой 401 или 403 не значит, что неверен ваш ключ.
errorCode | HTTP | Значение |
|---|---|---|
VALIDATION_ERROR | 400 | Тело или query не прошли проверку — message массив; у выплат — строка |
BAD_REQUEST | 400 | Некорректный запрос, причина в message. В том числе id в пути — не UUID (Validation failed (uuid is expected)) |
UNAUTHORIZED | 401 | Внешний сервис отклонил запрос авторизации. Ваш ключ тут ни при чём — напишите в поддержку |
FORBIDDEN | 403 | Доступ запрещён |
NOT_FOUND | 404 | Объект не найден |
CONFLICT | 409 | Конфликт состояния |
UNPROCESSABLE | 422 | Внешний сервис отклонил операцию, причина в message |
INTERNAL_ERROR | 500 | Внутренняя ошибка |
BAD_GATEWAY | 502 | Внешний сервис (эквайер карт, сервис кошельков или выплат) не ответил или ответил ошибкой |
SERVICE_UNAVAILABLE | 503 | Сервис временно недоступен |
errorCode | HTTP | Значение |
|---|---|---|
AUTH_REQUIRED | 401 | Не передан ни ключ, ни сессионный токен |
INVALID_API_KEY | 401 | Ключ не найден |
SESSION_INVALID | 401 | Сессионный токен истёк или повреждён |
INVALID_CREDENTIALS | 401 | Неверный email или пароль |
COMPANY_INACTIVE | 401 | Компания отключена — для ключа участника и при входе |
NO_COMPANY_MEMBERSHIP | 401 | Участник не состоит в компании |
PERMISSION_DENIED | 403 | Нет права, оно названо в required |
errorCode | HTTP | Значение |
|---|---|---|
QR_INVALID | 400 | НСПК отклонил QR (истёк, оплачен, неизвестен); код отказа в details.nspkCode. Только для ссылок без суммы в URL |
QR_RESOLVE_FAILED | 400 | Не удалось получить данные платежа по QR. Только для ссылок без суммы в URL |
QR_AMOUNT_MISSING | 400 | Из QR не удалось получить сумму |
AMOUNT_BELOW_MIN | 400 | Сумма ниже минимальной для компании |
AMOUNT_ABOVE_MAX | 400 | Сумма выше максимальной для компании |
ST00012_SUM_MISSING | 400 | В строке ЖКХ нет поля Sum |
ST00012_SUM_INVALID | 400 | Поле Sum в строке ЖКХ некорректно |
GIBDD_UIN_INVALID | 400 | УИН — не 20–25 цифр |
FINE_NOT_FOUND | 400 | Штраф по УИН не найден |
FINE_ALREADY_PAID | 400 | Штраф уже оплачен |
QUOTE_EXPIRED | 400 | Котировка истекла (живёт 30 секунд) |
QUOTE_NOT_FOUND | 404 | Котировки нет: id неверный, по ней уже создан ордер или она удалена после истечения |
INSUFFICIENT_BALANCE | 400 | Не хватает доступного баланса |
ORDER_NOT_FOUND | 404 | Ордер не найден |
RATE_UNAVAILABLE | 503 | Курс временно недоступен |
DRIVER_HELPER_TRANSIENT | 503 | Сервис проверки штрафов временно недоступен |
WALLET_BACKEND_ERROR | 502 | Платёжный сервис не принял ордер или спор |
DISPUTE_NOT_ALLOWED | 400 | Спор по ордеру сейчас открыть нельзя |
errorCode | HTTP | Значение |
|---|---|---|
CARDS_NOT_ENABLED | 403 | Программа карт не подключена компании |
IDEMPOTENCY_KEY_REQUIRED | 400 | Не передан обязательный Idempotency-Key |
CARD_NOT_FOUND | 404 | Карта не найдена |
CARD_NOT_OWNED | 403 | Карта или заявка принадлежит другой компании |
CARD_REQUEST_NOT_FOUND | 404 | Заявка на карту не найдена |
CARD_REQUEST_NOT_CANCELABLE | 400 | Заявку нельзя отменить — карта уже выпущена |
CARD_NOT_ACTIVE | 400 / 422 | Карта не в том статусе для операции |
CARD_RECHARGE_INVALID | 400 | Пополнение невозможно, причина в начале message |
CARD_EXTERNAL_ID_TAKEN | 409 | externalId уже занят картой или заявкой |
BRIDGE_ERROR | 409 / 422 / 502 | Компания не подключена к эквайеру карт (422), конфликт при выдаче карты (409) или неожиданный ответ эквайера на выпуск (502). Прочие отказы эквайера — общими кодами, см. выше |
errorCode | HTTP | Значение |
|---|---|---|
MEMBER_NOT_FOUND | 404 | Участник не найден |
CANNOT_CHANGE_OWN_ROLE | 403 | Нельзя менять свою роль |
ROLE_CHANGE_NOT_ALLOWED | 403 | Действие с владельцем без полномочий |
INVITE_NOT_FOUND | 404 | Приглашение не найдено |
INVITE_USED | 400 | Приглашение уже принято |
INVITE_EXPIRED | 400 | Срок приглашения истёк или приглашение отозвано |
INVITE_INVALID | 400 | Email не совпадает с приглашением |
WEBHOOK_DELIVERY_NOT_FOUND | 400 | Доставка вебхука не найдена |
errorCode | HTTP | Значение |
|---|---|---|
PAYOUTS_NOT_ENABLED | 403 | Выплаты не подключены компании |
PAYOUT_NOT_FOUND | 404 | Заявка не найдена |
PAYOUT_REQUISITE_INVALID | 400 | Неверный номер карты или телефона |
PAYOUT_BANK_REQUIRED | 400 | Для выплаты по телефону нужен bankId |
PAYOUT_BANK_UNKNOWN | 400 | Банка нет в справочнике |
PAYOUT_ACTIVE_LIMIT | 400 | Достигнут лимит активных заявок |
PAYOUT_REQUISITE_COOLDOWN | 400 | На этот реквизит недавно уже выплачивали |
PAYOUT_OUTSIDE_WORKING_HOURS | 400 | Вне часов приёма заявок |
PAYOUT_NOT_CANCELABLE | 400 | Заявку уже нельзя отменить |
PAYOUT_DISPUTE_NOT_ALLOWED | 400 | Спор по заявке сейчас открыть нельзя |
PAYOUT_SERVICE_REJECTED | 400 | Сервис выплат не принял заявку, средства вернулись |
PAYOUT_SERVICE_UNAVAILABLE | 503 | Сервис выплат недоступен. Сообщение «Сервис выплат сейчас не отвечает. Заявка принята…» — заявка принята, средства не возвращены. «Выплаты временно недоступны» — заявка не создавалась |
Список кодов пополняется. Неизвестный errorCode обрабатывайте как общую ошибку для своего statusCode.
| Ответ | Повторять? |
|---|---|
| 400, 401, 403, 404, 409, 422 | Нет — сначала исправьте причину |
| 500 | Да, с паузами. Изменяющие запросы — только с тем же Idempotency-Key |
| 502 | Зависит от операции, см. ниже |
| 503 | Да, через несколько секунд. PAYOUT_SERVICE_UNAVAILABLE — только с тем же Idempotency-Key |
Что значит 502 для разных операций:
POST /orders/accept/{id}— ордер не создан и средства не заморожены. Можно повторить, пока котировка не истекла. Если502повторяется для одной и той же котировки, платёж этому получателю не принимается — повтор не поможет. Недоступность платёжного сервиса может прийти и как500— ордер в этом случае тоже не создан.POST /cards/request— эквайер не ответил, исход неизвестен: карта могла выдаться. Резерв возвращается сразу, а выданная карта придёт вебхукомcard.assigned. Повторяйте только с тем жеIdempotency-Key.POST /cards/rechargeотвечает502, только если эквайер не ответил при расчёте стоимости, — списания тогда не было. Если сбой случился при самом пополнении, ответ —201со статусомpending, а пополнение досверяется автоматически, см. Пополнение карты.
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; ; attempt++) {
const response = await fetch(url, options);
if (response.ok) return response;
const body = await response.json().catch(() => null);
const retriable = response.status === 500 || response.status === 503;
if (!retriable || attempt === maxRetries) {
const err = new Error(body?.message ?? `HTTP ${response.status}`);
err.errorCode = body?.errorCode;
err.requestId = body?.requestId ?? response.headers.get('x-request-id');
throw err;
}
const delay = Math.min(1000 * 2 ** attempt, 10_000);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}Сохраняйте requestId, errorCode и path каждой ошибки — по requestId поддержка найдёт запрос у себя:
function logApiError(body) {
console.error({
requestId: body.requestId,
errorCode: body.errorCode,
statusCode: body.statusCode,
path: body.path,
message: body.message,
});
}При повторяющихся ошибках 5xx напишите в @lumo_support_bot и укажите requestId, время, эндпоинт и тело запроса (без ключа).