Uso

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 header X-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

HTTPcodeQuando ocorre
400invalid_requestCampo inválido, tipo errado, campo extra (mass assignment), UUID malformado, cursor malformado.
400invalid_expirationValidade da chave fora do intervalo permitido.
400invalid_scopeScope solicitado fora da allowlist.
400duplicate_scopeLista de scopes com itens duplicados.
401invalid_api_keyToken ausente, malformado, com hash divergente, revogado ou expirado.
403insufficient_scopeChave válida, mas sem o scope exigido pela rota.
403key_management_forbiddenTentativa de administrar chave sem ser o perfil clinica proprietário.
404resource_not_foundRecurso inexistente ou de outra clínica. A resposta é a mesma para não permitir enumeração.
409version_conflictexpected_updated_at não bate com a versão atual do recurso.
409idempotency_conflictMesma Idempotency-Key reusada com payload diferente.
409duplicate_contactTelefone já existe para a mesma clínica.
409contact_has_historyDELETE /contacts/{id} bloqueado por dependências (mensagens, agendamentos, follow-up etc).
409stage_not_emptyExcluir etapa ocupada sem move_contacts_to_stage_id.
409duplicate_followup_sequenceSequência de template repetida na campanha.
409tracking_link_conflictDomínio + slug já existe para a clínica.
409user_already_existsConvite para e-mail já cadastrado.
409last_clinic_ownerDesativar o último perfil clinica ativo.
409active_key_limit_reachedCinco chaves ativas simultâneas atingidas.
409duplicate_tagTag com mesmo nome (case-insensitive) já existe.
409tag_in_useExcluir tag em uso sem replacement_tag_id.
409duplicate_serviceServiço ativo com mesmo nome já existe.
413payload_too_largeBody JSON maior que 256 KiB.
415unsupported_media_typeAusência de Content-Type: application/json em mutação.
422business_rule_violationRegra de negócio violada (janela de follow-up, preços de serviço, ordenação de etapas etc).
429rate_limit_exceededLimite por minuto atingido. Vem com Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset.
500internal_errorErro não categorizado. Já ficou registrado com o request_id.
500database_operation_failedFalha no banco sem mapeamento específico.
500invalid_database_responseResposta 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_at em mutações de recursos com updated_at.
  • 409 idempotency_conflict: gerar outra Idempotency-Key.
  • 429: aguardar Retry-After com 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.