Códigos de erro

HTTP · AUTH · VAL · RES · BACKEND · idempotência

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.
Dica para o cliente HTTP: trate 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.
Severidade do código primário. Quando há vários erros, o 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:

{
  "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" } ]
    }
  }
}
Não sabe o *_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
  }
}
O que fazer com 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