Documentação do app v1.0

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.

401 direto, 404 para todos

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étodosPiso automáticoExceção
GET, HEADMEMBERLeituras listadas em ADMIN_READ_PATHS sobem para ADMIN — ver a seção Rotas fora do piso, no fim da página.
POST, PUT, PATCH, DELETEADMINSó /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.

PapelRankO que consegue
OWNER3Tudo o que ADMIN consegue, por herança da hierarquia.
ADMIN2O piso de toda mutação e das leituras de infraestrutura da org.
MEMBER1Leitura 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:

PathPor quê
/healthNão existe como rota real — entrada legada corrigida.
/metricsIdem.
/api/healthOrquestrador e monitor sem credencial; devolve 503 com payload quando o banco ou o Redis caem.
/api/pool-statsIdem — estado do pool do agente HTTP.
/api/auth/meSondagem de login; autoprotegida: sem sessão devolve 401 com authenticated: false.
/api/oauth/callbackSó os dois callbacks; init e status exigem sessão.
/api/oauth/github/callbackIdem.
/api/auth/logoutPúblico e isento de CSRF.
/auth/ssoÉ o próprio login.
/auth/callbackCallback do back-channel da Conta.
/api/auth/oidc/O barra final cobre backchannel-logout, provisioning e topbar.
/api/auth/modeO 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.

MétodoRotaO que faz
GET/api/keys/usageJanelas 5h/semanal de cada conta de plano da org.
GET/api/keys/optionsLista leve de id, name, provider e group para dropdowns.
POST/api/keysCria chave de provedor (loadBalancer.addKey).
PUT/api/keys/:idAtualiza chave.
DELETE/api/keys/:idRemove chave (204).
PUT/api/keys/:id/toggleLiga/desliga a chave.
PATCH/api/keys/bulk-modelTroca modelo em lote.
POST/api/keys/:id/duplicateClona chave com sufixo (copy).
PUT/api/keys/:id/resetReseta contadores e estado da chave.
POST/api/keys/bulk-reactivateReativa chave em lote.
POST/api/keys/bulk-disableDesabilita chave em lote.
POST/api/keys/:id/clear-errorLimpa estado de erro.
Teto de 500 ids por lote

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).

Grupos de chaves de provedor — groups.ts

MétodoRotaO que faz
GET/api/groupsLista grupos com contagem de chaves e estratégia.
GET/api/groups/:idDetalhe do grupo + suas chaves.
POST/api/groupsCria grupo (name, description, strategy, hedgeConfig, apiKeyIds).
PUT/api/groups/:idAtualiza; suporta delta (addKeyIds/removeKeyIds).
DELETE/api/groups/:idRemove grupo.

Grupos de config (URLs) — configGroups.ts

MétodoRotaO que faz
GET/api/config-groupsLista com contagem.
GET/api/config-groups/:idDetalhe + requestConfigs.
POST/api/config-groupsCria grupo de configs.
PUT/api/config-groups/:idAtualiza.
DELETE/api/config-groups/:idRemove.

Grupos compartilhados — sharedGroups.ts

MétodoRotaO que faz
GET/api/shared-key-groupsGrupos 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.

MétodoRotaO que faz
GET/api/configsLista configs; paginação opcional (page/limit).
GET/api/configs/:idDetalhe; invisível ao MEMBER vira 404, não 403.
POST/api/configsCria config + atribuições.
PUT/api/configs/:idAtualiza config + atribuições.
PUT/api/configs/:id/assignmentsDefine só os userIds atribuídos.
POST/api/configs/:id/toggleLiga/desliga config.
POST/api/configs/:id/duplicateDuplica (201).
DELETE/api/configs/:idRemove (204).
GET/api/configs/stats/summaryEstatísticas, respeitando o escopo do MEMBER.
404 em vez de 403

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.

MétodoRotaO que faz
GET/api/requestsLista paginada com filtros e sort.
GET/api/requests/summaryResumo agregado dos filtros.
GET/api/requests/facetsValores distintos para os filtros.
GET/api/requests/exportExporta CSV/JSON/NDJSON.
GET/api/requests/stats/by-configgroupBy por customUrl.
GET/api/requests/stats/by-groupAgregado por RequestConfigGroup ativo.
GET/api/requests/statsStats com apiKeyId + timeRange (default 24h).
GET/api/requests/:idDetalhe de uma request; descomprime corpos.
GET/api/requests/api-key-statsStats por chave de provedor.
DELETE/api/requestsApaga requests por ids (CSV na querystring).
Export e detalhe são rate-limited

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.

MétodoRotaO que faz
GET/api/metrics/advancedMétricas agregadas (days, default 7).
GET/api/metrics/latencyLatência.
GET/api/metrics/costsCustos.
GET/api/metrics/activitySérie de atividade (bucket/points).
GET/api/metrics/healthSaúde das chaves (ADMIN).
GET/api/metrics/modelsUso por modelo.
GET/api/metrics/insightsInsights derivados.
GET/api/metrics/countersSnapshot dos contadores da instância (ADMIN).
GET/api/healthHealth geral; devolve 503 com payload se o banco ou o Redis caírem.
GET/api/health/dbSELECT 1 + latência.
GET/api/pool-statsIn-flight/queued por host do agente HTTP.
GET/api/statsEstado do pool: status, uso e cooldown de cada chave (ADMIN).
POST/api/cleanup/runDispara limpeza de requests expiradas — rate-limited.
GET/api/cleanup/statsEstatí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.

