POST /v1/routing/explain
Faça dry-run da seleção de roteamento para um body de requisição candidato. O gateway executa a cadeia completa de estratégias — incluindo o LLM firewall — e retorna os candidatos, pesos, o modelo que teria sido selecionado e a confiança do roteador. Nenhum provider é chamado. Nenhuma linha decision_trace é emitida. Nenhum orçamento de rate limit é gasto no caminho de chat.
Projetado para checks de CI (“este prompt roteia como esperamos?”), demos pré-venda e experimentos de tuning de constraints.
Endpoint
Seção intitulada “Endpoint”POST https://api.floopy.ai/v1/routing/explainAutenticação
Seção intitulada “Autenticação”Authorization: Bearer <your-floopy-api-key>Content-Type: application/json- Permissão:
write_permission. O endpoint aceita um payload JSON (daí write), mas é não-mutativo — sem chamada ao provider, sem linha de log, sem incremento de contador. - Apenas plano Pro (requer Feedback-Driven routing).
- Estamos liberando esse endpoint por organização enquanto validamos a qualidade. Fale com o suporte caso sua organização ainda não esteja habilitada.
Request body
Seção intitulada “Request body”Limite do body: 64 KiB. Payloads maiores retornam 400 body_too_large.
{ "request": { "model": "gpt-5.4", "messages": [ { "role": "user", "content": "Summarise this incident report in 3 bullets." } ], "temperature": 0.3 }, "headers": { "floopy-session-id": "sess_demo_1" }}| Field | Type | Required | Constraints |
|---|---|---|---|
request | object | Yes | Um body padrão de chat-completion compatível com OpenAI (mesmo formato de POST /v1/chat/completions). |
headers | object | No | Um subconjunto de headers para encaminhar ao contexto de roteamento. Chaves reservadas são descartadas no servidor (veja Sanitização de headers). |
Chaves desconhecidas no nível superior retornam 400 invalid_body (o schema da requisição é deny_unknown_fields).
Sanitização de headers
Seção intitulada “Sanitização de headers”Antes de o map de headers ser passado para o engine de dry-run, entradas cuja chave em minúsculas combinar com as seguintes são descartadas:
- match exato:
authorization,cookie,host - match por prefixo:
x-forwarded-,x-real-ip
Isso impede que o endpoint de explain seja abusado como ferramenta de header-relay ou fingerprinting.
Resposta (200)
Seção intitulada “Resposta (200)”{ "dry_run": true, "strategy_id": "feedback_driven", "phase": "auto", "weights": { "session": 0.5, "auto": 0.3, "manual": 0.1, "benchmark": 0.1 }, "candidates": [ { "provider": "openai", "model": "gpt-5.4-mini", "score": 0.81 }, { "provider": "anthropic", "model": "claude-sonnet-4", "score": 0.78 } ], "filtered": [ { "provider": "openai", "model": "gpt-5.4", "reason": "constraint_max_cost_increase", "score": 0.86 } ], "would_select": { "provider": "openai", "model": "gpt-5.4-mini" }, "reason": "dispatched", "confidence": 0.78, "confidence_reason": "ok", "exploration_rate_effective": 0.05, "used_shared_pool_prior": false, "evidence": { "samples": 412, "top2_score_gap": 0.07, "outcome_variance": 0.04, "recent_regressions": { "kind": "exact", "exact": 1 }, "last_regression_at": "2026-05-04T18:25:00Z" }, "explanation": { "text": "A Floopy rotearia esta requisicao para openai/gpt-5.4-mini com base em 412 amostras historicas e uma confianca moderada de 0,78. O proximo candidato pontuou dentro de 0,07 pontos e a variancia dos resultados tem se mantido estavel. Uma regressao foi registrada nos ultimos 7 dias.", "template_id": "feedback_driven_moderate_confidence" }}A resposta tambem carrega um cabecalho Content-Language ecoando a locale usada para renderizar explanation.text. Veja a feature de Explicacao de Decisao.
| Field | Type | Description |
|---|---|---|
dry_run | boolean | Sempre true. |
strategy_id | string | Estratégia executada (feedback_driven, smart_cost, etc.). |
phase | string | null | Fase do Feedback-Driven. |
weights | object | null | Pesos dos sinais (apenas Feedback-Driven). |
candidates | array | Modelos considerados e seus scores. |
filtered | array | Candidatos removidos. reason é um enum fechado (veja GET /v1/decisions/{request_id}). |
would_select | object | null | O par (provider, model) para o qual o gateway despacharia. null quando o firewall bloqueou a requisição. |
reason | string | dispatched, exhausted, ou no_enabled_targets. |
confidence | number | null | Veja a metodologia de Confidence. |
confidence_reason | string | null | Enum fechado: ok, cap_day0, cap_shared, no_router_invoked, insufficient_samples, single_candidate. |
exploration_rate_effective | number | Taxa efetiva de exploração. |
used_shared_pool_prior | boolean | Se priors cross-tenant influenciaram o score. |
evidence | object | null | Entradas que produziram o numero de confianca, expostas para auditoria (apenas Feedback-Driven e Smart-Cost). null quando nenhum router foi invocado. Veja Metodologia de Confianca. |
evidence.samples | integer | Contagem rolante de amostras. |
evidence.top2_score_gap | number | Gap de score entre vencedor e segundo colocado. |
evidence.outcome_variance | number | Variancia rolante de resultados sobre o vencedor. |
evidence.recent_regressions | tagged union | Contagem bucketizada de regressoes sobre o (provider, model) do vencedor nos ultimos 7 dias. kind: "exact" com campo exact para n < 10; kind: "at_least" com campo at_least para os limiares 10 e 50. |
evidence.last_regression_at | ISO8601 | null | Regressao mais recente na janela de 7 dias, arredondada para o multiplo de 5 minutos mais proximo. null quando nao ha. |
explanation | object | null | Explicacao legivel da decisao em dry-run. Veja Explicacao de Decisao. |
explanation.text | string | Paragrafo em prosa simples (≤ 600 caracteres). Nunca referencia o conteudo do prompt. |
explanation.template_id | string | Id de template (enum fechado). |
Cabecalhos de resposta
Seção intitulada “Cabecalhos de resposta”A resposta carrega:
Content-Language: enouContent-Language: pt— a locale usada para renderizarexplanation.text. Resolvida do cabecalhoAccept-Languageda requisicao, caindo paraen. Veja Explicacao de Decisao.
Comportamento do firewall
Seção intitulada “Comportamento do firewall”O LLM firewall executa no caminho de dry-run. Se ele bloquear, a resposta ainda é 200, com:
{ "filtered": [{ "provider": "...", "model": "...", "reason": "firewall_blocked", "score": 0.0 }], "would_select": null, "reason": "no_enabled_targets" }Isto é intencional — o dry-run reflete fielmente o que aconteceria em produção, incluindo o firewall.
| Status | error code | Quando |
|---|---|---|
400 | invalid_body | O body falhou na validação de schema ou não é JSON. |
400 | body_too_large | O body excede 64 KiB. |
403 | write_permission / plan_required | Permissão ou flag de plano ausente. |
404 | no_route | Nenhuma regra de roteamento se aplica à org e ao modelo. |
429 | rate_limited | Excedeu o orçamento por organização ou por key. Carrega Retry-After. |
5xx | internal | Falha upstream. |
Exemplo curl
Seção intitulada “Exemplo curl”curl -s -X POST \ -H "Authorization: Bearer $FLOOPY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "request": { "model": "gpt-5.4", "messages": [{"role":"user","content":"Summarise this incident report in 3 bullets."}], "temperature": 0.3 }, "headers": {"floopy-session-id":"sess_demo_1"} }' \ "https://api.floopy.ai/v1/routing/explain" | jq .Rate limits
Seção intitulada “Rate limits”Ambas as janelas são avaliadas atomicamente — se qualquer uma negar, a requisição é rejeitada com 429.
- 30 requests / minuto / organização (
floopy_explain_rpm_budget, default). - 10 requests / minuto / API key.
O cap duplo existe para que uma key vazada não consiga sondar internals do roteamento em alta cadência.
Veja também
Seção intitulada “Veja também”GET /v1/decisions/{request_id}— mesmo formato para requisições reais.- GET /v1/constraints — defina as constraints que o dry-run respeita.
- Metodologia de Confidence.