Бонусы за приглашения

Как работают бонусы за приглашения

Поделитесь ссылкой. Когда друг зарегистрируется по ней и пополнит баланс, вы получите указанный бонус за его последующие пополнения.

Лимиты OpenRouter и ошибка 429: как разобрать причину и восстановить запросы

Разбор ошибок 429 и 402 в OpenRouter: как отличить лимит бесплатного тарифа от ограничений платформы и апстрим-провайдера, где смотреть заголовки и метаданные, а также готовый скрипт повтора запросов на Python.

Содержание
Лимиты OpenRouter и ошибка 429: как разобрать причину и восстановить запросы

При активной отправке запросов к моделям через шлюз OpenRouter клиентские приложения периодически сталкиваются со статусом HTTP 429 Too Many Requests. Так как сервис объединяет десятки независимых поставщиков инференса, эта ошибка возникает на разных уровнях инфраструктуры. Прямолинейный немедленный повтор обращения часто приводит к блокировке клиента или расходу попыток впустую. Чтобы восстановить стабильную отправку данных, требуется определить конкретный уровень сбоя и выбрать соответствующее действие: выждать паузу, снизить параллелизм, сменить поставщика модели или скорректировать лимит расходов.

Уровни ограничений: бесплатный тариф, платформа и апстрим

Официальная документация OpenRouter Limits разграничивает частотные лимиты сервиса, пропускную способность конечных провайдеров и блокировку по балансу.

В базовой сетке на странице OpenRouter Pricing для бесплатного тарифа зафиксирована планка в 50 запросов в сутки на общедоступные бесплатные модели (с суффиксом :free). Точные лимиты запросов в минуту (RPM) и пороги уровней могут изменяться, поэтому актуальные числовые значения проверяются по живой таблице ограничений.

При анализе сбоя критично разделить уровни возникновения кода 429 и смежный статус 402:

  1. Частотные лимиты платформы OpenRouter. Возникают при слишком частых обращениях к самому маршрутизатору. К этой же категории ограничений платформы (а не к отдельному независимому источнику) относится суточная квота бесплатного пула: при превышении лимита в 50 запросов в день на бесплатные модели запрос отклоняется платформой до момента сброса счетчика. При наступлении платформенного ограничения частоты сервер возвращает заголовки X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset. Успешные ответы с кодом HTTP 200 эти служебные заголовки не содержат, поэтому ориентироваться на них в штатном режиме работы невозможно.
  2. Ограничения вышестоящего провайдера (upstream rate limit). Модель физически развернута на серверах конкретной компании (Anthropic, Meta, DeepSeek, Mistral или сторонних хостингов). Если инфраструктура этого партнера перегружена, OpenRouter передает статус 429 клиенту. В структуре ответа поле error.metadata.provider_code содержит исходный код ошибки вышестоящего провайдера, когда он доступен (например, 429), а не название или идентификатор поставщика.
  3. Финансовые ограничения (HTTP 402 Payment Required). Документация OpenRouter Limits отделяет исчерпание средств от частотных ограничений. Статус 402 указывает на недостаточный или отрицательный баланс учетной записи либо на исчерпание индивидуального лимита ключа (key cap), а не исключительно на строго нулевой остаток.

Текущие параметры ключа проверяются прямым запросом:

curl -s -X GET https://openrouter.ai/api/v1/key \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

В ответе возвращаются поля usage, limit_reset и limit_remaining. Значение limit_remaining: null показывает, что для данного API Key не установлен локальный потолок трат (key cap). Это значение не подтверждает наличие депозита на основном счете организации, а лишь снимает искусственное ограничение с конкретного токена.

Сводная таблица диагностики

Код ответа и признакиИсточник проверкиПричина сбояДействие клиента
HTTP 402 Payment RequiredЭндпоинт GET /api/v1/key или личный кабинетНедостаточный или отрицательный баланс организации либо исчерпан лимит ключа (limit_remaining: 0)Пополнить счет или увеличить квоту токена; программный повтор без изменений бессмыслен
HTTP 429 с заголовками X-RateLimit-*HTTP-заголовки ответа шлюзаПревышен лимит параллелизма или частоты самой платформы OpenRouterПрочитать заголовок Retry-After и снизить число параллельных потоков
HTTP 429 с кодом в provider_codeПоле error.metadata.provider_code в теле JSON (опционально)Перегрузка или отказ вышестоящего провайдера (исходный код ошибки апстрима)Переключение модели или провайдера может помочь, но не гарантирует восстановление; атрибуция конкретного поставщика проверяется в Activity > provider_responses
HTTP 429 на моделях :freeРаздел OpenRouter PricingИсчерпан дневной лимит платформы (50 запросов/день) или доступная мощностьПерейти на платную модель либо отложить выполнение задачи
HTTP 429 при сложном роутингеДашборд: Activity > запрос > View Raw MetadataПромежуточный отказ узлов в объекте provider_responsesАтрибутировать отказавшего провайдера и проверить цепочку обхода в руководстве BYOK/Routing

