Ошибки
Sursa сообщает HTTP-статусом, выполнен ли запрос, а стабильным кодом ошибки — почему не выполнен. Все ответы с ошибкой устроены одинаково:
{
"detail": "the cursor is malformed or was issued for different filters",
"error": {
"code": "api.cursor_invalid",
"message": "the cursor is malformed or was issued for different filters",
"details": {"parameter": "cursor"},
"request_id": "01JB7Q2M4X9V3K8T5N6P0R1S2A",
"doc_url": "https://developers.sursa.com/errors/api.cursor_invalid"
}
}
| Поле | Значение |
|---|---|
error.code | Стабильный код вида <area>.<reason>. Ветвите по нему свой код. |
error.message | Понятное объяснение на английском. Его формулировка может измениться в любой момент; не разбирайте его. |
error.details | Структурированные сведения об ошибке, например параметр, ограничение или когда повторить. Состав полей зависит от кода. |
error.request_id | Идентификатор этого запроса — тот же, что в заголовке ответа X-Request-Id. |
error.doc_url | Страница об этом коде на этом портале. |
detail | Прежняя форма ошибки, оставленная для существующих интеграций. Новому коду следует читать error. |
У каждого кода есть своя страница в разделе Коды ошибок: HTTP-статус, поля details и операции,
которые его возвращают. На странице каждой операции в справочнике API перечислены коды, которые
эта операция может вернуть.
HTTP-статусы
| Статус | Значение |
|---|---|
200, 201, 202, 204 | Запрос выполнен. 202 означает, что запрос принят и ещё обрабатывается. |
400 | Запрос составлен неверно: ошибка в параметре, курсоре, metadata или Idempotency-Key. Исправьте запрос. |
401 | Нет действующего ключа. См. Аутентификация. |
402 | Достигнут лимит тарифа организации, например по числу карточек или объёму хранилища. |
403 | Ключ действует, но это делать ему нельзя: не хватает роли, области или возможности тарифа. |
404 | Объекта нет, или ключу нельзя его видеть. |
409 | Запрос противоречит текущему состоянию: повторяющийся external_id, открытая синхронизация, ещё выполняется запрос с тем же Idempotency-Key. |
410 | Срок жизни объекта истёк, как у загрузки через 24 часа, или маршрут выведен из работы. |
413 | Тело или файл слишком большие. |
415 | Тип файла не поддерживается, или его содержимое не соответствует имени. |
422 | Запрос составлен правильно, но недопустим: не хватает поля или значение вне диапазона. |
429 | Слишком много запросов, или исчерпана квота. См. Лимиты частоты и квоты. |
500, 502, 503, 504 | Сбой у Sursa. Повторите позже. |
Ошибки проверки
Для 422 с кодом validation.failed в detail перечислены все поля, не прошедшие
проверку, и у каждого указано, где оно находится:
{
"detail": [
{"loc": ["body", "title"], "msg": "String should have at least 1 character", "type": "string_too_short"}
],
"error": {"code": "validation.failed", "message": "request validation failed", "request_id": "01JB7…"}
}
Какие ошибки повторять
| Ошибка | Повторять? |
|---|---|
| Сетевая ошибка или тайм-аут, ответа нет | Да, с тем же Idempotency-Key. |
429 | Да, через столько секунд, сколько указано в Retry-After. |
500, 502, 503, 504 | Да, с экспоненциальной паузой: 1 с, 2 с, 4 с… до минуты, со случайным разбросом. |
409 api.idempotency_in_progress | Да, после Retry-After: первый запрос с этим ключом ещё выполняется. |
Любая другая 4xx | Нет. Тот же запрос снова завершится ошибкой; сначала исправьте его. |
Повторяйте запросы POST и PATCH только с Idempotency-Key, чтобы повтор после
потерянного ответа не создал второй объект. GET, PUT и DELETE безопасно повторять как есть.
// Retries a request on network errors, 429 and 5xx. The same Idempotency-Key is sent on every attempt.
async function withRetries(send, attempts = 5) {
for (let attempt = 1; ; attempt++) {
let response;
try {
response = await send();
} catch (error) {
if (attempt >= attempts) throw error;
}
if (response && response.status !== 429 && response.status < 500) return response;
if (attempt >= attempts) return response;
const retryAfter = Number(response?.headers.get("Retry-After"));
const backoff = Math.min(60, 2 ** (attempt - 1)) * (0.8 + Math.random() * 0.4);
await new Promise((resolve) => setTimeout(resolve, 1000 * (retryAfter || backoff)));
}
}
const key = crypto.randomUUID();
const response = await withRetries(() =>
fetch("https://api.sursa.com/v1/cards/text", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SURSA_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify({ title: "Refund policy", body_markdown: "Refunds take 14 days." }),
}),
);Как сообщить о проблеме
У каждого ответа, успешного или нет, есть заголовок X-Request-Id, а у каждой ошибки то же значение есть в
error.request_id. Записывайте его в журнал вместе со своей записью о запросе. Обращаясь в поддержку по поводу
запроса, сообщите его идентификатор: по нему сразу находится запись об этом запросе.
Незнакомые коды
Новые коды ошибок могут появиться в любой версии API. Обрабатывайте незнакомый код по его HTTP-статусу: незнакомую
4xx — как запрос, который в нынешнем виде не выполнится, незнакомую 5xx — как временный сбой.