MétodoRotaO que faz
POST/api/models/fetchLista modelos ao vivo de uma chave.
GET/api/providersTodos os provider configs (a lista do registry).
GET/api/providers/capabilitiesParâmetros suportados por provider.
POST/api/models/specsBusca specs de modelo (max tokens, etc.).
GET/api/models/specs/cachedSpecs já cacheadas + rate limits por provider.
GET/api/ratelimit/statusStatus de rate limit por provider/model/keyId; 403 sem tenant.
POST/api/test-connectionTesta conexão com authType/cookies.
Sem cache HTTP

/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.

Claude — oauth.ts

MétodoRotaO que faz
POST/api/oauth/initInicia o fluxo; grava a org dona na sessão (exige ADMIN).
POST/api/oauth/callbackCallback POST com sessionId e code.
GET/api/oauth/callbackCallback GET, por redirect do provedor.
GET/api/oauth/status/:sessionIdStatus; só responde para a org que iniciou.

GitHub e Copilot — githubOAuth.ts

MétodoRotaO que faz
POST/api/oauth/github/initInicia OAuth GitHub.
POST/api/oauth/github/callbackCallback POST.
GET/api/oauth/github/callbackCallback GET.
GET/api/oauth/github/status/:sessionIdStatus do fluxo.
POST/api/copilot/device/initCopilot Device Flow, passo 1.
POST/api/copilot/device/pollCopilot Device Flow, passo 2.
POST/api/github/add-tokenPAT do GitHub manual, sem OAuth.

Codex e Meta — device flow

MétodoRotaO que faz
POST/api/codex/device/initDevice Flow do Codex, passo 1.
POST/api/codex/device/pollDevice Flow do Codex, passo 2.
POST/api/meta/device/initDevice Flow do Meta, passo 1.
POST/api/meta/device/pollDevice Flow do Meta, passo 2.
Callbacks públicos, init e status não

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.

MétodoRotaO que faz
GET/api/auth/modeModo de login (público).
GET/auth/ssoInicia o login: PKCE, grava o pending no Redis, 302 para a Conta.
GET/auth/callbackCallback da Conta: valida iss/state, troca o código, aplica JIT, cria a sessão, 302.
GET/api/auth/oidc/topbarPerfil/empresa da Conta; valida a sessão por conta própria.
POST/api/auth/oidc/backchannel-logoutValida o logout_token por JWKS e encerra sessões por sid/org_id/sub.
POST/api/auth/oidc/provisioningWebhook da Conta; valida HMAC via x-conta-signature.
GET/api/auth/meQuem está logado, org ativa, orgs e o csrfToken.
POST/api/auth/logoutLogout RP; devolve o endSessionUrl da Conta.
O topbar se protege sozinho

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.

MétodoRotaO que fazPapel
GET/api/account/keysMinhas chaves pessoais.MEMBER
POST/api/account/keysCria a própria sk-lb-; o secret volta uma vez.MEMBER
DELETE/api/account/keys/:idRevoga a própria.MEMBER
GET/api/organizations/:id/api-keysLista chaves pessoais da org.ADMIN
DELETE/api/organizations/:id/api-keys/:keyIdRevoga chave de terceiro.ADMIN
GET/api/organizations/:id/service-keysLista service keys.ADMIN
POST/api/organizations/:id/service-keysCria service key; o raw volta uma vez.ADMIN
PUT/api/organizations/:id/service-keys/:keyIdAtualiza.ADMIN
DELETE/api/organizations/:id/service-keys/:keyIdRevoga.ADMIN
O segredo aparece uma vez

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.

MétodoRotaO que fazPapel
GET/api/organizationsOrgs do usuário.MEMBER
GET/api/organizations/:idDetalhe para membro.MEMBER
PUT/api/organizations/:idAtualiza org.ADMIN
GET/api/organizations/:id/membersLista membros.ADMIN
GET/api/organizations/:id/auditLog de auditoria da org.ADMIN
Sem cadastro nem convite

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.

MétodoRotaO que faz
GET/api/platform/overviewKPIs globais: orgs, users, séries, keys, funil, top orgs.
GET/api/platform/organizationsTodas as orgs (SQL cru) com membros, slug e suspendedAt.
GET/api/platform/organizations/:idDetalhe de uma org.
GET/api/platform/healthSaúde por provider, chaves de plano, desabilitadas.
GET/api/platform/nomadOrg da Nomad.
POST/api/platform/nomadDefine a org da Nomad.
GET/api/platform/shared-groupsLista grupos liberados.
PUT/api/platform/shared-groups/:groupIdLibera/ajusta grupo (enabled, scope, organizationIds, displayName).
GET/api/platform/keys/usageJanelas de uso das chaves de plano de todas as orgs.
POST/api/platform/keys/:id/usage-limit/resetLibera conta presa no limite; aceita windows: fiveHour|weekly.
GET/api/platform/requestsRequests cross-org (organizationId, status, limit até 500).
404, não 403

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.

CaminhoO que faz
GET /ws/requestsStream realtime de requests proxied, sem /api.
GET /api/ws/requestsMesmo handler, com /api (easypanel/nginx pode fazer strip).

O protocolo é um ping-pong de três tipos:

text
servidor → cliente   {"type": "hello" | "request" | "ping"}
cliente → servidor   {"type": "ping"}
O tenancy acompanha o socket

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.

As duas listas do guard

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:

Documentação gerada com a skill docs-padrao.