API de painel
Toda rota que o painel fala, num lugar só: método, caminho, papel exigido e o que cada uma devolve. As rotas vivem em backend/src/routes/ e não usam prefixo — cada arquivo escreve o caminho completo por extenso. Autenticação, papéis e as exceções ao piso vêm primeiro; as famílias de rota vêm na sequência.
Autenticação e papéis#
A superfície de painel tem uma única credencial: o cookie de sessão assinado — nome session_id, httpOnly, validade de 7 dias, no formato id da sessão seguido de um HMAC. Um cookie cru sem assinatura só foi aceito até LEGACY_UNSIGNED_UNTIL, 2026-10-01. A antiga credencial por bearer ADMIN_API_KEY foi removida: não existe no schema de ambiente e a sessão sentinela que ela criava é rejeitada mesmo se sobreviver no Redis. Em desenvolvimento, AUTH_DISABLED=true abre todas as rotas /api/* com aviso no boot — e a produção recusa o boot com essa variável ligada.
Toda mutação — POST, PUT, PATCH e DELETE — exige também o header X-CSRF-Token. O token é um HMAC derivado do segredo do cookie e do id da sessão (synchronizer sem estado), e o próprio GET /api/auth/me o devolve para o front. A verificação roda antes da lista de paths públicos e falha com 403 csrf_invalid em comparação em tempo constante. A única isenção é POST /api/auth/logout: um logout forjado só desloga, e exigir o token prenderia o usuário com sessão já expirada.
Rota inexistente devolve 404 para todo mundo, com ou sem sessão. E o 401 de sessão ausente ou expirada é enviado diretamente do guard, sem throw — o errorHandler global resolveria o status para 500. Sem sessão válida, o guard também falha fechado: sem escopo de tenant, a resposta é 500.
O piso automático por método
O papel mínimo de cada rota é automático, por método HTTP — nenhuma rota nova nasce aberta por esquecimento:
| Métodos | Piso automático | Exceção |
|---|---|---|
GET, HEAD | MEMBER | Leituras listadas em ADMIN_READ_PATHS sobem para ADMIN — ver a seção Rotas fora do piso, no fim da página. |
POST, PUT, PATCH, DELETE | ADMIN | Só /api/account/keys desce para MEMBER — ver a seção Rotas fora do piso, no fim da página. |
A hierarquia de papéis
Três papéis — OWNER > ADMIN > MEMBER — comparados por rank: quem tem rank igual ou superior ao exigido passa, e papel ausente falha fechado. O requireRole por rota roda depois do guard global: sem tenant responde 401; papel insuficiente responde 403 nomeando o mínimo exigido. Na prática o único valor usado é ADMIN — nenhuma rota exige OWNER diretamente; o papel só vale pela herança da hierarquia.
| Papel | Rank | O que consegue |
|---|---|---|
OWNER | 3 | Tudo o que ADMIN consegue, por herança da hierarquia. |
ADMIN | 2 | O piso de toda mutação e das leituras de infraestrutura da org. |
MEMBER | 1 | Leitura padrão do painel e as duas mutações sobre as próprias chaves de acesso. |
O que é público
Doze prefixos escapam do guard inteiro, por match de startsWith:
| Path | Por quê |
|---|---|
/health | Não existe como rota real — entrada legada corrigida. |
/metrics | Idem. |
/api/health | Orquestrador e monitor sem credencial; devolve 503 com payload quando o banco ou o Redis caem. |
/api/pool-stats | Idem — estado do pool do agente HTTP. |
/api/auth/me | Sondagem de login; autoprotegida: sem sessão devolve 401 com authenticated: false. |
/api/oauth/callback | Só os dois callbacks; init e status exigem sessão. |
/api/oauth/github/callback | Idem. |
/api/auth/logout | Público e isento de CSRF. |
/auth/sso | É o próprio login. |
/auth/callback | Callback do back-channel da Conta. |
/api/auth/oidc/ | O barra final cobre backchannel-logout, provisioning e topbar. |
/api/auth/mode | O front pergunta antes de desenhar o login. |
Chaves de provedor#
O CRUD e as ações em lote das contas de provedor. A criação passa pelo loadBalancer.addKey, e GET /api/keys/usage devolve as janelas de 5h e semanal de cada conta de plano da organização.
- GET
/api/keys/usage - GET
/api/keys/options - POST
/api/keys - PUT
/api/keys/:id - DELETE
/api/keys/:id - PUT
/api/keys/:id/toggle - PATCH
/api/keys/bulk-model - POST
/api/keys/:id/duplicate - PUT
/api/keys/:id/reset - POST
/api/keys/bulk-reactivate - POST
/api/keys/bulk-disable - POST
/api/keys/:id/clear-error
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/keys/usage | Janelas 5h/semanal de cada conta de plano da org. |
| GET | /api/keys/options | Lista leve de id, name, provider e group para dropdowns. |
| POST | /api/keys | Cria chave de provedor (loadBalancer.addKey). |
| PUT | /api/keys/:id | Atualiza chave. |
| DELETE | /api/keys/:id | Remove chave (204). |
| PUT | /api/keys/:id/toggle | Liga/desliga a chave. |
| PATCH | /api/keys/bulk-model | Troca modelo em lote. |
| POST | /api/keys/:id/duplicate | Clona chave com sufixo (copy). |
| PUT | /api/keys/:id/reset | Reseta contadores e estado da chave. |
| POST | /api/keys/bulk-reactivate | Reativa chave em lote. |
| POST | /api/keys/bulk-disable | Desabilita chave em lote. |
| POST | /api/keys/:id/clear-error | Limpa estado de erro. |
As duas ações em lote validam o corpo com .max(500) — max 500 keys per batch. O cliente fatia a lista só no reativar: BULK_REACTIVATE_MAX = 500 parte em lotes e, se um lote falha no meio, a mensagem diz quantas já tinham voltado. O desativar não fatia: com mais de 500 chaves ativas no recorte filtrado, o backend rejeita o corpo e nada é desativado — limitação conhecida, verificada em 2026-10-14.
Grupos#
Três famílias de grupos com o mesmo desenho de CRUD. Grupos de chaves agregam contas de provedor com strategy e hedgeConfig; grupos de config agregam URLs; e os grupos compartilhados são os que a org da Nomad libera para outras organizações escolherem (PLAT-12).
- GET
/api/groups - GET
/api/groups/:id - POST
/api/groups - PUT
/api/groups/:id - DELETE
/api/groups/:id - GET
/api/config-groups - GET
/api/config-groups/:id - POST
/api/config-groups - PUT
/api/config-groups/:id - DELETE
/api/config-groups/:id - GET
/api/shared-key-groups
Grupos de chaves de provedor — groups.ts
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/groups | Lista grupos com contagem de chaves e estratégia. |
| GET | /api/groups/:id | Detalhe do grupo + suas chaves. |
| POST | /api/groups | Cria grupo (name, description, strategy, hedgeConfig, apiKeyIds). |
| PUT | /api/groups/:id | Atualiza; suporta delta (addKeyIds/removeKeyIds). |
| DELETE | /api/groups/:id | Remove grupo. |
Grupos de config (URLs) — configGroups.ts
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/config-groups | Lista com contagem. |
| GET | /api/config-groups/:id | Detalhe + requestConfigs. |
| POST | /api/config-groups | Cria grupo de configs. |
| PUT | /api/config-groups/:id | Atualiza. |
| DELETE | /api/config-groups/:id | Remove. |
Grupos compartilhados — sharedGroups.ts
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/shared-key-groups | Grupos liberados pela Nomad que a org pode escolher para a URL (PLAT-12). |
Configs de URL#
As URLs do proxy. Criação e atualização aceitam as atribuições de usuário na mesma chamada; PUT /api/configs/:id/assignments define só os userIds atribuídos. A visibilidade RESTRICTED é o que esconde o detalhe do MEMBER.
- GET
/api/configs - GET
/api/configs/:id - POST
/api/configs - PUT
/api/configs/:id - PUT
/api/configs/:id/assignments - POST
/api/configs/:id/toggle - POST
/api/configs/:id/duplicate - DELETE
/api/configs/:id - GET
/api/configs/stats/summary
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/configs | Lista configs; paginação opcional (page/limit). |
| GET | /api/configs/:id | Detalhe; invisível ao MEMBER vira 404, não 403. |
| POST | /api/configs | Cria config + atribuições. |
| PUT | /api/configs/:id | Atualiza config + atribuições. |
| PUT | /api/configs/:id/assignments | Define só os userIds atribuídos. |
| POST | /api/configs/:id/toggle | Liga/desliga config. |
| POST | /api/configs/:id/duplicate | Duplica (201). |
| DELETE | /api/configs/:id | Remove (204). |
| GET | /api/configs/stats/summary | Estatísticas, respeitando o escopo do MEMBER. |
Uma config RESTRICTED que o MEMBER não pode ver não revela existência: o detalhe responde 404, não 403 — o mesmo princípio da URL pública, que não enumera organizações.
Requests#
O log de requests proxied: listagem com filtros e sort, resumo, facets, exportação e agregados por URL, grupo e chave. Tudo é leitura, exceto o DELETE por ids.
- GET
/api/requests - GET
/api/requests/summary - GET
/api/requests/facets - GET
/api/requests/export - GET
/api/requests/stats/by-config - GET
/api/requests/stats/by-group - GET
/api/requests/stats - GET
/api/requests/:id - GET
/api/requests/api-key-stats - DELETE
/api/requests
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/requests | Lista paginada com filtros e sort. |
| GET | /api/requests/summary | Resumo agregado dos filtros. |
| GET | /api/requests/facets | Valores distintos para os filtros. |
| GET | /api/requests/export | Exporta CSV/JSON/NDJSON. |
| GET | /api/requests/stats/by-config | groupBy por customUrl. |
| GET | /api/requests/stats/by-group | Agregado por RequestConfigGroup ativo. |
| GET | /api/requests/stats | Stats com apiKeyId + timeRange (default 24h). |
| GET | /api/requests/:id | Detalhe de uma request; descomprime corpos. |
| GET | /api/requests/api-key-stats | Stats por chave de provedor. |
| DELETE | /api/requests | Apaga requests por ids (CSV na querystring). |
GET /api/requests/export e GET /api/requests/:id são limitados por IP. O export responde 429 com Retry-After: 60 quando o teto estoura; o detalhe descomprime os corpos da request — é por isso que ele também entra no rate limit.
Métricas e health#
Agregados para as telas de insights e os sinais de operação. /api/health e /api/pool-stats são públicos — é o orquestrador que os consulta; o resto exige sessão, e duas leituras sobem para ADMIN.
- GET
/api/metrics/advanced - GET
/api/metrics/latency - GET
/api/metrics/costs - GET
/api/metrics/activity - GET
/api/metrics/health - GET
/api/metrics/models - GET
/api/metrics/insights - GET
/api/metrics/counters - GET
/api/health - GET
/api/health/db - GET
/api/pool-stats - GET
/api/stats - POST
/api/cleanup/run - GET
/api/cleanup/stats
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/metrics/advanced | Métricas agregadas (days, default 7). |
| GET | /api/metrics/latency | Latência. |
| GET | /api/metrics/costs | Custos. |
| GET | /api/metrics/activity | Série de atividade (bucket/points). |
| GET | /api/metrics/health | Saúde das chaves (ADMIN). |
| GET | /api/metrics/models | Uso por modelo. |
| GET | /api/metrics/insights | Insights derivados. |
| GET | /api/metrics/counters | Snapshot dos contadores da instância (ADMIN). |
| GET | /api/health | Health geral; devolve 503 com payload se o banco ou o Redis caírem. |
| GET | /api/health/db | SELECT 1 + latência. |
| GET | /api/pool-stats | In-flight/queued por host do agente HTTP. |
| GET | /api/stats | Estado do pool: status, uso e cooldown de cada chave (ADMIN). |
| POST | /api/cleanup/run | Dispara limpeza de requests expiradas — rate-limited. |
| GET | /api/cleanup/stats | Estatísticas da limpeza (ADMIN). |
Modelos e provedores#
Consultas ao vivo sobre o que cada chave suporta: modelos disponíveis, specs, capacidades por provedor e o estado de rate limit. A lista de providers vem do registry, e /api/test-connection valida credencial com authType/cookies antes de cadastrar.
- POST
/api/models/fetch - GET
/api/providers - GET
/api/providers/capabilities - POST
/api/models/specs - GET
/api/models/specs/cached - GET
/api/ratelimit/status - POST
/api/test-connection
| Método | Rota | O que faz |
|---|---|---|
| POST | /api/models/fetch | Lista modelos ao vivo de uma chave. |
| GET | /api/providers | Todos os provider configs (a lista do registry). |
| GET | /api/providers/capabilities | Parâmetros suportados por provider. |
| POST | /api/models/specs | Busca specs de modelo (max tokens, etc.). |
| GET | /api/models/specs/cached | Specs já cacheadas + rate limits por provider. |
| GET | /api/ratelimit/status | Status de rate limit por provider/model/keyId; 403 sem tenant. |
| POST | /api/test-connection | Testa conexão com authType/cookies. |
/api/models/fetch e /api/models/specs respondem sempre ao vivo — o fetch sai com Cache-Control: no-store. A consulta de rate limit exige tenant: sem ele, 403.
OAuth#
Quatro arquivos, quatro fluxos — Claude, GitHub/Copilot, Codex e Meta — todos gravando a organização dona na sessão do fluxo e consultando o andamento por sessionId. O device flow do Copilot, do Codex e do Meta tem os mesmos dois passos: init e poll.
- POST
/api/oauth/init - POST
/api/oauth/callback - GET
/api/oauth/callback - GET
/api/oauth/status/:sessionId - POST
/api/oauth/github/init - POST
/api/oauth/github/callback - GET
/api/oauth/github/callback - GET
/api/oauth/github/status/:sessionId - POST
/api/copilot/device/init - POST
/api/copilot/device/poll - POST
/api/github/add-token - POST
/api/codex/device/init - POST
/api/codex/device/poll - POST
/api/meta/device/init - POST
/api/meta/device/poll
Claude — oauth.ts
| Método | Rota | O que faz |
|---|---|---|
| POST | /api/oauth/init | Inicia o fluxo; grava a org dona na sessão (exige ADMIN). |
| POST | /api/oauth/callback | Callback POST com sessionId e code. |
| GET | /api/oauth/callback | Callback GET, por redirect do provedor. |
| GET | /api/oauth/status/:sessionId | Status; só responde para a org que iniciou. |
GitHub e Copilot — githubOAuth.ts
| Método | Rota | O que faz |
|---|---|---|
| POST | /api/oauth/github/init | Inicia OAuth GitHub. |
| POST | /api/oauth/github/callback | Callback POST. |
| GET | /api/oauth/github/callback | Callback GET. |
| GET | /api/oauth/github/status/:sessionId | Status do fluxo. |
| POST | /api/copilot/device/init | Copilot Device Flow, passo 1. |
| POST | /api/copilot/device/poll | Copilot Device Flow, passo 2. |
| POST | /api/github/add-token | PAT do GitHub manual, sem OAuth. |
Codex e Meta — device flow
| Método | Rota | O que faz |
|---|---|---|
| POST | /api/codex/device/init | Device Flow do Codex, passo 1. |
| POST | /api/codex/device/poll | Device Flow do Codex, passo 2. |
| POST | /api/meta/device/init | Device Flow do Meta, passo 1. |
| POST | /api/meta/device/poll | Device Flow do Meta, passo 2. |
Os callbacks são públicos no guard — é o provedor que chega neles, sem cookie. Mas init e status exigem sessão: é no init que a organização dona da chave é gravada na sessão do fluxo, e o status só responde para a org que iniciou.
Conta e autenticação#
O login é só pela Conta Nommand (OIDC): o app não tem login próprio, cadastro nem gestão de empresa. As rotas /auth/* são redirects do fluxo; as de /api/auth/* servem o front e os webhooks da Conta.
- GET
/api/auth/mode - GET
/auth/sso - GET
/auth/callback - GET
/api/auth/oidc/topbar - POST
/api/auth/oidc/backchannel-logout - POST
/api/auth/oidc/provisioning - GET
/api/auth/me - POST
/api/auth/logout
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/auth/mode | Modo de login (público). |
| GET | /auth/sso | Inicia o login: PKCE, grava o pending no Redis, 302 para a Conta. |
| GET | /auth/callback | Callback da Conta: valida iss/state, troca o código, aplica JIT, cria a sessão, 302. |
| GET | /api/auth/oidc/topbar | Perfil/empresa da Conta; valida a sessão por conta própria. |
| POST | /api/auth/oidc/backchannel-logout | Valida o logout_token por JWKS e encerra sessões por sid/org_id/sub. |
| POST | /api/auth/oidc/provisioning | Webhook da Conta; valida HMAC via x-conta-signature. |
| GET | /api/auth/me | Quem está logado, org ativa, orgs e o csrfToken. |
| POST | /api/auth/logout | Logout RP; devolve o endSessionUrl da Conta. |
A topbar vive sob o prefixo público /api/auth/oidc/, então valida a sessão por conta própria: 401 session_ended ou 502 conta_unavailable. O back-channel logout cria um sub-escopo Fastify com removeAllContentTypeParsers() e um parser *, para receber o corpo cru sem vazar para as outras rotas.
Chaves de acesso ao proxy#
As chaves sk-lb- que os clientes usam no proxy. Pessoais: o usuário cria e revoga as próprias. De serviço e as de terceiros na organização: só ADMIN.
- GET
/api/account/keys - POST
/api/account/keys - DELETE
/api/account/keys/:id - GET
/api/organizations/:id/api-keys - DELETE
/api/organizations/:id/api-keys/:keyId - GET
/api/organizations/:id/service-keys - POST
/api/organizations/:id/service-keys - PUT
/api/organizations/:id/service-keys/:keyId - DELETE
/api/organizations/:id/service-keys/:keyId
| Método | Rota | O que faz | Papel |
|---|---|---|---|
| GET | /api/account/keys | Minhas chaves pessoais. | MEMBER |
| POST | /api/account/keys | Cria a própria sk-lb-; o secret volta uma vez. | MEMBER |
| DELETE | /api/account/keys/:id | Revoga a própria. | MEMBER |
| GET | /api/organizations/:id/api-keys | Lista chaves pessoais da org. | ADMIN |
| DELETE | /api/organizations/:id/api-keys/:keyId | Revoga chave de terceiro. | ADMIN |
| GET | /api/organizations/:id/service-keys | Lista service keys. | ADMIN |
| POST | /api/organizations/:id/service-keys | Cria service key; o raw volta uma vez. | ADMIN |
| PUT | /api/organizations/:id/service-keys/:keyId | Atualiza. | ADMIN |
| DELETE | /api/organizations/:id/service-keys/:keyId | Revoga. | ADMIN |
O secret da chave pessoal e o raw da service key são devolvidos uma única vez, na criação — o banco guarda só o hash. O requireRole('ADMIN') destas rotas é redundante com o piso global de mutação: é defesa em profundidade, já que requireRole roda depois do guard.
Organizações e auditoria#
A organização é o tenant de tudo. A leitura é de membro; atualizar a org, ver membros e ler o log de auditoria são de ADMIN.
- GET
/api/organizations - GET
/api/organizations/:id - PUT
/api/organizations/:id - GET
/api/organizations/:id/members - GET
/api/organizations/:id/audit
| Método | Rota | O que faz | Papel |
|---|---|---|---|
| GET | /api/organizations | Orgs do usuário. | MEMBER |
| GET | /api/organizations/:id | Detalhe para membro. | MEMBER |
| PUT | /api/organizations/:id | Atualiza org. | ADMIN |
| GET | /api/organizations/:id/members | Lista membros. | ADMIN |
| GET | /api/organizations/:id/audit | Log de auditoria da org. | ADMIN |
Login por senha, cadastro, convite e gestão de empresa saíram (LB-CONTA-ONLY-01): a Conta Nommand é a fonte dessas operações. O prefixo /invites continua reservado só para manter o slug bloqueado.
Plataforma#
A visão cross-org para o administrador da plataforma. A guarda é própria — um preHandler que checa User.isPlatformAdmin antes de qualquer handler — e quase todos os handlers rodam dentro de runAsPlatform, o escape do tenancy.
- GET
/api/platform/overview - GET
/api/platform/organizations - GET
/api/platform/organizations/:id - GET
/api/platform/health - GET
/api/platform/nomad - POST
/api/platform/nomad - GET
/api/platform/shared-groups - PUT
/api/platform/shared-groups/:groupId - GET
/api/platform/keys/usage - POST
/api/platform/keys/:id/usage-limit/reset - GET
/api/platform/requests
| Método | Rota | O que faz |
|---|---|---|
| GET | /api/platform/overview | KPIs globais: orgs, users, séries, keys, funil, top orgs. |
| GET | /api/platform/organizations | Todas as orgs (SQL cru) com membros, slug e suspendedAt. |
| GET | /api/platform/organizations/:id | Detalhe de uma org. |
| GET | /api/platform/health | Saúde por provider, chaves de plano, desabilitadas. |
| GET | /api/platform/nomad | Org da Nomad. |
| POST | /api/platform/nomad | Define a org da Nomad. |
| GET | /api/platform/shared-groups | Lista grupos liberados. |
| PUT | /api/platform/shared-groups/:groupId | Libera/ajusta grupo (enabled, scope, organizationIds, displayName). |
| GET | /api/platform/keys/usage | Janelas de uso das chaves de plano de todas as orgs. |
| POST | /api/platform/keys/:id/usage-limit/reset | Libera conta presa no limite; aceita windows: fiveHour|weekly. |
| GET | /api/platform/requests | Requests cross-org (organizationId, status, limit até 500). |
Quem não é platform admin recebe 404, não 403 — a resposta não revela que a área existe. E o limit de /api/platform/requests aceita até 500 registros por consulta.
WebSocket#
O stream realtime do log de requests. Não é REST: é upgrade de WebSocket, em dois caminhos que apontam para o mesmo handler — sem e com o prefixo /api, porque o reverse-proxy pode fazer strip dele.
- GET
/ws/requests - GET
/api/ws/requests
| Caminho | O que faz |
|---|---|
GET /ws/requests | Stream realtime de requests proxied, sem /api. |
GET /api/ws/requests | Mesmo handler, com /api (easypanel/nginx pode fazer strip). |
O protocolo é um ping-pong de três tipos:
servidor → cliente {"type": "hello" | "request" | "ping"}
cliente → servidor {"type": "ping"}
O escopo da organização fica preso ao socket e é revalidado por keepalive: se a sessão sumir ou trocar de org/usuário, a conexão fecha com 1008.
Rotas fora do piso#
O piso por método tem duas listas de exceção em middleware/auth.ts. O que escapa delas é MEMBER-read comum: /api/providers e /api/providers/capabilities, as métricas fora de health e counters, os requests fora de api-key-stats e stats/by-group, /api/organizations e /api/account/keys.
Leituras que exigem ADMIN (ADMIN_READ_PATHS): /api/stats, /api/keys, /api/groups, /api/shared-key-groups, /api/config-groups, /api/oauth, /api/models e /api/ratelimit, /api/cleanup, /api/metrics/health e /api/metrics/counters, /api/requests/api-key-stats e /api/requests/stats/by-group, e as sub-rotas de /api/organizations/:id — members, audit, api-keys e service-keys: infraestrutura da org que o MEMBER não vê nem em leitura. Mutações que descem para MEMBER (MEMBER_MUTATION_PATHS): criar e revogar as próprias chaves em /api/account/keys — o handler só enxerga as chaves do usuário da sessão.
Próximos passos#
Você tem o mapa das rotas. Para a outra superfície — a que o cliente chama — e para o que gira em volta do painel:
Depois de dominar o básico, explore além: