Catálogo de códigos de erro
Para cada código retornado pela API: quando ocorre e como
resolver. Em qualquer chamado de suporte, inclua o
meta.request_id da resposta — ele permite
ao suporte rastrear sua requisição específica em segundos.
A §5 abaixo cobre os códigos específicos dos POST
e PATCH de escrita (Agenda — Fase 4b;
Pacientes — Fase 3*): idempotência, conflitos e
duplicidade.
1. Status HTTP
| HTTP | Significado | Quando ocorre |
|---|---|---|
200 |
Sucesso | Requisição processada normalmente |
201 |
Created | POST /v1/agenda/agendamentos ou POST /v1/pacientes bem-sucedido. Header Location: /v1/<recurso>/{id}. |
302 |
Redirect | Endpoints /arquivo redirecionam para presigned S3 (válida 300s) |
400 |
Bad Request | Parâmetro inválido (VAL_INVALID_PARAM ou VAL_BACKEND) — inclui Idempotency-Key ausente/inválida nos POST/PATCH de escrita |
401 |
Unauthorized | Falha de autenticação HMAC (códigos AUTH_*) |
403 |
Forbidden | Sem permissão ou escopo insuficiente |
404 |
Not Found | Endpoint ou recurso não encontrado (inclui recursos ocultos pelo auto-filtro de médico, mesmo em POST de agenda; em PATCH /v1/pacientes/{id} sinaliza paciente inexistente no tenant) |
405 |
Method Not Allowed | Endpoint só aceita o verbo declarado; outras tentativas devolvem 405 |
409 |
Conflict | CONCURRENT_REQUEST — outra requisição em andamento com a mesma Idempotency-Key. Header Retry-After: 2. |
422 |
Unprocessable Entity | Validação semântica do backend, conflito de idempotência, duplicidade ou resolução de alias (1.1.0). Códigos: IDEMPOTENCY_CONFLICT, CPF_ALREADY_EXISTS, MEDICAL_RECORD_ALREADY_EXISTS, PACIENTES_UNIFICACAO_MESMO_ID, CONFLICT, DUPLICATE, BUSINESS_RULE, INVALID_STATE, RESOLUTION_NOT_FOUND, RESOLUTION_AMBIGUOUS, PARAM_CONFLICT. |
429 |
Too Many Requests | Rate limit excedido — reduza concorrência e use backoff exponencial. |
500 |
Internal Server Error | Erro interno do gateway (raríssimo; abra chamado). |
503 |
Bad Gateway | Erro no processamento da requisição pelo backend Sivoe. |
504 |
Gateway Timeout | Backend Sivoe não respondeu dentro de 30 segundos. |
2. Códigos AUTH_* (autenticação HMAC)
Todos retornam 401 exceto AUTH_INSUFFICIENT_SCOPE (403) e OPERATION_NOT_ALLOWED (403).
| Código | Quando ocorre | Como resolver |
|---|---|---|
AUTH_MISSING_HEADER |
Falta X-API-Key, X-Signature ou X-Timestamp |
Envie os 3 headers obrigatórios em toda requisição |
AUTH_INVALID_API_KEY_FORMAT |
api_key não bate com svp_live_[0-9a-f]{32} |
Confira que a chave foi colada inteira (40 chars), sem espaços |
AUTH_INVALID_TIMESTAMP |
X-Timestamp não é epoch numérico |
Envie inteiros (sem casas decimais) — em Python, int(time.time()) |
AUTH_TIMESTAMP_EXPIRED |
X-Timestamp fora da janela ±300s vs servidor |
Ative NTP; em PowerShell 5.1, use [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() |
AUTH_KEY_NOT_FOUND |
api_key desconhecida na tabela master |
Verifique se a chave foi gerada para esta clínica; pode ter sido revogada |
AUTH_TENANT_OPEN_FAILED |
Falha interna ao resolver o tenant | Abra chamado com o meta.request_id da resposta |
AUTH_KEY_REVOKED |
Chave revogada ou inativa | Solicite ao suporte Sivoe (contato@roomtec.com.br) a reativação ou geração de uma nova chave |
AUTH_KEY_LEGACY |
Chave em formato legado, sem segredo armazenado de forma compatível | Solicite ao suporte Sivoe a regeneração do segredo |
AUTH_INVALID_SIGNATURE |
HMAC calculado pelo servidor difere do enviado | Verifique a fórmula em autenticação §2: query string em path_q; body byte a byte; \n (LF) entre uri e body |
AUTH_INSUFFICIENT_SCOPE |
Chave válida mas sem o escopo do endpoint | Escopos disponíveis (6): pacientes:read, pacientes:write (Fase 3*), exames:read, laudos:read, agenda:read, agenda:write. Solicite o escopo ao suporte Sivoe |
SERVICE_UNAVAILABLE |
Backend rejeitou a autenticação interna do gateway | Geralmente transitório; tente novamente. Persistindo, abra chamado |
OPERATION_NOT_ALLOWED |
Backend negou a operação para o perfil associado à chave | Abra chamado para revisão da permissão da sua chave |
3. Códigos VAL_* e RES_* (validação / recurso)
| Código | HTTP | Quando ocorre | Como resolver |
|---|---|---|---|
VAL_INVALID_PARAM |
400 |
Query/path param fora da whitelist | Veja error.details.field e ajuste — cada endpoint documenta seus limites na referência detalhada |
VAL_BACKEND |
400 |
Backend rejeitou o payload (validação semântica) | Inspecione error.message — geralmente campo obrigatório ausente ou inválido |
RES_NOT_FOUND |
404 |
Recurso inexistente ou oculto pelo auto-filtro de médico | Confirme o ID; se a chave tem medico_id, o recurso pode estar fora do filtro (ver FAQ) |
RES_ARQUIVO_NAO_DISPONIVEL |
404 |
Recurso existe mas o PDF S3 correspondente não foi anexado | Aguarde o laudo/exame ser finalizado pela clínica — não é erro técnico |
RES_ARQUIVO_S3_FALHA |
500 |
Falha temporária ao gerar presigned S3 | Retry com backoff exponencial (3 tentativas, jitter 100–500ms) |
4. Códigos BACKEND_* (problemas no backend Sivoe)
| Código | HTTP | Quando ocorre | Como resolver |
|---|---|---|---|
SERVICE_UNAVAILABLE |
503 |
Backend retornou HTTP 500 não-classificado | Abra chamado com meta.request_id |
SERVICE_UNAVAILABLE |
503 |
cURL falhou ao conectar no backend (rede / DNS / TLS) | Retry com backoff; se persistir > 5 min, abra chamado urgente |
SERVICE_TIMEOUT |
504 |
Timeout cURL > 30s no backend | Retry; pode indicar pico de carga ou query lenta — informe ao suporte se recorrente |
SERVICE_UNAVAILABLE |
503 |
Backend devolveu JSON inválido ou corpo vazio | Abra chamado com meta.request_id — pode ser bug do backend |
SERVICE_UNAVAILABLE |
503 |
Backend devolveu um código de status não esperado pelo gateway | Bug do backend — abra chamado para a Sivoe |
5. Códigos de escrita (Agenda — Fase 4b; Pacientes — Fase 3*)
Aparecem nos 3 POST de agenda
(criar, cancelar,
reagendar) e nos 3 endpoints de escrita de
paciente (POST /v1/pacientes,
PATCH /v1/pacientes/{id} e
POST /v1/pacientes/unificar). Contrato completo
de idempotência em
referência
§ Idempotência. CPF_ALREADY_EXISTS e
MEDICAL_RECORD_ALREADY_EXISTS são exclusivos da Fase 3*
(Pacientes); PACIENTES_UNIFICACAO_MESMO_ID é
exclusivo da unificação.
| Código | HTTP | Quando ocorre | Como resolver |
|---|---|---|---|
CONCURRENT_REQUEST |
409 |
Outra requisição com a mesma Idempotency-Key está em flight no momento. Header Retry-After: 2 presente. |
Aguarde o tempo do Retry-After e refaça a requisição com a mesma key e o mesmo body — o servidor devolverá o replay da resposta original (ou 503 replay, se o backend falhou; ver referência § Idempotência). Use backoff com jitter (100–500 ms) ao serializar requests no cliente. |
IDEMPOTENCY_CONFLICT |
422 |
Mesma Idempotency-Key reusada com body diferente. O servidor recusa para garantir integridade — não há resposta "parcial". |
Gere uma nova Idempotency-Key (UUID v4) ou reenvie com o body idêntico ao original (para receber o replay). Verifique se o cliente está mutando o payload entre tentativas. |
CONFLICT |
422 |
Horário ocupado: já existe outro agendamento no mesmo médico, mesma data e mesma hora (sem encaixe). Ocorre em criar e reagendar. |
Escolha outro horário (use GET /v1/agenda/slots para listar slots livres do procedimento) ou marque encaixe: true se for caso clínico aplicável. |
DUPLICATE |
422 |
Mesmo paciente já tem agendamento no mesmo dia/hora (regra anti-overbooking do paciente). Ocorre em criar. |
Confirme com a clínica se o paciente realmente quer um segundo procedimento na mesma janela; em caso positivo, ajuste para outro horário. |
BUSINESS_RULE |
422 |
Restrição clínica/operacional: convênio bloqueado para a agenda, limite de agendamentos atingido, idade do paciente fora do escopo do procedimento, ou procedimento sem duração configurada — nesse caso o backend não consegue inferir hora_fim. |
Veja error.message para a causa específica. Procedimento sem duração configurada: solicite à clínica que vincule o procedimento à agenda com o tempo de duração apropriado. |
INVALID_STATE |
422 |
Agendamento está em status terminal — cancelado ou finalizado — e não aceita transição (não pode ser cancelado novamente nem reagendado). Ocorre em cancelar e reagendar. |
Crie um novo agendamento via POST /v1/agenda/agendamentos em vez de reagendar/cancelar. Para auditar a transição, consulte o histórico via GET /v1/agenda/agendamentos/{id}. |
CPF_ALREADY_EXISTS |
422 |
CPF já cadastrado em outro paciente. Ocorre em POST /v1/pacientes (Fase 3*) e em PATCH /v1/pacientes/{id} quando se tenta alterar o CPF para o de outro paciente. PATCH com o mesmo CPF do próprio paciente NÃO dispara este erro — é tratado como idempotente. |
details.paciente_id_existente e details.nome identificam o paciente já cadastrado. Caminho de upsert manual: faça GET /v1/pacientes/<id_existente> e use PATCH para atualizar o registro existente em vez de criar um novo. |
MEDICAL_RECORD_ALREADY_EXISTS |
422 |
numero_prontuario já em uso por outro paciente. Ocorre em POST/PATCH /v1/pacientes da Fase 3* quando o cliente envia o campo. Em POSTs sem numero_prontuario, o backend auto-gera e o erro não ocorre. |
details.paciente_id_existente e details.nome trazem o paciente já dono daquele prontuário. Opções: omitir o campo (auto-gera novo) ou escolher outro número. |
PACIENTES_UNIFICACAO_MESMO_ID |
422 |
Exclusivo de POST /v1/pacientes/unificar: paciente_id_principal é igual a paciente_id_duplicado — não faz sentido unificar um paciente consigo mesmo. details.field = "paciente_id_duplicado". |
Envie dois IDs diferentes. O principal é o que será mantido; o duplicado é o que será excluído. Confirme cada ID com GET /v1/pacientes/{id} antes de unificar — a operação é irreversível. |
409 e 422 CONCURRENT_REQUEST
/IDEMPOTENCY_CONFLICT separadamente dos
422 BACKEND_*, CPF_ALREADY_EXISTS e
MEDICAL_RECORD_ALREADY_EXISTS. Os primeiros são
problemas do protocolo de idempotência (retry com
mesma key, ou gerar nova key); os segundos são problemas
de regra de negócio (mudar o body, escolher outro
horário, fazer GET + PATCH em vez de POST).
6. Códigos de resolução de alias (1.1.0)
Disponíveis na release 1.1.0, no
POST /v1/exames. Quando você envia
identificadores de negócio (aliases —
paciente_cpf, convenio_nome,
procedimento_abreviacao,
medico_exame_crm etc.) em vez do
*_id interno, o gateway resolve cada alias
para o *_id correspondente. Se a resolução
falha, devolve um 422 acionável. Todos os 3
códigos são consolidados num único
envelope (veja §7) — o gateway não aborta no primeiro
erro; reporta todos os campos de uma vez.
| Código | HTTP | Quando ocorre | Como resolver |
|---|---|---|---|
RESOLUTION_NOT_FOUND |
422 |
O alias informado não casou nenhum registro do tenant (busca por igualdade exata). Ex.: "Paciente nao localizado para o CPF informado." | Confira o valor do alias. Use a fonte indicada em error.details.lista[].fonte (ex.: GET /v1/pacientes?q=) para descobrir o valor correto, ou envie diretamente o *_id. |
RESOLUTION_AMBIGUOUS |
422 |
O alias casou 2 ou mais registros — o gateway não adivinha qual. Ex.: "Encontrado mais de um convenio com o mesmo nome." | Veja error.details.candidatos[] (máx. 10, com id e nome), escolha o registro certo e envie o *_id dele no lugar do alias. |
PARAM_CONFLICT |
422 |
Você enviou o *_id e um alias da mesma FK, e eles apontam para registros diferentes. Ex.: "convenio_id e convenio_nome apontam para registros diferentes." |
O item da lista traz id_informado (o que você mandou no *_id) e id_resolvido (o que o alias resolveu). Envie apenas um dos dois, ou corrija para que ambos coincidam. |
error.code do topo segue a
ordem PARAM_CONFLICT >
RESOLUTION_AMBIGUOUS >
RESOLUTION_NOT_FOUND, e a
error.message vira
"N campos precisam de atencao." (com N = nº de
campos com problema). Com um único erro, a
message é a mensagem específica daquele campo.
Envelope consolidado (M4)
O error.details agrega todos os campos que
falharam:
details.field— ofielddo primeiro item da lista (atalho).details.lista[]— um item por campo com problema. Cada item carregafield,codeemsg(a mensagem do campo — émsg, nãovalue). Conforme o caso, também:candidatos[](emRESOLUTION_AMBIGUOUS),id_informado+id_resolvido(emPARAM_CONFLICT), e os campos de enriquecimentofonte(onde descobrir o valor),alias_aceito(alias que evita mandar o*_id) evalores_aceitos(enum, para campos de domínio fechado comoprioridadeourequer_laudo).details.candidatos— atalho no topo com os candidatos; só aparece quando algum item éRESOLUTION_AMBIGUOUS.
{
"data": null,
"meta": { "request_id": "1a2b3c4d5e6f", "duracao_ms": 47 },
"error": {
"code": "RESOLUTION_AMBIGUOUS",
"message": "2 campos precisam de atencao.",
"details": {
"field": "convenio_nome",
"lista": [
{ "field": "convenio_nome", "code": "RESOLUTION_AMBIGUOUS",
"msg": "Encontrado mais de um convenio com o mesmo nome.",
"candidatos": [ { "id": 31, "nome": "UNIMED" }, { "id": 88, "nome": "UNIMED" } ],
"fonte": "GET /v1/convenios", "alias_aceito": "convenio_nome" },
{ "field": "medico_exame_crm", "code": "RESOLUTION_NOT_FOUND",
"msg": "Medico de exame nao localizado para o CRM informado.",
"fonte": "GET /v1/medicos?papel=EXAME", "alias_aceito": "medico_exame_crm" }
],
"candidatos": [ { "id": 31, "nome": "UNIMED" }, { "id": 88, "nome": "UNIMED" } ]
}
}
}
*_id? Em vez de
tratar esses erros reativamente, use os aliases e/ou os
defaults do tenant. Veja
FAQ — "Não sei o paciente_id /
convenio_id / medico_id" e a
referência detalhada de
endpoints.
400 (validação) vs 422 (resolução) no POST /v1/exames
Os dois caminhos de erro do POST /v1/exames
usam o mesmo envelope que ensina
(details.field + details.lista[] com
field/msg e o enriquecimento
fonte/alias_aceito) — diferem
apenas no status e no code.
Em particular, o 400 VAL_INVALID_PARAM
não é um 400 "seco": quando você omite um
campo obrigatório que não tem default no tenant, a
details.lista[] traz fonte e
alias_aceito de cada campo faltante.
| HTTP / code | Quando ocorre | Item da details.lista[] |
|---|---|---|
400 VAL_INVALID_PARAM / VAL_FILE_TOO_LARGE |
Formato inválido (CPF != 11 dígitos, enum fora de domínio, campo bloqueado, arquivo > 20 MB) ou campo obrigatório ausente sem default no tenant. | field, msg e (quando aplicável) fonte, alias_aceito, valores_aceitos. |
422 RESOLUTION_NOT_FOUND / RESOLUTION_AMBIGUOUS / PARAM_CONFLICT |
Resolução semântica de alias: alias não encontrado, ambíguo, ou *_id diverge do alias. |
Itens trazem ainda code, candidatos[] (ambíguo) ou id_informado+id_resolvido (conflito). |
Exemplo de 400 com a lista que ensina
(obrigatório omitido, tenant sem default):
{
"data": null,
"meta": { "version": "1.1.0", "timestamp": "2026-06-19T10:00:00-03:00" },
"error": {
"code": "VAL_INVALID_PARAM",
"message": "2 campos precisam de atencao.",
"details": {
"field": "unidade_id",
"lista": [
{ "field": "unidade_id", "msg": "Obrigatorio (sem default no tenant).",
"fonte": "GET /v1/unidades", "alias_aceito": "unidade_nome" },
{ "field": "medico_exame_id", "msg": "Obrigatorio (sem default no tenant).",
"fonte": "GET /v1/medicos?papel=EXAME", "alias_aceito": "medico_exame_crm" }
]
}
}
}
7. Envelope padrão de erro
Toda resposta de erro segue exatamente este formato.
data sempre null em erro;
error.code é o identificador estável;
error.message é texto em português (PT-BR sem
acentos especiais para evitar problemas de encoding);
error.details pode trazer info estruturada
(ex.: field no VAL_INVALID_PARAM).
{
"data": null,
"meta": {
"request_id": "a1b2c3d4e5f6",
"duracao_ms": 12
},
"error": {
"code": "AUTH_INVALID_SIGNATURE",
"message": "Assinatura HMAC invalida.",
"details": null
}
}
meta.request_id?
Anote-o e inclua em qualquer ticket de suporte. Esse mesmo
identificador é registrado no log de auditoria do gateway
— permite ao suporte rastrear sua requisição
específica em segundos.
8. Headers padronizados em toda resposta
Independente de status, toda resposta carrega:
X-Api-Version: v1
X-Content-Type-Options: nosniff
Cache-Control: no-store
X-Api-Versionpermite ao integrador detectar mudança de versão (você pode pinning seu cliente).X-Content-Type-Options: nosniffimpede MIME sniffing — defesa em profundidade.Cache-Control: no-storesinaliza explicitamente que respostas autenticadas não devem ser cacheadas em proxies intermediários.