Документация/API и Webhook'и/Ошибки и ответы

Ошибки и ответы

Каждый публичный API-эндпоинт возвращает предсказуемые формы ответа. Ошибки несут стабильный машиночитаемый код в теле, чтобы Вы могли разветвлять на нём без парсинга английского.

Конверт ответа

Успешные ответы возвращают ресурс (или список ресурсов) напрямую. Нет верхнеуровневой обёртки — клиенты могут десериализовать прямо в типизированную модель:

GET /api/public/v1/org

{
  "id": "e188fc87-…",
  "name": "Acme Corp",
  "plan": "growth",
  …
}

Ответы об отказе используют небольшую единую форму с кодом error как единственным обязательным полем. Опциональные поля дают контекст, помогающий исправить вызов:

{
  "error": "insufficient_scope",
  "required": "assignments:write",
  "present": ["assignments:read", "users:read"]
}

Справочник кодов состояния

СтатусКогда видитеЧто делать
200Запрос успешен. Тело содержит ресурс или сообщение подтверждения.Обрабатывайте тело.
400Неправильный запрос: отсутствующее обязательное поле, неверное enum-значение, тело слишком большое, не парсящийся JSON.Исправьте клиентский запрос — не повторяйте ту же полезную нагрузку.
401Отсутствующий, неправильно сформированный, отозванный или истёкший токен.Проверьте заголовок Authorization. Ротируйте токен, если он был отозван.
403Токен валиден, но не имеет требуемой области.Добавьте область в админ-интерфейсе; ответ несёт точное имя области.
404Ресурс не существует или принадлежит другой организации.Перезагрузите родительскую коллекцию. Не следуйте устаревшим ID между организациями.
409Конфликт — например, email уже используется.Рассматривайте как успех, если Ваша операция идемпотентна.
413Тело запроса превышает лимит на эндпоинт. Возвращается Kestrel для жёсткого переполнения; эндпоинт SARIF также возвращает это как 400 body_too_large, когда объявленный Content-Length превышает 10 МБ.Сократите полезную нагрузку. Для SARIF принимайте находки одного инструмента инкрементально.
429Лимит запросов достигнут на этом API-ключе.Уважайте Retry-After; см. Лимиты запросов.
5xxПроблема на стороне сервера.Повторите с экспоненциальным backoff. Захватите заголовок ответа X-Request-Id и цитируйте его в любом тикете поддержки.

Коды ошибок

Поле error стабильно между версиями — разветвляйте на нём в клиентском коде, а не на HTTP-статусе, так как один и тот же статус может нести разные коды:

КодСтатусЗначение
unauthorized401Нет пригодного bearer-токена в запросе.
insufficient_scope403Токен в порядке, область отсутствует. Ответ включает required и present.
rate_limited429Лимит превышен. См. заголовок Retry-After.
user_not_found404User ID не существует или не в Вашей организации.
team_not_found404Team ID не существует или не в Вашей организации.
assignment_not_found404Assignment ID не существует или не в Вашей организации.
empty_body400POST или PATCH отправлен без тела, где оно обязательно.
body_too_large400 / 413Тело запроса превысило лимит эндпоинта. SARIF эмитирует это как 400 с maxBytes в теле, когда объявленный Content-Length слишком велик; Kestrel эмитирует голый 413, когда тело фактически переполняется.

Request ID

Каждый ответ несёт заголовок X-Request-Id (UUID v4). Захватите его на стороне клиента и включите в запросы поддержки — это позволяет нам вытащить точный серверный trace для неудачного вызова. Для 5xx-ответов request ID — это единственный самый быстрый путь к диагнозу.

Ошибки валидации

Сбои валидации на телах POST и PATCH возвращают 400 с плоской error, описывающей первое сбойное правило. Многополевая поверхность валидации не часть v1 — исправьте первое сообщённое поле и повторите. Будущие версии могут добавить детали на поле; верхнеуровневый контракт error останется.

Разветвление на ошибках — пример

const res = await fetch(url, { headers });
if (res.ok) return res.json();

const body = await res.json().catch(() => ({}));
switch (body.error) {
  case 'rate_limited':
    return retryWithBackoff(url);
  case 'insufficient_scope':
    throw new Error(`API key missing scope: ${body.required}`);
  case 'unauthorized':
    throw new Error('API key revoked or invalid — rotate the secret');
  default:
    throw new Error(`API error ${res.status}: ${body.error || 'unknown'}`);
}