| id | api-openrouter | |
|---|---|---|
| categoria | api | |
| entidade | OpenRouter | |
| agente | api-openrouter | |
| thinker_ids |
|
|
| revisado_em | 2026-11-04 | |
| versao_observada | docs 2026-11-04 (catálogo /api/v1/models com 455 modelos) |
Especificação mantida pelo agente api-openrouter conforme .claude/external-research.md. O agente principal se baseia neste arquivo para escrever código compatível. Índice: docs/externos.
O OpenRouter é um agregador: uma chave Authorization: Bearer dá acesso a ~455 modelos de dezenas de fornecedores, com roteamento entre provedores, fallback e cobrança unificada sem acréscimo sobre o preço do fornecedor. O Thinker o usa só por Chat Completions (/api/v1/chat/completions) e também consome o catálogo público (ADR 0003). O que mudou de relevante: o endpoint serve também Responses e Anthropic Messages nativos para 382 modelos, usage agora vem sempre (parâmetros usage.include e stream_options.include_usage foram descontinuados), X-OpenRouter-Title substitui X-Title (o antigo segue aceito), há expiration_date por modelo e variantes :batch. A única lacuna relevante é o thinking da Anthropic Messages não virar reasoning (G1, issue #432).
- Base:
https://openrouter.ai/api/v1. Autenticação:Authorization: Bearer <chave>(overview). Sem seleção de região no host; roteamento regional (EUA ou UE) é por requisição/conta ("In-Region Routing", anúncio de 2026-09-09; forma exata não confirmada) e o filtroregionexiste no catálogo. - Protocolos servidos:
POST /api/v1/chat/completions(OpenAI-like),POST /api/v1/responses(descrito como forma primária),POST /api/v1/messages(Anthropic Messages) ePOST /api/v1/completions. O campoendpointsde cada modelo do catálogo diz quais se aplicam: 382 de 455 modelos listam os quatro;/api/v1/batchesaparece em 73,/api/v1/imagesem 10 e/api/alpha/extractionsem 1 (lido em 2026-11-04). - Outros:
GET /api/v1/generation?id=(estatísticas e custo de uma requisição),GET /api/v1/key(saldo/limite da chave e uso diário dos modelos gratuitos). - Cabeçalhos de atribuição (opcionais, app-attribution):
HTTP-Referer(URL do app, identificador único; sem ele não há página do app nem ranking),X-OpenRouter-Title(nome;X-Titlesegue aceito),X-OpenRouter-Categories(até 2 categorias minúsculas separadas por vírgula, ≤30 caracteres; inválidas são descartadas) eX-OpenRouter-App-Visibility: hidden(fora dos rankings públicos). Em localhost o título é obrigatório para rastrear. - Roteamento de provedores: objeto
providerno corpo:order,only,ignore,allow_fallbacks(padrãotrue),require_parameters(padrãofalse),data_collection(allow|deny),zdr,enforce_distillable_text,quantizations,sort(price|throughput|latency, ou objetoby/partition),preferred_min_throughput,preferred_max_latency,max_price(provider-selection).models: [](lista de fallback entre modelos) existe, mas não está na página de parâmetros (não confirmado em detalhe). - Afinidade de sessão: o OpenRouter mantém o mesmo provedor para pedidos seguintes por 10 minutos de inatividade, a fim de manter o cache quente;
session_id(corpo) oux-session-id(cabeçalho) fixa a sessão desde o primeiro pedido. Não vale comprovider.ordermanual (prompt-caching).
GET /api/v1/models: parâmetros opcionais de filtro/ordem (output_modalitiespadrãotext,supported_parameters,region,q,sort,limitpadrão 500 e máximo 1000,offset). A resposta trazdata,total_countelinks.next. A doc lista bearer global; se o endpoint funciona anônimo não está dito, mas o Thinker o consulta sem chave e a resposta foi obtida assim em 2026-11-04.- Campos do modelo:
id,canonical_slug(pode diferir doid; ex.:tencent/hy4->tencent/hy4-20261104),name("Fornecedor: Modelo"),created,context_length,architecture(input_modalities,output_modalities),pricing,top_provider(context_length,max_completion_tokens),supported_parameters,default_parameters,endpoints,reasoning(mandatory,default_enabled,default_effort,supported_efforts; ausente em modelos sem raciocínio),expiration_date(ISO, modelo pode sair depois dela),knowledge_cutoff,alias_target,benchmarks. - Ids têm formato
fornecedor/modelo. Sufixos de catálogo aparecem como entradas próprias::free(14 hoje) e:batch(modelo cobrado com 50% de desconto pela Batch API, anúncio de 2026-09-22; não serve o endpoint síncrono).:thinkinge:extendedestão descontinuados. Sufixos de roteamento valem em qualquer id e não aparecem no catálogo::nitro(ordena por vazão),:floor(ordena por preço),:exacto(qualidade em tool calling);:onlineestá descontinuado em favor da ferramenta de servidoropenrouter:web_search(model-variants). - Aposentadorias anunciadas pelo próprio catálogo (
expiration_date, lido em 2026-11-04):minimax/minimax-m2,m2.1,m2.7,m2-herem 2026-11-05;inception/mercury-2,2.5,3em 2026-11-08;bytedance-seed/seed-1.6,seed-1.6-flash,seed-2.0-codeem 2026-11-11;z-ai/glm-4.7,glm-4.5vem 2026-12-31.
supported_parameterspor modelo (contagens em 455):max_tokens438,response_format393,tools390,tool_choice382,structured_outputs380,temperature343,reasoningeinclude_reasoning338,reasoning_effort217,verbosity103,parallel_tool_calls21,web_search_options18.- Entrada:
text+image->texte variantes comfile,audio,video; saída só texto na grande maioria, alguns comimageouaudio. - Reasoning (doc): objeto
reasoningcomeffort(max,xhigh,high,medium,low,minimal,none),max_tokens(orçamento; Anthropic mínimo 1024),enabled,exclude(só Chat Completions; tokens continuam cobrados). Resposta em Chat Completions:message.reasoning(texto) ereasoning_details(array dereasoning.summary,reasoning.encrypted,reasoning.text, comid,format,index); em streaming vêm emchoices[].delta. Num laço de ferramentas é preciso devolverreasoning/reasoning_detailsinalterados e na mesma ordem. Tokens de raciocínio contam contra o limite de saída. - Prompt caching (doc): OpenAI, DeepSeek, Grok, Moonshot, Groq (Kimi K2) e Gemini 2.5+ (implícito) cacheiam sem marcação; Anthropic e Qwen exigem
cache_controlpor bloco (até 4 pontos; Gemini usa só o último) ou, na Anthropic,cache_controlno topo do corpo para modo automático. Multiplicadores: Anthropic escrita 1,25x (TTL 5 min) ou 2x (1 h), leitura 0,1x; Gemini leitura 0,25x; DeepSeek leitura 0,1x; Grok e Moonshot leitura 0,25x; Groq 0,5x; OpenAI leitura 0,25x ou 0,5x conforme o modelo e escrita 1,25x a partir do GPT-5.6. Mínimos: OpenAI 1.024 tokens; Anthropic 512 a 4.096 conforme o modelo. - Resposta estruturada, ferramentas (
tools,tool_choice,parallel_tool_callspadrãotrue) everbosity(low..max; na Anthropic viraoutput_config.effort) seguem o formato OpenAI. Parâmetro não suportado pelo provedor escolhido: a doc de parâmetros diz que o campo ausente é omitido no upstream; o tratamento de parâmetros não suportados fora derequire_parametersnão está descrito (não confirmado). - Faixas:
temperature0 a 2,top_p0 a 1,frequency_penalty/presence_penalty-2 a 2,top_logprobs0 a 20.
- SSE com linhas de comentário como
: OPENROUTER PROCESSING(keep-alive); parsers manuais devem ignorar linhas iniciadas por:(streaming). - O
usagevem sempre, sem parâmetro.usage: {include: true}estream_options: {include_usage: true}estão descontinuados e não têm efeito (usage-accounting). Em streaming o último chunk antes de[DONE]carrega ousagee, diferente da especificação OpenAI, tem umachoicecomdeltavazio repetindo ofinish_reason; tratar como contabilidade, não como segundo fim de stream. - Campos de
usage:prompt_tokens,completion_tokens,total_tokens,cost(valor cobrado da conta, em USD),is_byok,cost_details.upstream_inference_cost,prompt_tokens_details.cached_tokens(leitura de cache),prompt_tokens_details.cache_write_tokens(só em modelos com escrita de cache cobrada),completion_tokens_details.reasoning_tokens. Contagem usa o tokenizador nativo do modelo. A resposta pode trazercache_discount. - Erro no meio do stream: o status continua
200; chega um chunk comerrorno topo efinish_reason: "error", e o stream termina. Um200pode conter só o erro (inclusive quando todos os provedores falham antes da primeira saída). Failover entre provedores só ocorre se nenhuma saída chegou ao cliente. - Cancelar o stream interrompe processamento e cobrança apenas em provedores que suportam isso; nos demais, o modelo roda até o fim e a resposta completa é cobrada. Requisições não-stream sempre são cobradas inteiras.
- Preço em USD por token, como string, em
pricing:promptecompletionsempre; opcionaisinput_cache_read(312 modelos),input_cache_write(99),input_cache_write_1h(34),internal_reasoning,web_search,image,audio,image_output,audio_output,input_audio_cache,request,discounteoverrides(84 modelos; preços condicionais como contexto longo ou janela de horário).:freetem preço zero. - Sem acréscimo sobre o preço do fornecedor na inferência. Compra de créditos tem taxa (percentual não confirmado, a página do FAQ não renderizou o valor). BYOK: franquia de US$ 25.000/mês sem taxa, depois 5% do que custaria no OpenRouter. Log de prompts/respostas é opt-in e dá 1% de desconto.
- Lote: metade do preço padrão (anúncio de 2026-09-22).
- Corpo de erro:
{ "error": { "code": <número>, "message": "...", "metadata": {...} } }; o status HTTP coincide comerror.codeem erros de requisição (errors).
| Status | Significado | Ação no gateway |
|---|---|---|
| 400 | parâmetros inválidos/ausentes ou CORS | erro do cliente, não repetir |
| 401 | chave inválida/desativada ou sessão OAuth expirada | chave ruim |
| 402 | créditos insuficientes na conta ou na chave (saldo negativo derruba até :free) |
cota/saldo, não repetir na mesma chave |
| 403 | permissão, bloqueio de guardrail ou moderação (metadata.reasons, flagged_input) |
não repetir |
| 408 | timeout | repetível |
| 429 | limite (error_type: "rate_limit_exceeded" traz X-RateLimit-*; 429 de provedor traz provider_code) |
repetir com backoff e Retry-After |
| 502 | modelo fora do ar ou resposta inválida | repetível/fallback |
| 503 | nenhum provedor atende às restrições de roteamento | ajustar provider |
- Erros 500 têm a mensagem genérica;
metadata.provider_name,provider_codeerawaparecem em recusas por política de conteúdo. - Modelos
:free: 20 req/min; 50 req/dia com menos de 10 créditos comprados, 1.000/dia com 10 ou mais (limite alto concedido a partir de 9 créditos). Modelos pagos não têm teto de plataforma; o Cloudflare bloqueia abuso extremo. - Limite de chave e uso diário gratuito:
GET /api/v1/key(limit_remaining,free_model_daily_requests,is_free_tier).
- Roteamento de provedores por rota (desde 2026-11-06): os campos
provider.*são parâmetros por rota (routing-core/openrouter-provider-routing.ts,clientOwnsRoot: o objetoproviderdo cliente vence por inteiro). Erro embutido em 200 e erro SSE antes do primeiro conteúdo viram status real (upstream-error-classifier.ts,stream-warmup.tspeekStreamStart). Oproviderda resposta, oid(gen-...) e omodelvão paraagent_messages.served_provider,upstream_generation_ideserved_model(served-provider.ts).openrouter/freepublica em/v1/modelsa menor janela dos:free.
Estado conferido no código em 2026-11-04 (branch docs/externos-onda3, commit base 2f97f17).
- Inferência. Base
https://openrouter.ai, caminho fixo/api/v1/chat/completions, formatoopenai,Authorization: Bearer,Content-Type,HTTP-Referer: https://github.com/patrickbrandao/thinkereX-Title: Thinker(packages/backend/src/routing/proxy/provider-endpoints.ts:573-584). Não usa/responsesnem/messages, e não enviaX-OpenRouter-Categories,session_id,provideroureasoning. - Passthrough.
openrouterestá emPASSTHROUGH_PROVIDERS(provider-client-converters.ts:113): campos de dialeto OpenAI/OpenRouter não são removidos;reasoningereasoning_detailsdas mensagens são preservados só para OpenRouter (provider-client-converters.ts:373-380). Nemotron via OpenRouter temthinkingremovido (:437-464). O id do modelo mantém o prefixofornecedor/(provider-client.ts:332-336). - Usage. Pede
stream_options.include_usage(provider-endpoints.ts:84, aplicado emprovider-client.ts:920-925), hoje desnecessário mas inofensivo.parseUsageObjectlêprompt_tokens_details.cached_tokensecache_write_tokense o custo reportadousage.costoucost_details.upstream_inference_cost(stream-writer.ts:100-190); o custo reportado vence qualquer estimativa (common/utils/cost-calculator.ts:145-162). Linhas de comentário SSE:são ignoradas (stream-writer.ts:228). Erro no meio do stream (errorno topo) viraupstream_error(routing/proxy/stream-protocol.ts:68-120). - Cache. Para
anthropic/*injetacache_controlno último system/developer e na última tool, mais cache automático da Anthropic; paragoogle/*eqwen/*injeta no último system/developer/user (provider-client.ts:323-329,951-958,routing/proxy/cache-injection.ts:37-67). - Listagem.
GET https://openrouter.ai/api/v1/modelssem chave (model-discovery/provider-model-fetcher.service.ts:1026-1030).parseOpenRouter(:627-665) descarta ids terminados em:batche modelos com saída que não seja só texto, e guarda id, nome,context_length, modalidades e preçosprompt/completion; não lêsupported_parameters,reasoning,expiration_date,top_provider.max_completion_tokensnem preços de cache;capabilityReasoningecapabilityCodeficamfalsee vêm do models.dev (model-discovery/model-capabilities.ts:148-175). - Catálogo e preços (ADR 0003).
database/pricing-sync.service.ts:36busca o mesmo endpoint, filtra modelos que aceitam e produzem texto e guarda sóinput/output/context_length/nome (:105-135,:180-195). Serve de fallback de preço e de lista (model-discovery/model-fallback.ts:234-270,model-discovery.service.ts:312-326). Mapa de prefixos de fornecedor empackages/shared/src/providers.ts(openRouterPrefixes) e inferência de provedor por prefixo empackages/shared/src/provider-inference.ts:8,31. Desabilitado porTHINKER_CATALOG_SYNC=false. - Reasoning.
reasoning-format.ts:36,103-108trataopenroutercomo host que tolerareasoning_content. Othinkingda Anthropic Messages só é traduzido paraopenai(provider-client.ts:928-935); em OpenRouter segue sem tradução (G1). - Snippet de setup. Chave obtida em
https://openrouter.ai/keys(packages/frontend/src/services/provider-api-key-urls.ts:24); texto do card "Auto-route to 300+ models" (frontend/src/i18n/locales/en/providerSetup.ts:266). O catálogo atual tem 455 modelos, então "300+" continua correto.
Pontos no código:
packages/shared/src/providers.tspackages/shared/src/provider-inference.tspackages/backend/src/routing/proxy/provider-endpoints.tspackages/backend/src/routing/proxy/provider-client.tspackages/backend/src/routing/proxy/provider-client-converters.tspackages/backend/src/routing/proxy/cache-injection.tspackages/backend/src/routing/proxy/stream-writer.tspackages/backend/src/routing/proxy/stream-protocol.tspackages/backend/src/routing/proxy/reasoning-format.tspackages/backend/src/routing/proxy/anthropic-messages-adapter.tspackages/backend/src/model-discovery/provider-model-fetcher.service.tspackages/backend/src/model-discovery/model-capabilities.tspackages/backend/src/model-discovery/model-fallback.tspackages/backend/src/model-discovery/known-model-prices.tspackages/backend/src/database/pricing-sync.service.tspackages/backend/src/database/models-dev-sync.service.tspackages/backend/src/model-prices/model-pricing-cache.service.tspackages/backend/src/common/utils/cost-calculator.tspackages/frontend/src/services/provider-api-key-urls.ts
| ID | Severidade | Lacuna | Dono | Issue | Status |
|---|---|---|---|---|---|
| G1 | degradação | thinking da Anthropic Messages não é traduzido para reasoning quando o destino é openrouter (a tradução só existe para openai, provider-client.ts:928-935); modelos Claude via OpenRouter provavelmente rodam sem raciocínio e thinking segue como campo desconhecido (efeito não confirmado sem chave real). Sugestão: mapear para reasoning.max_tokens/effort: none ou usar /api/v1/messages para anthropic/*. |
providers | #432 | resolvida (normalização de reasoning na entrada e na saída) |
| G2 | oportunidade | parseOpenRouter ignora supported_parameters, reasoning, top_provider.max_completion_tokens e expiration_date; capacidades de raciocínio/ferramentas dependem do models.dev e modelos prestes a sair (ex.: MiniMax M2.x em 2026-11-05) continuam listados. Sugestão: preencher capacidades e ocultar/avisar modelos vencidos a partir do catálogo. |
backend-dev | — | aberta |
| G3 | oportunidade | Preços de cache (input_cache_read, input_cache_write, input_cache_write_1h) e overrides de contexto longo não entram no cache de preços; o custo real hoje vem de usage.cost, então só afeta estimativa e exibição de preço. Mesma causa do Kilo (#408). |
backend-dev | — | aberta |
| G4 | oportunidade | Enviar session_id/x-session-id (o Thinker já tem providerCacheKey) para fixar o provedor e manter caches quentes em Anthropic, Gemini e DeepSeek; hoje a afinidade depende só do heurístico de 10 minutos. |
providers | — | aberta |
| G5 | oportunidade | Atribuição: trocar X-Title por X-OpenRouter-Title (o antigo segue aceito) e avaliar X-OpenRouter-Categories e X-OpenRouter-App-Visibility: hidden. O HTTP-Referer atual expõe o app em rankings públicos; é decisão do mantenedor. |
providers | — | aberta |
| G6 | oportunidade | Endpoint nativo /api/v1/messages e /api/v1/responses disponíveis em 382 modelos; usar para anthropic/* preservaria thinking, cache_control e blocos nativos sem tradução (relacionada a G1). |
providers | — | aberta |
- 2026-11-04: primeira revisão completa. Contrato escrito a partir da doc e do catálogo vivo (455 modelos). Registradas G1 a G6; aberta a issue #432 para G1. Pendente de conferência com chave real: efeito do campo
thinkingno Chat Completions, tratamento de parâmetros não suportados e percentual da taxa de compra de créditos. - 2026-11-03: esqueleto criado, sem pesquisa.