Обработка ошибок¶
Платформа использует стандартные HTTP-коды и единый JSON-формат ошибок во всех эндпоинтах.
HTTP-коды¶
| Код | Значение | Когда возникает |
|---|---|---|
200 OK |
Успех | Успешные GET/PUT/PATCH-запросы |
201 Created |
Ресурс создан | Успешный POST, создающий новую сущность |
204 No Content |
Успех без тела | Успешные DELETE или назначение роли |
400 Bad Request |
Ошибка валидации или бизнес-правила | Некорректный ввод, отсутствие обязательных полей, нарушение доменных правил |
401 Unauthorized |
Аутентификация не прошла | Отсутствует, недействителен или просрочен ключ; неправильная подпись |
403 Forbidden |
Нет прав | Доступ к ресурсу другой организации, отсутствие нужного скоупа, IP вне списка разрешённых |
404 Not Found |
Ресурс не существует | ID не существует или принадлежит другой организации |
409 Conflict |
Конфликт уникальности | Например, кошелёк по такому активу уже выпущен |
429 Too Many Requests |
Превышен лимит запросов | Исчерпан бюджет запросов на API-ключ или IP. См. Надёжность и лимиты → Лимиты запросов. |
500 Internal Server Error |
Серверная ошибка | Необработанное исключение (логируется на сервере) |
502 Bad Gateway |
Сбой провайдера хранения | Кастоди-бэкенд вернул ошибку или недоступен |
Формат ошибки¶
Все ответы об ошибке используют единую структуру:
{
"statusCode": 400,
"message": "Человекочитаемое описание ошибки.",
"errors": {
"generalErrors": [
"Человекочитаемое описание ошибки."
]
}
}
| Поле | Тип | Описание |
|---|---|---|
statusCode |
integer | HTTP-код |
message |
string | Краткое описание |
errors.generalErrors |
string[] | Один или несколько текстов ошибок |
Для ошибок валидации (от FastEndpoints) в ответе также могут быть ошибки по конкретным полям:
{
"statusCode": 400,
"message": "Произошли одна или несколько ошибок.",
"errors": {
"email": ["Требуется корректный email."],
"password": ["Пароль должен быть не короче 8 символов."]
}
}
Ответы уровня 500¶
Из соображений безопасности ошибки уровня 500 не раскрывают внутренние детали — тело ответа всегда содержит обобщённое сообщение:
{
"statusCode": 500,
"message": "One or more errors occurred.",
"errors": {
"generalErrors": ["An unexpected error occurred."]
}
}
Стратегии обработки¶
Транзиентные ошибки — повторяйте¶
502 Bad Gateway от кастоди-бэкенда — почти всегда временная. Повторите с
экспоненциальной задержкой (например, 1 с, 2 с, 4 с, 8 с, 16 с) до
максимум 5 попыток. Если после этого 502 сохраняется — поднимайте алерт.
Ошибки авторизации — не повторяйте¶
401 обычно означает:
- Системные часы клиента ушли больше чем на 30 секунд от серверных → синхронизируйте через NTP.
- Подпись посчитана неверно — пересмотрите алгоритм. См. Аутентификацию.
- Ключ отозван — попросите администратора создать новый.
- IP запроса вне списка разрешённых.
403 означает «у ключа нет нужного скоупа». Запросите у администратора
ключ с подходящим набором скоупов.
Ошибки валидации — исправляйте ввод¶
400 всегда возвращает машиночитаемые errors в ответе — используйте их
для обратной связи пользователю или для починки запроса.
Конфликты — обрабатывайте идемпотентно¶
409 обычно значит, что вы попытались создать что-то уже существующее
(дубликат email-а, кошелька по тому же активу и т. д.). Идемпотентный код
обращается за существующим ресурсом вместо повторного создания.
Ошибки доменных правил¶
Некоторые ответы 400 кодируют нарушения бизнес-логики, а не ошибки
валидации, например:
Другие примеры:
"Source and destination wallets must be for the same asset.""Insufficient balance."
Такие ошибки не исчезнут после повтора — поправьте запрос или исходное состояние и отправьте заново.
Пример обработки ошибок (Node.js)¶
async function callApi(method, path, body) {
const headers = signRequest(method, path, body); // см. Аутентификацию
headers["Content-Type"] = "application/json";
const response = await fetch(`${BASE_URL}${path}`, {
method,
headers,
body: body || undefined,
});
if (response.ok) return response.json();
const error = await response.json();
switch (response.status) {
case 400: // некорректный ввод — покажите error.errors пользователю
throw new ValidationError(error);
case 401: // плохой ключ / подпись / расхождение часов — НЕ повторять
case 403: // нет нужного скоупа / IP не разрешён — НЕ повторять
throw new AuthError(error);
case 404:
throw new NotFoundError(error);
case 409:
throw new ConflictError(error);
case 502: // транзиентный сбой кастоди-бэкенда — повтор с задержкой
await sleep(2000);
return callApi(method, path, body);
default:
throw new Error(error.message);
}
}