Руководство по обработке ошибок: Saga API¶
Краткое содержание: Полный справочник по обработке ошибок 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: "JWT signature verification failed"
- Причина: Supabase JWT не прошёл верификацию через JWKS
- Решение: Повторная аутентификация через Supabase
- Предотвращение: Проверять валидность Supabase session перед запросом
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 часа)
- Детали: Включает
lockedUntiltimestamp
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
AUTH_SESSION_ERROR
- HTTP Status: 422 Unprocessable Entity
- Message: "Failed to establish auth session"
- Причина: Supabase session истекла или недействительна
- Решение: Повторить вход через Google OAuth или email
- Предотвращение: Проверять
supabase.auth.getSession()перед операциями
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
DEPOSIT_NETWORK_ERROR
- HTTP Status: 422 Unprocessable Entity
- Причина: Депозит отправлен в неподдерживаемую сеть
- Решение: Отправлять USDC/USDT только в поддерживаемые сети (TRON TRC-20, Ethereum ERC-20)
- Предотвращение: Проверять выбранную сеть перед отправкой депозита
Ошибки 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
Ошибки сервера (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 - Пошаговое разрешение ошибок