Руководство по обработке ошибок: Saga API¶
Аудитория: разработчики, backend-инженеры, frontend-разработчики Последнее обновление: 2025-11-17 Краткое содержание: Полный справочник по обработке ошибок Saga API — структура error responses, HTTP status codes, comprehensive error code reference, troubleshooting guides для каждой категории ошибок.
Структура ответа с ошибкой¶
Стандартный формат ошибки¶
Все API ошибки возвращаются в унифицированном формате:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {
"field": "problematic_field",
"reason": "detailed_explanation",
"suggestion": "how_to_fix"
}
},
"timestamp": "2025-10-03T12:00:01Z"
}
Описание полей:
success: false для всех error responseserror.code: Машиночитаемый код ошибки (uppercase, snake_case)error.message: Понятное пользователю описание проблемыerror.details: Дополнительная контекстная информация (опционально)timestamp: ISO 8601 timestamp когда произошла ошибка
Примеры ответов с ошибками¶
Ошибка валидации:
{
"success": false,
"error": {
"code": "INVALID_AMOUNT",
"message": "Investment amount must be at least 10 USDC",
"details": {
"field": "amount",
"provided": "5.00",
"minimum": "10.00",
"suggestion": "Increase investment amount to at least 10 USDC"
}
},
"timestamp": "2025-10-03T12:34:56Z"
}
Ошибка аутентификации:
{
"success": false,
"error": {
"code": "TOKEN_EXPIRED",
"message": "Authentication token has expired",
"details": {
"expiredAt": "2025-10-02T12:34:56Z",
"suggestion": "Re-authenticate to obtain a new token"
}
},
"timestamp": "2025-10-03T12:34:56Z"
}
Ошибка бизнес-логики:
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient USDC balance for investment",
"details": {
"requested": "1000.00 USDC",
"available": "750.50 USDC",
"shortfall": "249.50 USDC",
"suggestion": "Deposit more USDC or reduce investment amount"
}
},
"timestamp": "2025-10-03T12:34:56Z"
}
HTTP Status Codes¶
Использование кодов статуса¶
Saga API использует стандартные HTTP status codes:
| Код статуса | Категория | Использование |
|---|---|---|
| 200 OK | Success | Успешные GET, PUT, PATCH запросы |
| 201 Created | Success | Успешный POST создающий новый ресурс |
| 204 No Content | Success | Успешный DELETE (без тела ответа) |
| 400 Bad Request | Client Error | Ошибки валидации, некорректный ввод |
| 401 Unauthorized | Client Error | Отсутствует или некорректная аутентификация |
| 403 Forbidden | Client Error | Валидная auth, но недостаточно прав |
| 404 Not Found | Client Error | Ресурс не существует |
| 409 Conflict | Client Error | Конфликт ресурса (дубликат, заблокирован) |
| 422 Unprocessable Entity | Client Error | Ошибки семантической валидации |
| 429 Too Many Requests | Client Error | Превышен rate limit |
| 500 Internal Server Error | Server Error | Неожиданная ошибка на стороне сервера |
| 503 Service Unavailable | Server Error | Временная недоступность, обслуживание |
Дерево решений для кодов статуса¶
Запрос успешен?
├─ ДА → 2xx коды статуса
│ ├─ Создан новый ресурс? → 201 Created
│ ├─ Удалён ресурс? → 204 No Content
│ └─ Другой успех → 200 OK
│
└─ НЕТ → Коды ошибок
├─ Ошибка клиента?
│ ├─ Некорректный ввод? → 400 Bad Request
│ ├─ Auth отсутствует? → 401 Unauthorized
│ ├─ Недостаточно прав? → 403 Forbidden
│ ├─ Ресурс не найден? → 404 Not Found
│ ├─ Конфликт ресурса? → 409 Conflict
│ ├─ Ошибка бизнес-логики? → 422 Unprocessable Entity
│ └─ Превышен rate limit? → 429 Too Many Requests
│
└─ Ошибка сервера?
├─ Временная проблема? → 503 Service Unavailable
└─ Неожиданная ошибка → 500 Internal Server Error
Справочник кодов ошибок¶
Ошибки аутентификации (401/403)¶
TOKEN_EXPIRED - HTTP Status: 401 Unauthorized - Message: "Authentication token has expired" - Причина: JWT токен старше 24 часов - Решение: Повторная аутентификация для получения нового токена - Предотвращение: Реализовать обновление токена перед истечением
TOKEN_INVALID - HTTP Status: 401 Unauthorized - Message: "Invalid authentication token" - Причина: Неправильный формат JWT, некорректная подпись, или изменённый токен - Решение: Повторная аутентификация с валидными credentials - Предотвращение: Никогда не изменять JWT токены на стороне клиента
TOKEN_MISSING
- HTTP Status: 401 Unauthorized
- Message: "Authentication token required"
- Причина: Authorization header отсутствует
- Решение: Добавить Authorization: Bearer <token> header
- Предотвращение: Проверять auth state перед API вызовами
SIGNATURE_INVALID - HTTP Status: 401 Unauthorized - Message: "Wallet signature verification failed" - Причина: Подписанное сообщение не соответствует адресу кошелька - Решение: Убедиться что подключён правильный кошелёк, переподписать сообщение - Предотвращение: Проверять адрес кошелька перед подписью
NONCE_EXPIRED - HTTP Status: 401 Unauthorized - Message: "Authentication nonce has expired" - Причина: Challenge nonce старше 5 минут - Решение: Запросить новый challenge и подписать немедленно - Предотвращение: Завершить auth flow в течение 5 минут
PERMISSION_DENIED - HTTP Status: 403 Forbidden - Message: "Insufficient permissions for this operation" - Причина: Пользователю не хватает необходимой роли или разрешения - Решение: Связаться с админом для предоставления прав - Предотвращение: Проверять права пользователя перед попыткой действия
ADMIN_REQUIRED - HTTP Status: 403 Forbidden - Message: "Admin privileges required" - Причина: Endpoint требует admin токен, предоставлен user токен - Решение: Использовать admin authentication flow - Предотвращение: Использовать правильный auth flow для admin endpoints
Ошибки валидации (400/422)¶
INVALID_AMOUNT - HTTP Status: 422 Unprocessable Entity - Message: "Invalid investment/withdrawal amount" - Причина: Сумма ниже минимума, выше максимума, или отрицательная - Решение: Скорректировать сумму в разрешённом диапазоне - Детали: - Минимальная инвестиция: 10 USDC - Максимальная инвестиция: 1,000,000 USDC - Должна быть положительным числом
INVALID_EMAIL - HTTP Status: 400 Bad Request - Message: "Invalid email address format" - Причина: Email не соответствует RFC 5322 формату - Решение: Предоставить валидный email (user@domain.com) - Предотвращение: Валидировать email на клиенте перед отправкой
INVALID_WALLET_ADDRESS - HTTP Status: 400 Bad Request - Message: "Invalid Ethereum wallet address" - Причина: Адрес не 42-символьная hex строка начинающаяся с 0x - Решение: Предоставить валидный Ethereum адрес - Предотвращение: Валидировать формат адреса (ethers.utils.isAddress)
INVALID_STRATEGY
- HTTP Status: 422 Unprocessable Entity
- Message: "Invalid investment strategy"
- Причина: Strategy ID не существует или deprecated
- Решение: Использовать валидную стратегию: "conservative-5", "balanced-10", "aggressive-20"
- Предотвращение: Получить доступные стратегии от /api/user/strategies
MISSING_REQUIRED_FIELD - HTTP Status: 400 Bad Request - Message: "Required field missing: {field_name}" - Причина: Обязательное поле отсутствует в request body - Решение: Включить все обязательные поля - Предотвращение: Валидировать request schema на клиенте
INVALID_DATE_RANGE - HTTP Status: 400 Bad Request - Message: "Invalid date range" - Причина: Дата начала после даты окончания, или будущие даты - Решение: Предоставить валидный диапазон дат (start ≤ end, обе ≤ сегодня) - Предотвращение: Валидировать даты перед отправкой
Ошибки бизнес-логики (422)¶
INSUFFICIENT_BALANCE
- HTTP Status: 422 Unprocessable Entity
- Message: "Insufficient USDC balance for operation"
- Причина: Баланс пользователя < запрошенной суммы
- Решение: Пополнить USDC или уменьшить сумму
- Детали: Включает available, requested, shortfall суммы
INVESTMENT_LOCKED
- HTTP Status: 422 Unprocessable Entity
- Message: "Investment locked in cooldown period"
- Причина: Недавняя смена стратегии, нужно дождаться cooldown
- Решение: Подождать до истечения cooldown (обычно 24 часа)
- Детали: Включает lockedUntil timestamp
STRATEGY_NOT_AVAILABLE - HTTP Status: 422 Unprocessable Entity - Message: "Selected strategy currently unavailable" - Причина: Стратегия приостановлена из-за рыночных условий или обслуживания - Решение: Выбрать альтернативную стратегию - Предотвращение: Проверить статус стратегии перед инвестицией
WITHDRAWAL_LIMIT_EXCEEDED
- HTTP Status: 422 Unprocessable Entity
- Message: "Daily withdrawal limit exceeded"
- Причина: Пользователь достиг дневного лимита на вывод
- Решение: Подождать до следующего дня или запросить увеличение лимита
- Детали: Включает dailyLimit, usedToday, available суммы
DUPLICATE_INVESTMENT - HTTP Status: 409 Conflict - Message: "Investment already exists for this strategy" - Причина: Пользователь уже имеет активную инвестицию в этой стратегии - Решение: Использовать существующую инвестицию или создать новую стратегию - Предотвращение: Проверить активные инвестиции пользователя сначала
INVESTMENT_NOT_FOUND
- HTTP Status: 404 Not Found
- Message: "Investment not found"
- Причина: Investment ID не существует или принадлежит другому пользователю
- Решение: Проверить investment ID, убедиться в праве владения
- Предотвращение: Получить инвестиции пользователя от /api/user/investments
Ошибки Blockchain (422/503)¶
BLOCKCHAIN_ERROR - HTTP Status: 503 Service Unavailable - Message: "Blockchain operation failed" - Причина: RPC нода недоступна, перегрузка сети, или ошибка контракта - Решение: Повторить запрос после короткой задержки - Детали: Включает blockchain error message
WALLET_CONNECTION_ERROR
- HTTP Status: 422 Unprocessable Entity
- Message: "Failed to connect to wallet"
- Причина: MetaMask не установлен или заблокирован
- Решение: Установить MetaMask, разблокировать кошелёк, обновить страницу
- Предотвращение: Проверить window.ethereum перед подключением
GAS_ESTIMATION_FAILED - HTTP Status: 422 Unprocessable Entity - Message: "Unable to estimate gas for transaction" - Причина: Недостаточно gas, revert контракта, или некорректные параметры - Решение: Увеличить gas limit или проверить параметры транзакции - Детали: Включает estimated gas needed
TRANSACTION_FAILED - HTTP Status: 422 Unprocessable Entity - Message: "Blockchain transaction failed" - Причина: Транзакция reverted on-chain - Решение: Проверить transaction hash в explorer, повторить если временная проблема - Детали: Включает transaction hash, revert reason
NETWORK_MISMATCH - HTTP Status: 422 Unprocessable Entity - Message: "Wrong blockchain network" - Причина: Кошелёк подключён к неправильной сети (не Saga Testnet) - Решение: Переключить кошелёк на Saga Testnet (chainId: 1337) - Предотвращение: Автоматически предлагать смену сети при подключении
Ошибки Rate Limiting (429)¶
RATE_LIMIT_EXCEEDED
- HTTP Status: 429 Too Many Requests
- Message: "Too many requests, please try again later"
- Причина: Пользователь превысил часовой rate limit
- Решение: Подождать пока rate limit сбросится
- Детали:
- Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
- Retry-After header с секундами ожидания
Лимиты для конкретных Endpoint:
- User API: 1000 запросов/час на пользователя
- Admin API: 10,000 запросов/час на админа
- Public API: 100 запросов/час на IP
- Faucet: 10 запросов/день на адрес кошелька
Ошибки сервера (500/503)¶
INTERNAL_SERVER_ERROR - HTTP Status: 500 Internal Server Error - Message: "An unexpected error occurred" - Причина: Необработанное исключение сервера - Решение: Связаться с поддержкой если сохраняется - Детали: Error ID для ссылки в поддержке
DATABASE_ERROR - HTTP Status: 503 Service Unavailable - Message: "Database temporarily unavailable" - Причина: Пул соединений БД исчерпан или обслуживание - Решение: Повторить запрос после короткой задержки - Предотвращение: Реализовать exponential backoff
SERVICE_UNAVAILABLE
- HTTP Status: 503 Service Unavailable
- Message: "Service temporarily unavailable"
- Причина: Запланированное обслуживание или неожиданный downtime
- Решение: Проверить status page, повторить позже
- Детали: Включает maintenanceWindow если запланировано
Лучшие практики обработки ошибок¶
Для Frontend разработчиков¶
1. Всегда проверяйте поле success:
const response = await fetch('/api/user/balance');
const data = await response.json();
if (!data.success) {
// Обработка ошибки
console.error(`Error: ${data.error.code} - ${data.error.message}`);
showErrorToUser(data.error.message);
return;
}
// Продолжить с success case
const balance = data.data.balance;
2. Реализуйте переключение по типу ошибки:
function handleApiError(error: ApiError) {
switch (error.code) {
case 'TOKEN_EXPIRED':
// Перенаправить на логин
router.push('/login');
break;
case 'INSUFFICIENT_BALANCE':
// Показать модальное окно пополнения
showDepositModal(error.details.shortfall);
break;
case 'RATE_LIMIT_EXCEEDED':
// Показать обратный отсчёт повтора
const retryAfter = error.details.retryAfter;
showRetryCountdown(retryAfter);
break;
default:
// Общее сообщение об ошибке
showErrorToast(error.message);
}
}
3. Реализуйте логику повтора с экспоненциальной задержкой:
async function apiCallWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
const data = await response.json();
if (response.status === 503 || response.status === 429) {
// Сервер недоступен или rate limited - повторить
const delay = Math.min(1000 * Math.pow(2, i), 10000); // Макс 10s
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return data;
} catch (error) {
if (i === maxRetries - 1) throw error; // Последняя попытка провалилась
}
}
}
4. Отображайте понятные пользователю ошибки:
// ❌ ПЛОХО: Показать сырые коды ошибок
alert('Error: INVALID_AMOUNT');
// ✅ ХОРОШО: Показать чёткие, действенные сообщения
showNotification({
type: 'error',
title: 'Некорректная сумма инвестиции',
message: 'Пожалуйста инвестируйте минимум 10 USDC. Вы ввели 5.00 USDC.',
action: {
label: 'Скорректировать сумму',
onClick: () => focusAmountInput()
}
});
Для Backend разработчиков¶
1. Всегда возвращайте структурированные ошибки:
// ✅ ХОРОШО: Структурированный ответ с ошибкой
func (h *InvestmentHandler) CreateInvestment(c *gin.Context) {
if amount < minInvestment {
c.JSON(422, gin.H{
"success": false,
"error": gin.H{
"code": "INVALID_AMOUNT",
"message": fmt.Sprintf("Investment amount must be at least %.2f USDC", minInvestment),
"details": gin.H{
"field": "amount",
"provided": amount.String(),
"minimum": minInvestment.String(),
"suggestion": "Increase investment amount to at least 10 USDC",
},
},
"timestamp": time.Now().UTC().Format(time.RFC3339),
})
return
}
}
2. Включайте полезный контекст в ошибки:
// ❌ ПЛОХО: Расплывчатая ошибка
return errors.New("insufficient balance")
// ✅ ХОРОШО: Детальный контекст
return fmt.Errorf("insufficient balance: requested %s USDC, available %s USDC (shortfall: %s USDC)",
requested.String(), available.String(), shortfall.String())
3. Логируйте ошибки для отладки:
if err := db.CreateInvestment(investment); err != nil {
log.WithFields(log.Fields{
"userID": userID,
"amount": amount.String(),
"strategyID": strategyID,
"error": err.Error(),
}).Error("Failed to create investment")
c.JSON(500, gin.H{
"success": false,
"error": gin.H{
"code": "DATABASE_ERROR",
"message": "Failed to process investment",
"errorID": generateErrorID(), // Для ссылки в поддержке
},
})
return
}
Тестирование сценариев ошибок¶
Примеры cURL¶
Вызвать ошибку валидации:
curl -X POST https://app.saga.surf/api/user/investments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"strategyId": "balanced-10",
"amount": "5.00"
}'
# Ожидается: 422 INVALID_AMOUNT (ниже минимума 10 USDC)
Вызвать ошибку аутентификации:
Вызвать Rate Limit:
for i in {1..1100}; do
curl -X GET https://app.saga.surf/api/user/balance \
-H "Authorization: Bearer $TOKEN"
done
# Ожидается: 429 RATE_LIMIT_EXCEEDED (после 1000 запросов)
Вызвать ошибку бизнес-логики:
curl -X POST https://app.saga.surf/api/user/investments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"strategyId": "balanced-10",
"amount": "999999.00"
}'
# Ожидается: 422 INSUFFICIENT_BALANCE (если баланс пользователя < 999999)
Связанная документация¶
Справочники API:
- Authentication Guide - Устранение проблем auth ошибок
- User Endpoints - Коды ошибок User API
- Admin Endpoints - Коды ошибок Admin API
Поддержка:
- Troubleshooting Guide - Пошаговое разрешение ошибок
📋 Метаданные¶
Версия: 2.6.268
Обновлено: 2025-10-21
Статус: Published