Ошибки API: единый контракт
HTTP-код обозначает категорию отказа, а машинный code уточняет причину. Выберите единый формат ошибок, чтобы обрабатывать ответы разных парсеров одним способом.
Выбор формата
| Значение | Поведение |
|---|---|
| Без заголовка | Прежние статусы и формат ошибок: legacy. |
legacy | Явный выбор прежнего формата. |
problem-v1 | Единый контракт ниже: application/problem+json. |
| Пустое, неизвестное или повторённое значение | 400 в прежнем формате; при превышении IP-лимита — 429. Передавайте заголовок один раз. |
Значения чувствительны к регистру. Параметры URL, JSON и Accept не переключают формат. Заголовок применяется к API парсеров и аналитики; веб-страницы, кабинетные токены, биллинг и /api/v1/jobs сохраняют свой договор.
Пример запроса
curl -i -X POST "https://bhapi.ru/ozon/api/v1/item/detail-by-url" \
-H "X-API-Token: ВАШ_API_TOKEN" \
-H "X-BHAPI-Error-Format: problem-v1" \
-H "Content-Type: application/json" \
--data '{"url":"https://www.ozon.ru/product/primer-tovara-123456789/"}'
Успешный ответ сохраняет прежние тело и Content-Type. Ошибки нового формата имеют Content-Language: ru, Cache-Control: no-store и X-Request-ID. На ответах API обеих версий присутствует Vary: X-BHAPI-Error-Format.
Структура ответа
Основа — Problem Details (RFC 9457). Поля code, request_id и retry — обязательные расширения BHAPI.
{
"type": "https://bhapi.ru/docs/errors#rate_limit_exceeded",
"title": "Превышена частота запросов",
"status": 429,
"detail": "Увеличьте интервал между запросами.",
"code": "rate_limit_exceeded",
"request_id": "798c170a130d4c4bb4a3cc1c5e546e44",
"scope": "ip_rate",
"retry": {"action": "wait", "after_seconds": 30}
}
Здесь Retry-After: 30 совпадает с retry.after_seconds. 30 секунд — пример известной паузы, а не значение по умолчанию.
| Поле | Тип | Смысл |
|---|---|---|
type | string | Идентификатор причины: https://bhapi.ru/docs/errors#<code>. |
title | string | Краткий русский заголовок. |
status | integer | Фактический HTTP-статус. |
detail | string | Безопасное пояснение. В новом формате это строка. |
code | string | Машинный код из каталога ниже. |
request_id | string | Серверный UUID: 32 шестнадцатеричных символа, совпадает с X-Request-ID. |
retry | object | Действие клиента и, если известен, интервал ожидания. |
scope | string | Необязательная область известного лимита. |
issues | array<object> | Необязательные сведения об ошибках входных полей. |
Обрабатывайте code или type, а не текст title/detail. Для неизвестного будущего кода используйте HTTP-категорию и retry.action. Дополнительные поля можно игнорировать. instance и сведения о списании usage в этой версии отсутствуют.
Коды и причины
Таблица относится к problem-v1. Конкретные методы возвращают применимые к ним причины. Идентификатор type ведёт к соответствующему коду в этой таблице.
| Код | Причина |
|---|---|
| 400 | token_transport_invalid: Недопустимый способ передачи API-токена.malformed_request: Некорректный синтаксис запроса. |
| 401 | authentication_required: API-токен не передан.invalid_api_token: API-токен недействителен. |
| 403 | access_denied: Доступ запрещён.parser_access_denied: Токен не даёт доступа к этому направлению.subscription_inactive: Нет действующего доступа по подписке. |
| 404 | item_not_found: Подтверждено отсутствие товара.route_not_found: Маршрут API не найден.job_not_found: Задание не найдено или недоступно. |
| 405 | method_not_allowed: HTTP-метод не поддерживается; проверьте заголовок Allow. |
| 409 | request_in_progress: Необходимый слот операции уже занят. |
| 413 | request_too_large: Тело запроса превышает допустимый размер.image_too_large: Изображение превышает допустимый размер. |
| 415 | unsupported_media_type: Неподдерживаемый Content-Type или тип изображения. |
| 422 | invalid_request: Неверные значения параметров, URL, cursor или фильтров. |
| 429 | rate_limit_exceeded: Лимит частоты запросов или API-токена.quota_exhausted: Исчерпана тарифная квота. |
| 500 | internal_error: Внутренняя ошибка BHAPI.finalization_failed: Сбой завершения после получения результата; проверьте исход операции. |
| 502 | upstream_invalid_response: Ответ источника не прошёл проверку.item_fetch_failed: Не удалось подтвердить карточку товара.upstream_request_rejected: Источник отклонил запрос без подтверждённой ошибки клиента. |
| 503 | parser_overloaded: Временная перегрузка парсера.upstream_unreachable: Не удалось соединиться с источником.upstream_access_restricted: Источник ограничил доступ.parser_internal_error: Внутренняя ошибка источника. |
| 504 | upstream_timeout: Истёк срок ожидания источника. |
| Другой 4xx | request_rejected: Запрос отклонён; проверьте параметры и описание метода. |
Действия и квоты
| Действие | Что делать |
|---|---|
fix_request | Исправить параметры, метод или формат данных. |
provide_token | Передать действующий API-токен. |
review_access | Проверить права токена и активный доступ. |
review_plan | Проверить квоту и тариф в кабинете. |
wait_for_reset | Дождаться восстановления лимита. |
wait | Выдержать указанную паузу перед новым вызовом. |
check_outcome | Сверить исход текущего вызова перед новым запросом. |
contact_support | Обратиться в поддержку с X-Request-ID и временем запроса. |
retry.after_seconds — необязательный integer от 1 до 86400, только для wait и wait_for_reset, если время известно. Отсутствие этого поля не означает, что можно повторить немедленно. Когда интервал есть, Retry-After содержит то же число секунд.
| Значение | Смысл |
|---|---|
ip_rate | Частота запросов с IP; учитывайте общий адрес за proxy. |
token_day | Дневной лимит API-токена. |
token_total | Общий лимит токена; автоматическое восстановление не обещается. Действие по умолчанию — review_access: проверьте ограничения ключа. |
free_quota | Free-квота. Другой ключ не восстанавливает грант; ежемесячное обновление не предусмотрено. |
paid_monthly | Месячная квота платного тарифа; проверьте период в кабинете. |
scope отсутствует, если область не установлена. Перегрузка парсера — 503 parser_overloaded, а не расходуемая клиентская квота.
HTTP-ошибка не гарантирует отсутствие списания. При check_outcome сначала сверьте результат по X-Request-ID и времени запроса. Действие не гарантирует идемпотентность; новый вызов может повлечь новый расход. Переход между форматами не отменяет выполненную операцию.
Ошибки входных полей
issues содержит до 20 безопасных пояснений. Поля записи: location (body, query, path или header), path (массив имён полей и индексов), code, message. Для фильтров возможен available — список допустимых значений.
Сырые входные значения и секреты не возвращаются. Для секретных полей подробный путь может отсутствовать; полный список ошибок и вариантов не гарантируется. Исправляйте разрешённые параметры, не извлекайте значения из текста сообщения.
Переход и ограничения
Сначала добавьте обработку application/problem+json, code, request_id и retry.action; затем включите заголовок на нужных вызовах. В legacy тело ошибки может иметь строку, список или объект detail; новый объект возвращается непосредственно в корне JSON.
В новом формате перегрузка меняется с 429 на 503, занятой слот — с 429 на 409, отказ подтверждения карточки — с 417 на 502. Без заголовка прежние статусы сохраняются. Подтверждённый отказ единственного синхронного результата получает HTTP-ошибку; пустой успешный поиск остаётся успехом.
batch и job: ошибки отдельных элементов пакета и состояния заданий внутри успешного ответа не преобразуются в Problem Details. Общий отказ доступа, входных данных или выполнения запроса к parser task API может возвращать новый формат. Кабинетный /api/v1/jobs в него не входит.
Чтобы вернуться к прежнему формату, уберите заголовок или укажите legacy. Не повторяйте запрос автоматически при неясном исходе. Для браузерных клиентов проверьте, разрешают ли ваши CORS и proxy передачу заголовка и чтение X-Request-ID/Retry-After.