Ошибки и лимиты
Обработка ошибок и rate limiting
Ошибки и лимиты
Полное руководство по обработке ошибок и работе с rate limiting.
Коды ошибок
400 Bad Request
Неверные параметры запроса.
{
"ok": false,
"error": {
"code": "INVALID_PARAMS",
"message": "Параметр 'q' обязателен и должен содержать минимум 2 символа",
"details": {
"field": "q",
"value": "a",
"constraint": "minLength:2"
}
}
}Причины:
- Отсутствует обязательный параметр
- Неверный формат параметра
- Значение вне допустимого диапазона
Решение: Проверьте документацию endpoint'а и исправьте параметры.
401 Unauthorized
Отсутствует или неверный API ключ.
{
"ok": false,
"error": {
"code": "UNAUTHORIZED",
"message": "API ключ отсутствует или неверен"
}
}Причины:
- Заголовок
Authorizationне передан - Неверный формат ключа
- Ключ не существует
Решение: Проверьте наличие и правильность API ключа.
403 Forbidden
API ключ недействителен или приостановлен.
{
"ok": false,
"error": {
"code": "KEY_EXPIRED",
"message": "Срок действия API ключа истёк",
"details": {
"expiresAt": "2026-04-01T00:00:00.000Z"
}
}
}Причины:
- Ключ истёк (
KEY_EXPIRED) - Ключ отозван (
KEY_REVOKED) - Ключ приостановлен (
KEY_SUSPENDED) - Недостаточно прав (
INSUFFICIENT_PERMISSIONS)
Решение: Создайте новый ключ в личном кабинете.
404 Not Found
Ресурс не найден.
{
"ok": false,
"error": {
"code": "GAME_NOT_FOUND",
"message": "Игра с указанным ID не найдена",
"details": {
"gameId": "INVALID123"
}
}
}Причины:
- Неверный ID игры
- Игра удалена из каталога
- Неверный endpoint
Решение: Проверьте правильность ID или используйте поиск.
429 Too Many Requests
Превышен лимит запросов.
{
"ok": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Превышен лимит запросов в минуту",
"details": {
"limit": 30,
"resetAt": "2026-05-05T14:00:00.000Z",
"retryAfter": 45
}
}
}Причины:
- Превышен лимит в минуту (30 запросов)
- Превышен лимит в сутки (43200 запросов)
Решение: Подождите до времени resetAt или используйте retryAfter (секунды).
500 Internal Server Error
Внутренняя ошибка сервера.
{
"ok": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Внутренняя ошибка сервера",
"requestId": "req_abc123xyz"
}
}Причины:
- Непредвиденная ошибка на сервере
- Проблема с базой данных
Решение: Повторите запрос позже. Если ошибка повторяется, сообщите requestId в поддержку.
503 Service Unavailable
Внешний сервис недоступен.
{
"ok": false,
"error": {
"code": "UPSTREAM_ERROR",
"message": "Сервис PlayStation Store временно недоступен",
"details": {
"service": "psn",
"retryAfter": 60
}
}
}Причины:
- PSN Store недоступен
- Battle.net недоступен
- Проблемы с внешним API
Решение: Подождите retryAfter секунд и повторите запрос.
Rate Limiting
Лимиты
30 запросов в минуту и 43200 запросов в сутки на один API ключ.
| Период | Лимит | Сброс |
|---|---|---|
| Минута | 30 запросов | Каждую минуту |
| Сутки | 43200 запросов | В 00:00 UTC |
Заголовки ответа
Каждый ответ содержит заголовки с информацией о лимитах:
X-RateLimit-Limit-Minute: 30
X-RateLimit-Remaining-Minute: 27
X-RateLimit-Reset-Minute: 2026-05-05T14:00:00.000Z
X-RateLimit-Limit-Day: 43200
X-RateLimit-Remaining-Day: 43076
X-RateLimit-Reset-Day: 2026-05-06T00:00:00.000ZПроверка лимитов
Через заголовки
const response = await fetch('https://gapi.qb2.ru/api/v1/catalog/search?q=test', {
headers: { 'Authorization': 'Bearer gapi_...' }
});
const remaining = response.headers.get('X-RateLimit-Remaining-Minute');
const resetAt = response.headers.get('X-RateLimit-Reset-Minute');
console.log(`Осталось запросов: ${remaining}`);
console.log(`Сброс в: ${resetAt}`);Через endpoint
curl -H "Authorization: Bearer gapi_..." \
"https://gapi.qb2.ru/api/v1/account/usage"Стратегии обработки
1. Exponential Backoff
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}2. Rate Limiter
class RateLimiter {
constructor(maxRequests, perSeconds) {
this.maxRequests = maxRequests;
this.perSeconds = perSeconds;
this.requests = [];
}
async acquire() {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.perSeconds * 1000);
if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.perSeconds * 1000 - (now - oldestRequest);
await new Promise(resolve => setTimeout(resolve, waitTime));
return this.acquire();
}
this.requests.push(now);
}
}
const limiter = new RateLimiter(30, 60); // 30 запросов в 60 секунд
async function makeRequest(url, options) {
await limiter.acquire();
return fetch(url, options);
}3. Queue System
class RequestQueue {
constructor(rateLimit) {
this.queue = [];
this.processing = false;
this.rateLimit = rateLimit;
}
async add(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
setTimeout(() => {
this.processing = false;
this.process();
}, 1000 / this.rateLimit);
}
}
const queue = new RequestQueue(30 / 60); // 30 запросов в минуту
// Использование
queue.add(() => fetch('https://gapi.qb2.ru/api/v1/catalog/search?q=test', {
headers: { 'Authorization': 'Bearer gapi_...' }
}));Рекомендации
Мониторинг лимитов
- Всегда проверяйте заголовки
X-RateLimit-*после каждого запроса - Используйте
/account/usageдля получения детальной статистики - Логируйте превышения лимитов для анализа паттернов использования
Оптимизация запросов
- Кэшируйте результаты — особенно для
/catalog/games/:idи/catalog/exchange-rates - Используйте пагинацию — не запрашивайте больше данных, чем нужно
- Батчинг — группируйте запросы вместо множества мелких
- Фильтрация — используйте параметр
platformдля ускорения поиска
Обработка ошибок
async function safeApiCall(url, options) {
try {
const response = await fetch(url, options);
const data = await response.json();
if (!data.ok) {
switch (data.error.code) {
case 'RATE_LIMIT_EXCEEDED':
const retryAfter = data.error.details.retryAfter;
console.log(`Rate limit exceeded. Retry after ${retryAfter}s`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return safeApiCall(url, options);
case 'KEY_EXPIRED':
console.error('API key expired. Please create a new one.');
throw new Error('API key expired');
case 'UPSTREAM_ERROR':
console.warn('External service unavailable. Retrying...');
await new Promise(resolve => setTimeout(resolve, 5000));
return safeApiCall(url, options);
default:
throw new Error(data.error.message);
}
}
return data;
} catch (error) {
console.error('API call failed:', error);
throw error;
}
}При rate-limit ориентируйтесь на remaining и resetAt в JSON и headers. При 401/403 проверьте статус ключа и срок действия.