Разбор двух сценариев: лимит ключа против отказа провайдера

Поведение клиентского приложения при остановке запросов строится на проверке метаданных. Ниже представлены два гипотетических сценария (а не наблюдения за реальным аккаунтом).

Сценарий 1 (гипотетический): Исчерпание локального лимита токена

В этом гипотетическом сценарии фоновый процесс получает отказ со статусом HTTP 402. Положительный баланс учетной записи здесь является явным исходным допущением, проверенным отдельно через панель управления (сам ответ эндпоинта ключа состояние общего счета не подтверждает). Запрос к эндпоинту https://openrouter.ai/api/v1/key возвращает:

{
  "data": {
    "label": "worker-key",
    "usage": 25.04,
    "limit": 25.0,
    "is_free_tier": false,
    "limit_remaining": 0.0,
    "limit_reset": null
  }
}

Баланс учетной записи по условию положителен, но поле limit_remaining равно нулю. Ключ достиг установленного администратором порога в 25 долларов. Любые повторные попытки с этим ключом завершатся той же ошибкой 402. Процесс должен немедленно завершить работу и отправить уведомление оператору для корректировки лимита.

Сценарий 2 (гипотетический): Перегрузка вышестоящего провайдера

В качестве иллюстративного примера рассмотрим случай, когда запрос возвращает HTTP 429 из-за отказа на стороне апстрима. Сам факт получения 429 не дает оснований делать вывод о работоспособности шлюза или наличии положительного баланса. В теле ошибки может присутствовать блок метаданных:

{
  "error": {
    "message": "Provider returned rate limit error",
    "code": 429,
    "metadata": {
      "provider_code": 429
    }
  }
}

Поле error.metadata.provider_code является опциональным и содержит исходный код ошибки вышестоящего провайдера, когда он доступен (в данном примере — 429), а не имя или идентификатор провайдера. Наличие этого кода не позволяет определить, какой именно поставщик отклонил запрос.

Чтобы атрибутировать конкретного отказавшего поставщика, требуется открыть в консоли управления раздел: Activity > конкретный запрос > View Raw Metadata. В объекте provider_responses отображается список опрошенных хостов и их фактические статусы, как описано в руководстве по роутингу. Переключение на другого провайдера или смена модели в такой ситуации могут помочь, но не гарантируют немедленного восстановления.

Клиентский скрипт повтора запросов на Python 3

Для сценариев, когда статус 429 носит временный характер, задержка перед следующей попыткой вычисляется по заголовку Retry-After. Этот заголовок передается сервером в виде целого числа секунд либо даты в формате HTTP.

Приведенный скрипт использует только стандартную библиотеку Python 3, обрабатывает исключительно код 429, применяет экспоненциальное нарастание задержки со случайным смещением (jitter) при отсутствии серверной подсказки и останавливает выполнение, если сервер требует ожидания свыше 60 секунд.

import email.utils
import json
import os
import random
import sys
import time
import urllib.error
import urllib.request

API_KEY = os.environ.get("OPENROUTER_API_KEY")
MODEL_ID = os.environ.get("OPENROUTER_MODEL_ID", "openai/gpt-4o-mini")
MAX_ATTEMPTS = 3
MAX_ACCEPTABLE_WAIT = 60.0


def parse_retry_after(header_value: str | None) -> float | None:
    if not header_value:
        return None
    raw = header_value.strip()
    if raw.isdigit():
        return max(0.0, float(raw))
    try:
        parsed_date = email.utils.parsedate_to_datetime(raw)
        delay = parsed_date.timestamp() - time.time()
        return max(0.0, delay)
    except Exception:
        return None


