Перейти к содержимому
Последнее обновление

Обработка ошибок

Все ошибки 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"
}
ПолеОписание
statusCodeHTTP-код ответа
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_REQUESTINSUFFICIENT_FUNDS:Не хватает средств на резерв под выпуск карты
CARD_RECHARGE_INVALIDINSUFFICIENT_FUNDS:Не хватает средств на пополнение карты
CARD_RECHARGE_INVALIDMIN_RECHARGE_AMOUNT:Сумма пополнения меньше минимальной для компании
CARD_RECHARGE_INVALIDCARD_RETIRED:Карта закрывается или закрыта
CARD_RECHARGE_INVALIDCARD_FAILED:Карта не была выпущена
CARD_NOT_ACTIVECARD_NOT_ACTIVE:Операция доступна только для карты в статусе assigned
CARD_NOT_ACTIVECARD_NOT_CLOSEABLE:Карту в этом статусе нельзя закрыть
BAD_REQUESTWEBHOOK_URL_NOT_CONFIGURED:Тестовый вебхук, а webhookUrl не задан

Request ID

Каждый ответ с ошибкой содержит requestId — и в теле, и в заголовке X-Request-Id. Если вы сами передали заголовок X-Request-Id, вернётся ваше значение: так запрос удобно связать со своими логами. Иначе мы генерируем UUID.

HTTP-коды

КодКогдаЧто делать
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 не значит, что неверен ваш ключ.

Коды ошибок

Общие

errorCodeHTTPЗначение
VALIDATION_ERROR400Тело или query не прошли проверку — message массив; у выплат — строка
BAD_REQUEST400Некорректный запрос, причина в message. В том числе id в пути — не UUID (Validation failed (uuid is expected))
UNAUTHORIZED401Внешний сервис отклонил запрос авторизации. Ваш ключ тут ни при чём — напишите в поддержку
FORBIDDEN403Доступ запрещён
NOT_FOUND404Объект не найден
CONFLICT409Конфликт состояния
UNPROCESSABLE422Внешний сервис отклонил операцию, причина в message
INTERNAL_ERROR500Внутренняя ошибка
BAD_GATEWAY502Внешний сервис (эквайер карт, сервис кошельков или выплат) не ответил или ответил ошибкой
SERVICE_UNAVAILABLE503Сервис временно недоступен

Аутентификация и доступ

errorCodeHTTPЗначение
AUTH_REQUIRED401Не передан ни ключ, ни сессионный токен
INVALID_API_KEY401Ключ не найден
SESSION_INVALID401Сессионный токен истёк или повреждён
INVALID_CREDENTIALS401Неверный email или пароль
COMPANY_INACTIVE401Компания отключена — для ключа участника и при входе
NO_COMPANY_MEMBERSHIP401Участник не состоит в компании
PERMISSION_DENIED403Нет права, оно названо в required

Ордера и котировки

errorCodeHTTPЗначение
QR_INVALID400НСПК отклонил QR (истёк, оплачен, неизвестен); код отказа в details.nspkCode. Только для ссылок без суммы в URL
QR_RESOLVE_FAILED400Не удалось получить данные платежа по QR. Только для ссылок без суммы в URL
QR_AMOUNT_MISSING400Из QR не удалось получить сумму
AMOUNT_BELOW_MIN400Сумма ниже минимальной для компании
AMOUNT_ABOVE_MAX400Сумма выше максимальной для компании
ST00012_SUM_MISSING400В строке ЖКХ нет поля Sum
ST00012_SUM_INVALID400Поле Sum в строке ЖКХ некорректно
GIBDD_UIN_INVALID400УИН — не 20–25 цифр
FINE_NOT_FOUND400Штраф по УИН не найден
FINE_ALREADY_PAID400Штраф уже оплачен
QUOTE_EXPIRED400Котировка истекла (живёт 30 секунд)
QUOTE_NOT_FOUND404Котировки нет: id неверный, по ней уже создан ордер или она удалена после истечения
INSUFFICIENT_BALANCE400Не хватает доступного баланса
ORDER_NOT_FOUND404Ордер не найден
RATE_UNAVAILABLE503Курс временно недоступен
DRIVER_HELPER_TRANSIENT503Сервис проверки штрафов временно недоступен
WALLET_BACKEND_ERROR502Платёжный сервис не принял ордер или спор
DISPUTE_NOT_ALLOWED400Спор по ордеру сейчас открыть нельзя

Карты

errorCodeHTTPЗначение
CARDS_NOT_ENABLED403Программа карт не подключена компании
IDEMPOTENCY_KEY_REQUIRED400Не передан обязательный Idempotency-Key
CARD_NOT_FOUND404Карта не найдена
CARD_NOT_OWNED403Карта или заявка принадлежит другой компании
CARD_REQUEST_NOT_FOUND404Заявка на карту не найдена
CARD_REQUEST_NOT_CANCELABLE400Заявку нельзя отменить — карта уже выпущена
CARD_NOT_ACTIVE400 / 422Карта не в том статусе для операции
CARD_RECHARGE_INVALID400Пополнение невозможно, причина в начале message
CARD_EXTERNAL_ID_TAKEN409externalId уже занят картой или заявкой
BRIDGE_ERROR409 / 422 / 502Компания не подключена к эквайеру карт (422), конфликт при выдаче карты (409) или неожиданный ответ эквайера на выпуск (502). Прочие отказы эквайера — общими кодами, см. выше

Команда и вебхуки

errorCodeHTTPЗначение
MEMBER_NOT_FOUND404Участник не найден
CANNOT_CHANGE_OWN_ROLE403Нельзя менять свою роль
ROLE_CHANGE_NOT_ALLOWED403Действие с владельцем без полномочий
INVITE_NOT_FOUND404Приглашение не найдено
INVITE_USED400Приглашение уже принято
INVITE_EXPIRED400Срок приглашения истёк или приглашение отозвано
INVITE_INVALID400Email не совпадает с приглашением
WEBHOOK_DELIVERY_NOT_FOUND400Доставка вебхука не найдена

Выплаты

errorCodeHTTPЗначение
PAYOUTS_NOT_ENABLED403Выплаты не подключены компании
PAYOUT_NOT_FOUND404Заявка не найдена
PAYOUT_REQUISITE_INVALID400Неверный номер карты или телефона
PAYOUT_BANK_REQUIRED400Для выплаты по телефону нужен bankId
PAYOUT_BANK_UNKNOWN400Банка нет в справочнике
PAYOUT_ACTIVE_LIMIT400Достигнут лимит активных заявок
PAYOUT_REQUISITE_COOLDOWN400На этот реквизит недавно уже выплачивали
PAYOUT_OUTSIDE_WORKING_HOURS400Вне часов приёма заявок
PAYOUT_NOT_CANCELABLE400Заявку уже нельзя отменить
PAYOUT_DISPUTE_NOT_ALLOWED400Спор по заявке сейчас открыть нельзя
PAYOUT_SERVICE_REJECTED400Сервис выплат не принял заявку, средства вернулись
PAYOUT_SERVICE_UNAVAILABLE503Сервис выплат недоступен. Сообщение «Сервис выплат сейчас не отвечает. Заявка принята…» — заявка принята, средства не возвращены. «Выплаты временно недоступны» — заявка не создавалась

Список кодов пополняется. Неизвестный 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, а пополнение досверяется автоматически, см. Пополнение карты.

Exponential backoff

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, время, эндпоинт и тело запроса (без ключа).