Authentication
Dois mecanismos reais e distintos: JWT do Dashboard e API Key da Public API. Ambos usam o header Authorization: Bearer …, com credenciais diferentes.
Dashboard — JWT Bearer
Sessão autenticada de usuário. Obtenha o token em POST /auth/register ou POST /auth/login (resposta: accessToken). Envie Authorization: Bearer <ACCESS_TOKEN>.
Implementação: JwtAuthGuard (valida assinatura + campos do payload e monta CurrentUserContext) via @Protected(), exceto Links/Analytics do Dashboard, que usam LinksJwtAuthGuard (cópia local que só verifica a assinatura). Subdomain no Dashboard soma JwtRateLimitGuard (60/60, fail-open). No Swagger, esses endpoints exibem o cadeado bearer.
curl http://localhost:PORT/links \
-H "Authorization: Bearer <ACCESS_TOKEN>"Public API — API Key Bearer
Integrações externas. Crie a chave no Dashboard (POST /api-keys — a chave raw aparece uma única vez). Envie Authorization: Bearer <API_KEY>. A chave é armazenada como hash SHA-256 determinístico; permissões com fail-closed ([]). Toda rota pública combina ApiKeyAuthGuard + RateLimitGuard + RequirePermissionGuard(scope). No Swagger, esses endpoints exibem o cadeado apiKey.
curl http://localhost:PORT/api/v1/links \
-H "Authorization: Bearer <API_KEY>"Scopes (12 valores reais)
links:readlinks:writeanalytics:readapi-keys:managedomains:readdomains:managewebhooks:readwebhooks:managebilling:readbilling:managesubdomain:readsubdomain:manage
Exemplos: GET /api/v1/links exige links:read; POST /billing/checkout exige billing:manage; GET /me não exige scope adicional. Sem o scope: 403 Missing required scope. usage e invoices do Billing são JWT-only (decisão B9) — não existem na Public API.
Públicos sem autenticação
POST /auth/register, POST /auth/login, POST /auth/forgot-password, POST /auth/reset-password, GET /health, GET /ready e POST /webhooks/stripe (este autentica via header stripe-signature + raw body, não via JWT/API Key).