def execute_completion(prompt_text: str) -> str | None:
    if not API_KEY:
        sys.stderr.write("Переменная окружения OPENROUTER_API_KEY не задана.\n")
        return None

    endpoint = "https://openrouter.ai/api/v1/chat/completions"
    payload = json.dumps(
        {
            "model": MODEL_ID,
            "messages": [{"role": "user", "content": prompt_text}],
        }
    ).encode("utf-8")

    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }

    for attempt in range(1, MAX_ATTEMPTS + 1):
        req = urllib.request.Request(endpoint, data=payload, headers=headers, method="POST")
        try:
            with urllib.request.urlopen(req, timeout=30) as response:
                status_code = response.getcode()
                body = response.read().decode("utf-8")
                if status_code == 200:
                    data = json.loads(body)
                    return data["choices"][0]["message"]["content"]
        except urllib.error.HTTPError as err:
            if err.code == 429:
                retry_header = err.headers.get("Retry-After")
                server_delay = parse_retry_after(retry_header)

                if server_delay is not None:
                    wait_seconds = server_delay
                else:
                    base_delay = 2.0 ** attempt
                    wait_seconds = base_delay + random.uniform(0.1, 1.0)

                if wait_seconds > MAX_ACCEPTABLE_WAIT:
                    sys.stderr.write(
                        f"Сервер запросил паузу {wait_seconds:.1f} с. "
                        "Ожидание превышает 60 секунд. Запрос отменен.\n"
                    )
                    return None

                if attempt == MAX_ATTEMPTS:
                    sys.stderr.write("Исчерпан лимит из 3 попыток на статус 429.\n")
                    return None

                sys.stderr.write(
                    f"Получен 429. Попытка {attempt} завершилась неудачей. "
                    f"Пауза {wait_seconds:.2f} с перед следующим запросом.\n"
                )
                time.sleep(wait_seconds)
                continue
            elif err.code == 402:
                sys.stderr.write("Ошибка 402: проверьте баланс счета и лимит ключа.\n")
                return None
            else:
                sys.stderr.write(f"HTTP-ошибка {err.code}: запрос отклонен без повтора.\n")
                return None
        except urllib.error.URLError as err:
            sys.stderr.write(f"Сетевой сбой: {err.reason}. Повтор отменен.\n")
            return None

    return None


if __name__ == "__main__":
    result = execute_completion("Назови три базовых принципа надежности сетевых API.")
    if result:
        print(result)

Повтор сетевого запроса и побочные эффекты

При проектировании сценариев с вызовом внешних функций (tool calling) в агентных процессах повтор HTTP-запроса требует осторожности. Если модель на предыдущем шаге уже инициировала вызов внешнего инструмента, изменившего состояние внешней системы (запись в базу данных, отправка платежа, создание тикета), повторный запуск всей цепочки приведет к дублированию операции. Ни в коем случае нельзя повторять вызовы, если связанные бизнес-инструменты уже были выполнены, либо при неоднозначном сетевом результате (например, при сетевом сбое или тайм-ауте, когда неизвестно, был ли запрос фактически принят и обработан).

В приведенном примере повтор выполняется исключительно для явно отклоненного моделью запроса со статусом 429. При этом генерация текста не заявляется как строго идемпотентная или бесплатная: повторные вызовы расходуют квоты и бюджет, а стохастическая природа генерации может давать иной результат. Если сбой произошел в момент выполнения внешней команды агентом, состояние системы синхронизируется через журнал действий до возобновления диалога с моделью.

Формирование отказоустойчивой маршрутизации

На странице тарифов для плана Free зафиксирован лимит платформы в 50 запросов в день на бесплатные модели. При этом указанное в документации отсутствие ограничений платформы относится к переходу на платную модель, а не к покупке кредитов — пополнение баланса не снимает автоматически лимит с бесплатных моделей. Актуальные условия и вариативность квот для своей учетной записи необходимо проверять по живой таблице ограничений. Кроме того, даже использование платных моделей не исключает перегрузки отдельных серверных кластеров провайдеров (хотя переключение провайдеров может помочь, оно не гарантирует восстановление).

Для надежной работы сервисов в продакшене команды комбинируют следующие подходы:

  • Указывают резервные модели в поле models запроса OpenRouter, позволяя маршрутизатору автоматически передавать вызов запасному исполнителю.
  • Ограничивают максимальное число одновременных запросов на стороне клиента через очереди задач.
  • Для критически важных участков инфраструктуры поддерживают независимый резервный маршрут через другие мультимодельные API с совместимым форматом запросов, чтобы переключать трафик при продолжительной недоступности основного шлюза.

Готовы оптимизировать LLM workflow?

Подключите единый API, управляйте ключами и контролируйте расходы на AI-модели в BetterToken.

Начать бесплатно