NoteBugsDocs

Referência da API

Autenticação e sessão

As rotas que respondem sem credencial: sessão, primeiro acesso, entrar e sair.

Estas quatro, mais /api/health, são a lista pública inteira: elas respondem sem credencial porque são o caminho para obter uma.

GET/api/auth/session
Papel: nenhum

Quem é esta requisição, quais espaços de trabalho ela alcança e se a instalação ainda espera o primeiro acesso.

Responde sempre 200, inclusive sem credencial nenhuma: “não há ninguém” é uma resposta, e não uma falha. É por ela que a interface decide entre desenhar o quadro, a tela de login ou a de primeiro acesso.

Requisição

curl -s http://localhost:3000/api/auth/session

Resposta200

{
  "user": {
    "id": "cmt94cjx5000vql01imegem2r",
    "name": "Ana",
    "email": "[email protected]",
    "role": "ADMIN",
    "avatarUrl": null,
    "jobTitle": null,
    "timezone": "",
    "notifyBackupReady": true,
    "hasApiToken": true,
    "apiTokenCreatedAt": "2026-08-25T20:37:29.998Z",
    "apiTokenLast4": "mGQ",
    "passwordPending": false,
    "lastLoginAt": "2026-08-27T19:50:42.947Z",
    "createdAt": "2026-08-25T20:29:57.593Z",
    "tenants": [
      {
        "id": "cmsp4djx60002p801o7ybpkv7",
        "name": "Pessoal",
        "color": "amber",
        "role": "ADMIN",
        "membership": null
      }
    ]
  },
  "setupRequired": false,
  "publicMode": false
}

Respostas de erro

CódigoQuando acontece
429Requisições demais para este IP no minuto. A resposta traz Retry-After com quantos segundos esperar.

publicMode: true significa que a instalação dispensa o login: a identidade é um visitante anônimo de papel ADMIN, sem registro no banco.

POST/api/auth/setup
Papel: nenhum

Cria a PRIMEIRA conta da instalação, que nasce administradora.

A janela em que esta rota aceita alguma coisa é exatamente o intervalo entre a instalação subir e a primeira conta existir. Depois disso, 409 para sempre, e não há autocadastro.

Corpo da requisição

CampoTipoDescrição
nameobrigatóriostringNome de quem vai aparecer no cabeçalho e nos comentários.
emailobrigatóriostringIdentidade da conta: única no banco, normalizada para minúsculas.
passwordobrigatóriostringDe 8 a 200 caracteres. Espaço no começo ou no fim faz parte dela e não é aparado.

Requisição

curl -s -X POST http://localhost:3000/api/auth/setup \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ana",
    "email": "[email protected]",
    "password": "uma senha longa"
  }'

Resposta201

{
  "user": {
    "id": "cmt94cjx5000vql01imegem2r",
    "name": "Ana",
    "email": "[email protected]",
    "role": "ADMIN",
    "tenants": []
  },
  "setupRequired": false,
  "publicMode": false
}

Respostas de erro

CódigoQuando acontece
409A instalação já tem uma conta. A janela do primeiro acesso se fecha sozinha e não reabre.
422O corpo não passou pelo schema. O campo details traz fieldErrors e formErrors do zod, campo a campo.
429Requisições demais para este IP no minuto. A resposta traz Retry-After com quantos segundos esperar.

A conta criada aqui vira membro de todos os espaços que já existem, e o papel não vem no corpo: ela nasce ADMIN por definição.

POST/api/auth/login
Papel: nenhum

Troca e-mail e senha por uma sessão, entregue num cookie HttpOnly.

O cookie é SameSite=Lax e ganha Secure conforme o pedido chegou: numa instalação de mesa em http://localhost ele precisa ser aceito, e atrás de um proxy com TLS precisa ser Secure.

Corpo da requisição

CampoTipoDescrição
emailobrigatóriostringNormalizado para minúsculas antes da consulta.
passwordobrigatóriostringSem mínimo de tamanho nesta rota: qualquer credencial errada recebe a mesma resposta.

Requisição

# -c guarda o cookie de sessão no arquivo indicado
curl -s -c cookies.txt -X POST http://localhost:3000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "password": "uma senha longa" }'

Resposta200

{
  "user": {
    "id": "cmt94cjx5000vql01imegem2r",
    "name": "Ana",
    "email": "[email protected]",
    "role": "ADMIN",
    "tenants": []
  },
  "setupRequired": false,
  "publicMode": false
}

Respostas de erro

CódigoQuando acontece
401E-mail ou senha inválidos. A resposta é a mesma frase nos três casos de falha, e leva o mesmo tempo.
422O corpo não passou pelo schema. O campo details traz fieldErrors e formErrors do zod, campo a campo.
429Tentativas de acesso demais. O limite do login é mais estreito (10 a cada 5 minutos) e o contador inclui o e-mail alvo.

Para consumir a API de fora, prefira o token de API: Authorization: Bearer. O cookie tem precedência sobre ele quando os dois vêm juntos.

POST/api/auth/logout
Papel: nenhum

Encerra a sessão desta aba.

Idempotente e sem identidade exigida: sair sem estar dentro devolve 200.

Requisição

curl -s -b cookies.txt -X POST http://localhost:3000/api/auth/logout

Resposta200

{ "ok": true }

Respostas de erro

CódigoQuando acontece
429Requisições demais para este IP no minuto. A resposta traz Retry-After com quantos segundos esperar.