GAPI Documentation

Ошибки и лимиты

Обработка ошибок и 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_...' }
}));

Рекомендации

Мониторинг лимитов

  1. Всегда проверяйте заголовки X-RateLimit-* после каждого запроса
  2. Используйте /account/usage для получения детальной статистики
  3. Логируйте превышения лимитов для анализа паттернов использования

Оптимизация запросов

  1. Кэшируйте результаты — особенно для /catalog/games/:id и /catalog/exchange-rates
  2. Используйте пагинацию — не запрашивайте больше данных, чем нужно
  3. Батчинг — группируйте запросы вместо множества мелких
  4. Фильтрация — используйте параметр 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 проверьте статус ключа и срок действия.

On this page