Auth, Accounts & API Keys
Rotas de gerenciamento de contas, autenticação, API keys e audit logs.
GET /api/accounts
Seção intitulada “GET /api/accounts”Descrição: Lista todas as contas do usuário autenticado, com dados de cada conta (nome, plano, status), o ID da conta ativa, status de admin da plataforma, e modo suporte.
Auth: Required (Supabase session, sem getAuthContext() — usa supabase.auth.getUser() diretamente)
Rate Limit: Nenhum
Audit: Nenhum
Request: N/A
Response:
- 200:
{ "data": { "accounts": [ { "account_id": "uuid", "role": "owner", "account": { "id": "uuid", "name": "Empresa X", "slug": "empresa-x", "logo_url": "https://...", "plan": "starter", "status": "active", "created_at": "2026-01-01T..." } } ], "active_account_id": "uuid | null", "is_admin": false, "support_mode": null }}- 401: Auth error
Notas:
is_adminé calculado verificando se o email do usuário está emPLATFORM_ADMIN_EMAILSsupport_modelido do cookiex17-support-mode(set durante impersonation)active_account_idlido do cookiex17-active-account-id
POST /api/accounts
Seção intitulada “POST /api/accounts”Descrição: Cria uma nova conta e vincula o usuário como owner. Seta cookie de conta ativa.
Auth: Required (Supabase session, sem getAuthContext())
Rate Limit: Nenhum
Audit: Nenhum
Request:
- Body (Zod
createAccountSchema):
{ "name": "string (obrigatório)"}Response:
- 201:
{ "data": { "account": { "id": "uuid", "name": "Empresa X", "slug": "empresa-x", "..." }, "role": "owner" }}- 401: Auth error
- 422: Validation error
Notas:
- Slug gerado automaticamente: lowercase, caracteres alfanuméricos + hifens, max 50 chars
- Usa
createAdminClient()para bypass RLS na inserção (accounts + account_users) - Seta cookie
x17-active-account-idpara a nova conta (httpOnly, 1 ano TTL)
POST /api/accounts/switch
Seção intitulada “POST /api/accounts/switch”Descrição: Troca a conta ativa do usuário. Valida que o usuário pertence a conta alvo.
Auth: Required (Supabase session, sem getAuthContext())
Rate Limit: Nenhum
Audit: Nenhum
Request:
- Body (Zod
switchAccountSchema):
{ "account_id": "uuid (obrigatório)"}Response:
- 200:
{ "data": { "account": { "id": "uuid", "name": "Empresa X", "..." }, "role": "owner" }}- 401: “Você não pertence a esta conta”
- 422: Validation error
Notas:
- Seta cookie
x17-active-account-idpara a nova conta - Atualiza
account_users.last_active_atpara o timestamp atual
POST /api/auth/accept-invite
Seção intitulada “POST /api/auth/accept-invite”Descrição: Aceita um convite de equipe. Valida token, expiração, email do usuário, e cria vínculo account_users.
Auth: Required (getAuthContext())
Rate Limit: Custom: 5 req/min por IP
Audit: create em account_user
Request:
- Body:
{ "token": "string (obrigatório)"}Response:
- 200:
{ "data": { "message": "Convite aceito", "account_id": "uuid", "role": "member" }}- 403: Convite enviado para outro email
- 404: Convite não encontrado
- 422: Token ausente, convite expirado, ou usuário já e membro
- 429: Rate limit excedido
Notas:
- Busca convite na tabela
team_invitescom status “pending” - Se expirado, atualiza status para “expired” antes de retornar erro
- Compara email do convite com email do usuário autenticado (case-insensitive)
- Usa claim optimista: UPDATE com WHERE
status = "pending"para evitar race condition - Usa
createAdminClient()para bypass RLS
GET /api/api-keys
Seção intitulada “GET /api/api-keys”Descrição: Lista todas as API keys do account. Retorna apenas metadados (não a key completa).
Auth: Required (getAuthContext())
Rate Limit: Nenhum
Audit: Nenhum
Request: N/A
Response:
- 200:
{ "data": [ { "id": "uuid", "name": "Integração Shopify", "key_prefix": "x17_live_sk_", "last_used_at": "2026-02-20T... | null", "created_at": "2026-01-15T..." } ]}Notas: Ordenadas por created_at desc. NAO retorna key_hash (campo sensivel).
POST /api/api-keys
Seção intitulada “POST /api/api-keys”Descrição: Cria uma nova API key. Gera key aleatoria com prefixo x17_live_sk_ e retorna a key completa (única vez que e visivel).
Auth: Required (getAuthContext()) + role “owner” ou “admin”
Rate Limit: api preset (30/min)
Audit: create em api_key
Request:
- Body:
{ "name": "string"}Response:
- 201:
{ "data": { "id": "uuid", "name": "Integração Shopify", "key_prefix": "x17_live_sk_", "full_key": "x17_live_sk_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345", "created_at": "2026-02-20T..." }}- 401: Auth error (sem permissão)
- 429: Rate limit excedido
Notas:
- Key gerada com
crypto.randomBytes(24).toString("base64url") full_keysó é retornado nesta resposta (não é armazenado em plaintext no futuro)key_prefixarmazenado como os primeiros 12 caracteres para identificação visual- Nota de segurança: o código atual armazena a key como
key_hashmas sem hash real (comentario “Em producao: hash com bcrypt”)
DELETE /api/api-keys/[id]
Seção intitulada “DELETE /api/api-keys/[id]”Descrição: Remove permanentemente uma API key (hard delete).
Auth: Required (getAuthContext()) + role “owner” ou “admin”
Rate Limit: Nenhum
Audit: delete em api_key
Request:
- Path params:
id(uuid) - ID da API key
Response:
- 204: No content
- 401: Auth error (sem permissão)
Notas: Delete scoped por account_id para prevenir acesso cross-tenant.
GET /api/audit-logs
Seção intitulada “GET /api/audit-logs”Descrição: Lista audit logs do account com paginação offset-based. Suporta filtro por entity type e action.
Auth: Required (getAuthContext())
Rate Limit: api preset (30/min)
Audit: Nenhum
Request:
- Query params:
page(number, default: 1) - páginalimit(number, default: 50, max: 100) - itens por páginaentityType(string, opcional) - filtro por tipo de entidadeaction(string, opcional) - filtro por ação (“create” | “update” | “delete” | etc.)
Response:
- 200:
{ "data": { "logs": [ { "id": "uuid", "account_id": "uuid", "user_id": "uuid", "action": "create", "entity_type": "automation", "entity_id": "uuid", "changes": { "name": "Nova Automação" }, "ip_address": "192.168.1.1", "created_at": "2026-02-20T..." } ], "total": 150, "page": 1, "limit": 50 }}- 429: Rate limit excedido
Notas:
- Usa
createAdminClient()para bypass RLS (tabelaaudit_logspode não ter RLS policy para SELECT por conta) - Resposta usa
NextResponse.jsondireto (nãosuccess()helper) — formato ligeiramente diferente:{ data: { logs, total, page, limit } }em vez de{ data: [...] } - Páginação via
range(offset, offset + limit - 1)do Supabase - Contagem total via
select("*", { count: "exact" })