Endpoints HTTP expostos pelo Wedding Finance Planner.
💡 A maioria das interações é feita via Server Actions (em src/app/actions/), invocadas direto pelos formulários React. Este documento lista apenas as rotas HTTP clássicas.
GET /api/auth/signin
POST /api/auth/callback/credentials
GET /api/auth/signout
GET /api/auth/session
Gerenciados pelo Auth.js v5. Veja NextAuth docs.
Login (Server Action equivalente):
// src/app/login/login-form.tsx
await signIn("credentials", {
email,
password,
totp, // opcional, se 2FA estiver ativo
redirect: false,
});
Rate limit: 5 tentativas/min por email + 30/min por IP. Estourar
qualquer um derruba para TOO_MANY_ATTEMPTS.
Erros conhecidos retornados:
| Mensagem | Significado |
|---|---|
2FA_REQUIRED |
Usuário tem 2FA ativo; reenvie com totp. |
2FA_SETUP_REQUIRED |
Política exige 2FA mas usuário não configurou. |
TOO_MANY_ATTEMPTS |
Rate limit estourado — aguarde 1 minuto. |
null (callback retorna falha) |
Credenciais inválidas. Inclui email inexistente, senha errada e conta arquivada/desativada (indistinto, anti-enumeração). |
GET /api/calendar.ics
Requer: sessão válida.
Retorna: arquivo .ics (Content-Type: text/calendar) com:
deadline setado.dueDate.Exemplo de uso (Google Calendar):
https://casamento.seudominio.com/api/calendar.ics⚠️ O endpoint exige autenticação. Para uma versão pública (read-only, com token opaco), abra uma issue.
GET /api/backup
Requer: sessão válida, role com canViewSensitiveFinance (ADMIN, GROOM
ou BRIDE). Outras roles recebem 403.
Retorna: JSON com envelope { checksum, payload }. Formato:
{
"checksum": { "algorithm": "sha256", "value": "<64 hex>" },
"payload": {
"version": 3,
"exportedAt": "2026-05-23T12:00:00.000Z",
"meta": {
"appVersion": "0.1.0",
"hostname": "debian",
"nodeVersion": "v20.x",
"exportedBy": { "id": "...", "email": "..." }
},
"eventSettings": { ... },
"securitySettings": { ... },
"users": [...],
"vendors": [...],
"...": "demais 21 coleções",
"notificationLogs": [...],
"auditLogs": [...]
}
}
Headers:
Content-Type: application/json; charset=utf-8Content-Disposition: attachment; filename="wedding-finance-backup-YYYY-MM-DD.json"Cache-Control: no-storeX-Backup-Version: 3X-Backup-Checksum: <sha256 hex>Apenas ADMIN exporta users, notificationLogs e auditLogs. GROOM/BRIDE
recebem o payload sem essas três coleções. Cada chamada grava AuditLog
com action BACKUP_EXPORT.
POST /api/backup/validate
Content-Type: multipart/form-data
Body: file=@backup.json
Requer: mesma permissão do export. Não toca no banco. Retorna 200 com
{ ok, version, systemVersion, exportedAt, meta, checksumValid, counts,
warnings } ou 422 com issues.
POST /api/backup/restore
Content-Type: multipart/form-data
Body:
file=@backup.json
password=<senha do admin logado>
confirm=WIPE_AND_RESTORE
Requer: ADMIN + senha bcrypt correta + flag de confirmação +
rate-limit (3/h por usuário+IP). Wipe + insert em
prisma.$transaction({ timeout: 120s }). Checksum inválido → 422 antes
de qualquer escrita. Audit BACKUP_RESTORE ao final com contagens.
Veja backup-restore.md para a ordem de wipe/restore e garantias.
GET /api/cron/reminders
Authorization: Bearer <CRON_SECRET>
Requer: Bearer token (CRON_SECRET do .env), comparado timing-safe.
O que faz:
dueDate em ≤ 3 dias ou já vencidos.deadline em ≤ 2 dias ou já vencidas.notify("PAYMENT_DUE"|"PAYMENT_OVERDUE"|"TASK_DUE"|"TASK_OVERDUE")
por email e WhatsApp (destinatários: equipe — ADMIN/GROOM/BRIDE/PLANNER).EventSettings.rsvpReminderEnabled
está ligado, cutuca convidados/grupos ainda INVITED convidados há ≥ rsvpReminderDays
(padrão 7), com contato alcançável (telefone do grupo ou, na falta, do 1º integrante;
senão e-mail). Dispara RSVP_REMINDER / RSVP_REMINDER_GROUP com o link de RSVP.
Deduplica por telefone (grupo tem prioridade) e respeita a idempotência por dia.NotificationLog (idempotente por dia: kind + refType + refId + data BRT).Retorna:
{
"ok": true,
"checked": { "payments": 12, "tasks": 8 },
"sent": { "email": 6, "whatsapp": 6 },
"skipped": { "alreadyToday": 14, "noChannel": 0 }
}
Erros:
401 se Bearer inválido ou ausente.GET /api/cron/cleanup-files
Authorization: Bearer <CRON_SECRET>
Requer: Bearer token (CRON_SECRET do .env), comparado timing-safe.
Tem rate limit por IP (5/min).
O que faz:
Attachment com deletedAt há mais de 30 dias
(apaga o arquivo em disco e o registro).Attachment
correspondente).⚠️ Sem este cron os anexos soft-deletados nunca são apagados do disco — o diretório de uploads cresce indefinidamente. Agende-o (ex.: diário). Veja deploy.md.
Retorna:
{
"ok": true,
"summary": {
"softDeletedHardRemoved": 3,
"orphanFilesRemoved": 1,
"errors": 0
}
}
Erros:
401 se Bearer inválido ou ausente.429 se o rate limit por IP for excedido.500 se CRON_SECRET não estiver configurado ou falha geral no cleanup.POST /api/files
GET /api/files/[id]
Requer: sessão válida.
Body: multipart/form-data com:
file: o arquivo (limite 25 MB).ownerType: Vendor |
Contract |
Venue (string). |
ownerId: id da entidade dona.Retorna: { id, filename, mimeType, size, storagePath } do Attachment
criado.
Retorna: o binário do arquivo (Content-Disposition: inline ou
attachment dependendo do MIME).
⚠️ Validações importantes ao implementar/alterar:
- tamanho máximo
- allowlist de MIME
- detecção de magic bytes
- escopo de acesso (cada arquivo só visível ao usuário/projeto)
GET /rsvp/[token]
POST /rsvp/[token] (via Server Action)
Requer: apenas o rsvpToken (cuid) — não exige login.
Comportamento:
rsvpStatus, plusOnesConfirmed, dietary,
rsvpRespondedAt.Para regenerar tokens (se algum vazou), use o Prisma Studio ou um script:
import { prisma } from "@/lib/prisma";
import { cuid } from "...";
await prisma.guest.update({
where: { id: guestId },
data: { rsvpToken: cuid() },
});
Atualmente o sistema não recebe webhooks externos. Caso futuro:
Quaisquer webhooks devem:
X-Signature: sha256=<hex>),timingSafeEquals,Toda resposta de erro segue:
{ "error": "Mensagem amigável", "code": "OPTIONAL_CODE" }
| Status | Significado |
|---|---|
| 401 | Não autenticado |
| 403 | Autenticado mas sem permissão |
| 404 | Recurso não encontrado |
| 409 | Conflito (ex.: email já existe) |
| 422 | Validação falhou (Zod) |
| 429 | Rate limit excedido |
| 500 | Erro interno |
Não há CORS configurado — o sistema é projetado para ser acessado pelo mesmo
domínio. Se você precisar de um cliente cross-origin (app mobile, por
exemplo), configure next.config.ts cuidadosamente.
Não há v1/v2 na URL. Mudanças de contrato são feitas conforme necessário
— o frontend é o único cliente atualmente.
Se isto mudar (clientes terceiros), adote Accept: application/vnd.wedding.v1+json
ou prefixo /api/v1/....