Erros e códigos
Toda resposta de erro usa o mesmo envelope estável:
{
"error": {
"code": "resource_not_found",
"message": "Recurso não encontrado.",
"details": null,
"request_id": "0c1d2e3f-..."
}
}
code: identificador estável em inglês, usável para switch automático;message: mensagem em português, pronta para exibir ao operador;details: objeto opcional com contexto adicional estruturado;request_id: UUID que também vai no headerX-Request-Id. Inclua sempre que abrir um chamado.
O corpo nunca expõe SQLSTATE, nome de tabela, constraint, stack trace, segredo ou dado pessoal. Esses detalhes ficam somente em log interno com redação.
Códigos por status HTTP
| HTTP | code | Quando ocorre |
|---|---|---|
| 400 | invalid_request | Campo inválido, tipo errado, campo extra (mass assignment), UUID malformado, cursor malformado. |
| 400 | invalid_expiration | Validade da chave fora do intervalo permitido. |
| 400 | invalid_scope | Scope solicitado fora da allowlist. |
| 400 | duplicate_scope | Lista de scopes com itens duplicados. |
| 401 | invalid_api_key | Token ausente, malformado, com hash divergente, revogado ou expirado. |
| 403 | insufficient_scope | Chave válida, mas sem o scope exigido pela rota. |
| 403 | key_management_forbidden | Tentativa de administrar chave sem ser o perfil clinica proprietário. |
| 404 | resource_not_found | Recurso inexistente ou de outra clínica. A resposta é a mesma para não permitir enumeração. |
| 409 | version_conflict | expected_updated_at não bate com a versão atual do recurso. |
| 409 | idempotency_conflict | Mesma Idempotency-Key reusada com payload diferente. |
| 409 | duplicate_contact | Telefone já existe para a mesma clínica. |
| 409 | contact_has_history | DELETE /contacts/{id} bloqueado por dependências (mensagens, agendamentos, follow-up etc). |
| 409 | stage_not_empty | Excluir etapa ocupada sem move_contacts_to_stage_id. |
| 409 | duplicate_followup_sequence | Sequência de template repetida na campanha. |
| 409 | tracking_link_conflict | Domínio + slug já existe para a clínica. |
| 409 | user_already_exists | Convite para e-mail já cadastrado. |
| 409 | last_clinic_owner | Desativar o último perfil clinica ativo. |
| 409 | active_key_limit_reached | Cinco chaves ativas simultâneas atingidas. |
| 409 | duplicate_tag | Tag com mesmo nome (case-insensitive) já existe. |
| 409 | tag_in_use | Excluir tag em uso sem replacement_tag_id. |
| 409 | duplicate_service | Serviço ativo com mesmo nome já existe. |
| 413 | payload_too_large | Body JSON maior que 256 KiB. |
| 415 | unsupported_media_type | Ausência de Content-Type: application/json em mutação. |
| 422 | business_rule_violation | Regra de negócio violada (janela de follow-up, preços de serviço, ordenação de etapas etc). |
| 429 | rate_limit_exceeded | Limite por minuto atingido. Vem com Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset. |
| 500 | internal_error | Erro não categorizado. Já ficou registrado com o request_id. |
| 500 | database_operation_failed | Falha no banco sem mapeamento específico. |
| 500 | invalid_database_response | Resposta do banco fora do contrato esperado. |
Tratamento recomendado
- 400/422: corrigir o payload e tentar de novo. Não retentarautomaticamente.
- 401: revogar/perdeu a chave? Gerar outra. Não retentar com a mesma chave.
- 403: o scope necessário não foi concedido. Inclua o scope pelo painel antes de tentar de novo.
- 404: tratar como "não existe" sem distinguir "não é meu".
- 409 version_conflict: buscar a versão atual, reconciliar, tentar de novo. Sempre passa
expected_updated_atem mutações de recursos comupdated_at. - 409 idempotency_conflict: gerar outra
Idempotency-Key. - 429: aguardar
Retry-Aftercom backoff e jitter. Não retentar imediatamente. - 5xx: retentar com backoff exponencial e jitter. Insistir poucas vezes. Se persistir, abra chamado com o
request_id.
Idempotência e rate limit
Detalhes sobre Idempotency-Key, expected_updated_at, cursores e limites estão em paginação e idempotência.