Tratamento de Erros
Todos os erros da API seguem o mesmo formato base, o ApiError, independentemente do endpoint, método HTTP ou status. Isso permite tratamento programático sem parsing de mensagens.
Versões anteriores retornavam erros de validação como dicionário {"field": ["mensagem"]} e itens de sync com falha como string em error. Agora toda resposta de erro traz um array errors[] de objetos ApiError no mesmo formato.
Como funciona
Toda resposta de erro contém um array errors[]. Cada item desse array é um ApiError que carrega no mínimo dois campos: type (categoria programática) e message (texto descritivo).
{
"success": false,
"message": "Turma 'ALG001-2099.1' não encontrada.",
"errors": [
{
"type": "not_found",
"message": "Turma 'ALG001-2099.1' não encontrada.",
"entity": "enrollment",
"code": "ALG001-2099.1"
}
]
}
Os campos extras (entity, code, field, rule, etc.) variam conforme o type. A tabela abaixo é a principal ferramenta de decisão: olhe o type, identifique a categoria e os campos esperados.
type | HTTP | Quando acontece |
|---|---|---|
validation_failed | 422 | Schema do request inválido (campo obrigatório ausente, formato errado) |
code_not_found | sync (200/207/422) ou 404 | Code referenciado não existe (ex: turma com subject_code inexistente) |
constraint_violation | sync (200/207/422) ou 422 | Regra de negócio violada (ex: turma sem nenhum curso vinculado) |
not_found | 404 | Recurso buscado por code não existe |
authentication_failed | 401 ou 403 | API Key ausente, inválida, desativada ou com scope insuficiente |
rate_limit_exceeded | 429 | Limite de requisições por minuto atingido |
internal | sync (200/207/422) ou 500 | Falha inesperada do servidor. Em sync, o item falhado vai para data.errors[]. Fora de sync, vira HTTP 500 |
Em endpoints de sincronização (
*/sync), o status HTTP reflete o resultado do lote: 200 se todos os itens passaram, 207 se alguns passaram e outros falharam, 422 se nenhum item foi persistido. Veja Status HTTP do sync em lote.
Em que parte da resposta os erros aparecem
A localização do errors[] depende do cenário. Há dois lugares possíveis:
Na raiz da resposta: o request inteiro falhou. Vale para validação (422), 404, single-item (422), autenticação (401/403) e rate limit (429).
Em data.errors[]: o request foi aceito e processado item a item (sync em lote). Cada item que falhou vira um objeto com index, code e seu próprio errors[]. O status HTTP varia conforme o resultado do lote (200/207/422) e success é true quando ao menos um item foi persistido. Veja Status HTTP do sync em lote.
Exemplo de falha em endpoint single (errors na raiz)
{
"success": false,
"message": "Turma 'ALG001-2025.1' ou aluno 'ALU2024999' não encontrado.",
"errors": [
{
"type": "not_found",
"message": "Turma 'ALG001-2025.1' ou aluno 'ALU2024999' não encontrado.",
"entity": "enrollment",
"code": "ALG001-2025.1"
}
]
}
Status HTTP do sync em lote
Endpoints de sincronização (*/sync) processam cada item de forma independente, então o status HTTP reflete o resultado do lote como um todo:
| Situação | HTTP | success |
|---|---|---|
| Todos os itens passaram | 200 | true |
| Alguns passaram e outros falharam (parcial) | 207 | true |
| Houve falhas e nenhum item foi persistido | 422 | false |
Em todos os casos, a resposta traz data.created, data.updated, data.failed e, quando há falhas, data.errors[] com o detalhe de cada item. Independente do status, sempre verifique data.failed para saber quais itens não passaram. Não trate apenas 200 como sucesso: um 207 significa que parte do lote foi sincronizada.
Sync com falhas parciais
Quando um endpoint de sincronização processa um batch, cada item é tratado individualmente. Os que dão certo entram em created ou updated; os que falham entram em data.errors[]. Uma falha parcial retorna HTTP 207 (success: true); se nada for persistido, retorna 422 (success: false).
{
"success": true,
"data": {
"created": 8,
"updated": 1,
"failed": 1,
"errors": [
{
"index": 2,
"code": "ALG001-2025.1",
"errors": [
{
"type": "code_not_found",
"message": "Professores não encontrado(a)s: PROF999, PROF888",
"entity": "professor",
"missing": ["PROF999", "PROF888"]
}
]
}
]
}
}
index é a posição do item no array enviado (0-based) e code é o identificador do item.
Dois níveis de errors
Existem dois arrays chamados errors em níveis diferentes:
data.errors[](externo): a lista de itens do batch que falharam. Sempre tem o mesmo tamanho quefailed.data.errors[N].errors[](interno): a lista de falhas daquele item específico. Um único item pode acumular múltiplos erros quando tem várias referências inválidas ao mesmo tempo.
Em vez de reportar uma falha por vez, a API acumula tudo e devolve junto. Acumulação por item se aplica a:
- Turma:
subject_code+course_codes+professor_codes+student_codes(até 4 erros) - Gestor de Área:
area_codes+unit_code(até 2 erros) - Curso:
area_code+unit_code(até 2 erros)
Batch misto: sucessos e falhas convivem
O exemplo abaixo envia 3 itens onde o 1º e o 3º falham e o 2º é criado normalmente:
{
"success": true,
"data": {
"created": 1,
"updated": 0,
"failed": 2,
"errors": [
{
"index": 0,
"code": "GEST_A",
"errors": [
{ "type": "code_not_found", "message": "Áreas não encontrado(a)s: AX, AY", "entity": "area", "missing": ["AX", "AY"] },
{ "type": "code_not_found", "message": "Unidade 'UX' não encontrado(a).", "entity": "unit", "code": "UX" }
]
},
{
"index": 2,
"code": "GEST_C",
"errors": [
{ "type": "code_not_found", "message": "Unidade 'UY' não encontrado(a).", "entity": "unit", "code": "UY" }
]
}
]
}
}
Note que index: 1 não aparece porque foi sucesso. Use index para mapear cada falha de volta ao item original que você enviou.
Cenários comuns
Os blocos abaixo mostram um exemplo de cada categoria de erro.
Referência completa de campos
Cada type carrega um conjunto específico de campos contextuais além de type e message. A tabela é a referência canônica.
type | Campos contextuais |
|---|---|
validation_failed | field (dot-notation) |
code_not_found (um code) | entity, code |
code_not_found (múltiplos) | entity, missing[] |
constraint_violation | entity, rule, invalid_codes[] (quando aplicável) |
not_found | entity, code |
authentication_failed (scope) | rule, required_scope, client_scope |
authentication_failed (auth) | rule |
rate_limit_exceeded | rule, limit, retry_after |
internal | exception_class |
Regras de negócio (constraint_violation)
O campo rule permite identificar a regra violada sem parsear a mensagem.
rule | Endpoint | Significado |
|---|---|---|
at_least_one_professor | /enrollments/sync | Item sem professor_codes |
at_least_one_course | /enrollments/sync, /coordinators/sync | Item sem course_codes |
at_least_one_area | /area-managers/sync | Item sem area_codes |
enrollment_without_courses | /enrollments/sync, POST /enrollments/{code}/students/{code} | Turma sem cursos vinculados |
user_suspended | /sso/generate-token | Usuário com suspended_at não nulo |
Regras de autenticação e rate limit
rule | HTTP | Significado |
|---|---|---|
unauthenticated | 401 | Header Authorization ausente |
invalid_api_key | 401 | API Key não corresponde a nenhum cliente |
api_key_disabled | 403 | API Key existe mas está desativada no painel |
insufficient_scope | 403 | Scope da API Key não permite a operação |
rate_limit_exceeded | 429 | Limite de requisições por minuto atingido |