A API usa códigos HTTP padrão combinados com um envelope de erro estruturado. Todos os erros seguem o mesmo formato.
{
"error": {
"code": "NOT_FOUND",
"message": "Estudante não encontrado.",
"details": {
"id": "550e8400-e29b-41d4-a716-446655440000"
}
}
}
| Campo | Tipo | Descrição |
|---|
error.code | string | Código de erro padronizado (ver tabela abaixo) |
error.message | string | Mensagem legível para humanos em português |
error.details | object (opcional) | Detalhes adicionais (ex: campos de validação, IDs) |
| Code | HTTP | Quando ocorre |
|---|
MISSING_API_KEY | 401 | Nenhuma API key enviada |
INVALID_API_KEY | 401 | API key inexistente, inativa, ou formato inválido |
EXPIRED_API_KEY | 401 | API key expirada (expiresAt no passado) |
REVOKED_API_KEY | 401 | API key revogada pelo administrador |
INSUFFICIENT_SCOPE | 403 | A key não tem o scope necessário para o endpoint |
BRANCH_SCOPE_MISMATCH | 403 | Key BRANCH tentou acessar outra branch via x-branch-id |
| Code | HTTP | Quando ocorre |
|---|
BRANCH_REQUIRED | 400 | Key BUSINESS sem header x-branch-id em endpoint branch-scoped |
INVALID_BRANCH | 404/400 | Branch não existe, não pertence ao Business, ou está inativa |
| Code | HTTP | Quando ocorre |
|---|
VALIDATION_ERROR | 400 | Dados enviados não passaram na validação (campos obrigatórios faltando, valores inválidos, etc.) |
BAD_REQUEST | 400 | JSON inválido ou requisição malformada |
NOT_FOUND | 404 | Recurso solicitado não existe |
METHOD_NOT_ALLOWED | 405 | Método HTTP não suportado pela rota |
CONFLICT | 409 | Conflito de estado (ex: webhook já existe para esta branch) |
| Code | HTTP | Quando ocorre |
|---|
RATE_LIMITED | 429 | Limite de requisições por minuto excedido |
Veja Rate Limits para detalhes.
| Code | HTTP | Quando ocorre |
|---|
IDEMPOTENCY_CONFLICT | 409 | Idempotency-Key reutilizada com payload diferente |
Veja Idempotência para detalhes.
| Code | HTTP | Quando ocorre |
|---|
INTERNAL_ERROR | 500 | Erro inesperado no servidor |
Quando a validação falha, details contém a lista de issues:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Erro de validação nos dados enviados.",
"details": [
{
"code": "too_small",
"path": ["name"],
"message": "String deve conter pelo menos 1 caractere(s)"
}
]
}
}
| HTTP | Significado |
|---|
| 200 | Sucesso (GET, PUT) |
| 201 | Criado (POST) |
| 204 | Sem conteúdo (DELETE) |
| 400 | Erro de validação ou requisição inválida |
| 401 | Não autenticado |
| 403 | Autenticado mas sem permissão |
| 404 | Recurso não encontrado |
| 405 | Método não permitido |
| 409 | Conflito |
| 429 | Rate limit excedido |
| 500 | Erro interno |
async function callGdreduApi (url, options) {
const res = await fetch(url, options)
const body = await res.json().catch(() => null)
if (!res.ok) {
const err = body?.error
if (err?.code === 'RATE_LIMITED') {
// Implementar backoff e retry
const retryAfter = parseInt(res.headers.get('Retry-After') || '60', 10)
await sleep(retryAfter * 1000)
return callGdreduApi(url, options)
}
if (err?.code === 'INVALID_API_KEY' || err?.code === 'EXPIRED_API_KEY') {
// Notificar que a key precisa ser renovada
throw new Error(`Problema de autenticação: ${err.message}`)
}
throw new Error(`${err?.code || 'UNKNOWN'}: ${err?.message || res.statusText}`)
}
return body
}