FAQ

Perguntas frequentes de integradores

Perguntas frequentes (FAQ)

Respostas curtas e diretas para as dúvidas mais comuns de integradores. Q1–Q12 cobrem autenticação, retry, presigned S3 e auto-filtro. Q13–Q15 cobrem Idempotency-Key e regras específicas dos POST de Agenda — o mesmo contrato vale para os endpoints de escrita de paciente (Fase 3*). Q16–Q17 cobrem dúvidas específicas da escrita de pacientes (CPF duplicado vs concorrência; PATCH RFC 7396). Q18 cobre os aliases e defaults do POST /v1/exames (release 1.1.0) — quando você não tem os *_id internos.

Q1) Por que a 1ª chamada após inatividade demora ~2300ms?

O cache de autenticação interna do gateway expirou. O gateway precisa refazer a autenticação no backend, e essa etapa leva ~1.9–2.0 s. Chamadas subsequentes dentro de 5 minutos usam o cache local e respondem em <800 ms.

Ação: configure o timeout do seu cliente HTTP em no mínimo 5 segundos para absorver a cold call com folga.

Q2) Como rotacionar o api_secret?

Solicite a rotação ao suporte Sivoe em contato@roomtec.com.br. O api_key permanece — só o api_secret muda, e ele é entregue uma única vez por canal seguro. Guarde imediatamente em cofre/HSM.

O cache de autenticação invalida automaticamente quando o secret muda — a primeira requisição após rotação será uma cold call (~2,3 s).

Q3) Minha aplicação web em outro domínio também é bloqueada por CORS?

CORS só se aplica a chamadas feitas a partir de navegadores. Sua aplicação server-to-server (Node, Python, PHP, Go, Java… rodando em servidor) chama a API normalmente, de qualquer lugar — CORS é irrelevante nesse cenário.

A whitelist estrita só vale para chamadas via browser (Try it out, Console HMAC, SPAs). Para esses casos, a API só aceita requisições originadas do portal oficial (https://developer.sivoe.med.br ou http://localhost/developer/ em DEV). Não recomendamos usar HMAC no navegador em produção — chaves devem viver em servidor / vault.

Q4) Qual a diferença entre RES_NOT_FOUND e RES_ARQUIVO_NAO_DISPONIVEL num 404?

Ambos retornam HTTP 404, mas significam coisas distintas:

  • RES_NOT_FOUND: o recurso não existe ou está oculto pelo auto-filtro de médico (não distingue, para não vazar existência).
  • RES_ARQUIVO_NAO_DISPONIVEL: o recurso existe e está acessível, mas o PDF S3 correspondente não foi anexado ainda (laudo pendente, etc.).

No primeiro caso, verifique o ID. No segundo, aguarde — não é erro técnico.

Q5) O auto-filtro de médico me oculta exames — é bug?

Não, é comportamento esperado. Chaves com medico_id definido aplicam:

  • /v1/exames — filtro SIMPLES: vê exames onde o médico é executor, laudante OU solicitante.
  • /v1/laudos — filtro AMPLIADO: laudos do médico + laudos de exames onde ele aparece em qualquer função.

Para visão completa da clínica, solicite ao suporte Sivoe uma chave sem medico_id. Os parâmetros medico_id/medico_laudo_id da query são ignorados silenciosamente quando a chave já tem médico amarrado (para não permitir enumeração).

Q6) A presigned S3 expira em 5 minutos — devo cachear?

Não. A URL retornada em Location: dos endpoints /arquivo tem validade de 300 segundos e tokens AWS-Sig-V4 únicos. Em retry, sempre refaça GET /v1/exames/{id}/arquivo ou GET /v1/laudos/{id}/arquivo para obter uma URL nova.

Path S3 e URL nunca aparecem em log de auditoria — só metadados (exame_id, tipo, expires_in).

Q7) NTP é obrigatório no cliente?

Fortemente recomendado. A janela anti-replay é ±300 segundos. Sem NTP, qualquer deriva de relógio > 5 minutos derruba todas as suas requisições com AUTH_TIMESTAMP_EXPIRED.

  • Linux: chrony ou systemd-timesyncd
  • Windows: serviço w32time (já vem ativo)
  • macOS: System Settings → General → Date & Time → "Set automatically"
  • Docker: o container herda o relógio do host — sincronize o host

Q8) Como obter o request_id de uma requisição que falhou?

Toda resposta (sucesso ou erro) carrega meta.request_id no envelope JSON. Esse mesmo identificador é registrado no log de auditoria do gateway.

{
  "data":  null,
  "meta":  { "request_id": "a1b2c3d4e5f6", "duracao_ms": 12 },
  "error": { "code": "AUTH_INVALID_SIGNATURE", "message": "...", "details": null }
}

Inclua o request_id em qualquer ticket de suporte — o suporte localiza sua chamada em segundos.

Q9) Posso testar a API sem criar uma chave?

Sim! Use a chave sandbox publicada no banner do Try it out do portal. Dados sintéticos, escopos read-only, sem médico. Útil para validar fluxo + assinatura antes de pedir credenciais reais ao admin da clínica.

Não use a chave sandbox em produção. Os dados são fictícios e o ambiente tem rate limit reduzido. Em produção, peça a chave própria da sua clínica.

Q10) Existe SDK oficial em minha linguagem?

Ainda não na v1. SDKs oficiais em Node.js, Python e Go estão no roadmap. Por enquanto, as 4 receitas HMAC nesta documentação cobrem:

Para outras linguagens (Go, Ruby, C#, Java, Kotlin), siga o pseudocódigo da visão geral de autenticação — a fórmula é universal: HMAC-SHA256(payload, secret) em hex lowercase.

Q11) Como reportar um bug ou pedir uma feature?

Envie e-mail para o suporte Sivoe em contato@roomtec.com.br. Inclua no chamado:

  • O meta.request_id da requisição que falhou (Q8).
  • A saída completa do erro (status HTTP + body JSON).
  • Sua versão de cliente HTTP (curl, Python, Node, PHP) e SO.
  • Se possível, um exemplo mínimo reprodutível (curl pronto para colar).

Q12) Por que /v1/exames/{id}/arquivo retorna 302 em vez de streamar o PDF?

Por performance e custo. O gateway gera uma presigned S3 (válida 300s) e redireciona seu cliente direto para o S3, que serve o PDF com a infra da AWS — sem onerar o gateway com transferência de bytes.

Seu cliente HTTP normalmente segue redirects automaticamente (curl com -L, axios por padrão, requests por padrão). Em PHP cURL, garanta CURLOPT_FOLLOWLOCATION => false para não reassinar a presigned com seu HMAC — a presigned já autentica contra o S3.

Q13) O que é Idempotency-Key e por que é obrigatório nos POSTs e PATCHs?

É um header HTTP que identifica unicamente uma operação de escrita. O servidor cacheia a primeira resposta por 24 horas; um retry com a mesma key e o mesmo body devolve a resposta cacheada (replay), não cria um registro duplicado. Contrato vale igual para Agenda (POST /v1/agenda/…) e Pacientes (POST/PATCH /v1/pacientes) — Fase 3*.

Cenário típico: você manda POST /v1/agenda/agendamentos, a rede falha antes da resposta chegar e você não sabe se o backend processou. Sem Idempotency-Key, um retry crio dois agendamentos. Com ela, o servidor reconhece a key, devolve o resultado original e nada duplica.

Use UUID v4 — uuidgen (bash), uuid.uuid4() (Python), crypto.randomUUID() (Node). Uma key por requisição lógica (criar X é uma key, cancelar X é outra). Sem o header → 400 VAL_INVALID_PARAM.

Detalhes completos, incluindo o comportamento de network-error-como-503, em referência § Idempotência.

Q14) Posso reusar a mesma Idempotency-Key?

Sim — desde que o body seja exatamente igual. Reusar com body diferente devolve 422 IDEMPOTENCY_CONFLICT:

  • Retry idêntico (mesma key, mesmo body) → resposta cacheada (replay) com status original preservado. 201 replay continua 201, 200 replay continua 200. Header Idempotency-Replay: true.
  • Mesma key, body diferente422 IDEMPOTENCY_CONFLICT. O servidor recusa para garantir integridade.
  • Nova operação (ex.: cancelar um agendamento já criado) → gere uma nova key.

Cuidado com keys recicladas entre operações: cancelar o 8421 com a mesma key usada pra criá-lo não funciona — o body é diferente.

Q15) Por que paciente_id é obrigatório em GET /v1/agenda/agendamentos?

O caso de uso principal do endpoint é o portal do paciente externo: o integrador busca os agendamentos de um paciente específico para exibir no app/portal de terceiro. Sem paciente_id, a chamada vazaria toda a agenda do tenant — risco de dump de PII e de calendário operacional.

A obrigatoriedade é mitigação anti-dump. Outros filtros (data_inicio, status, unidade_id…) reduzem ainda mais o conjunto retornado, mas o paciente_id permanece a única restrição obrigatória.

Sem ele → 400 VAL_INVALID_PARAM com details.field=paciente_id.

Q16) Por que ao tentar criar paciente com o CPF de outro recebo 422 CPF_ALREADY_EXISTS e não 409?

Os dois status sinalizam conflitos diferentes:

  • 409 CONCURRENT_REQUEST — conflito de protocolo de idempotência: outra requisição com a mesma Idempotency-Key está em vôo no servidor agora. É um conflito transitório — aguarde Retry-After: 2 segundos e refaça com a mesma key/body para receber o replay.
  • 422 CPF_ALREADY_EXISTS — conflito de regra de negócio: o CPF já está cadastrado para outro paciente no tenant. Não é transitório — retry com a mesma key não resolve, porque o paciente continuará no banco.

422 traz no envelope o caminho de upsert manual:

{
  "data": null,
  "meta": { "request_id": "...", "duracao_ms": 31 },
  "error": {
    "code": "CPF_ALREADY_EXISTS",
    "message": "CPF ja cadastrado em outro paciente.",
    "details": { "paciente_id_existente": 4811, "nome": "MARIA DA SILVA" }
  }
}

Use details.paciente_id_existente para fazer GET /v1/pacientes/4811 (com escopo pacientes:read) e em seguida PATCH /v1/pacientes/4811 para atualizar o registro existente. O mesmo se aplica a MEDICAL_RECORD_ALREADY_EXISTS.

Q17) O que muda no PATCH /v1/pacientes/{id}? Posso enviar só o campo que quero alterar?

Sim — PATCH segue RFC 7396 JSON Merge Patch:

  • Campo omitido — não é tocado (preserva o valor atual no banco).
  • Campo com valor null ou "" — é resetado (apaga o valor atual).
  • Sub-objeto endereco ou convenio presentesubstitui inteiro o atual. Não há sub-merge na v1 — campos não enviados dentro do sub-objeto ficam vazios.
  • Sub-objeto {} ou nullzera o sub-objeto.

Exemplo: para mudar só o e-mail, envie apenas { "email": "novo@example.com" }. O CPF, endereço, convênio etc. permanecem intactos. Para apagar o convênio, envie { "convenio": null }.

Se você precisa preservar parte de um sub-objeto (ex.: mudar só endereco.cep sem perder o logradouro), faça GET /v1/pacientes/{id} primeiro, mescle no cliente e envie o sub-objeto completo no PATCH. PATCH idempotente: enviar o mesmo body com a mesma key devolve o replay (200 OK, Idempotency-Replay: true).

Q18) Não sei o paciente_id / convenio_id / medico_id — e agora?

Você não precisa dos *_id internos para criar um exame. Na release 1.1.0, o POST /v1/exames aceita identificadores de negócio (aliases) que o gateway resolve para o *_id correspondente:

  • paciente_cpfpaciente_id
  • convenio_nome / convenio_codigo_ansconvenio_id
  • procedimento_abreviacao / procedimento_codigo_tussprocedimento_id
  • medico_exame_crm / medico_laudo_crmmedico_exame_id / medico_laudo_id
  • unidade_nomeunidade_id

Além disso, cada tenant pode ter defaults para unidade_id, medico_exame_id, medico_laudo_id e convenio_id: quando você omite esses campos (e nenhum alias os resolve), o gateway aplica o default do tenant. Para descobrir o que pode ser omitido, consulte GET /v1/config — o nó data.defaults lista cada campo (valor int = há default; null = você precisa mandar o *_id ou um alias).

Na prática, um payload mínimo basta:

{
  "paciente_cpf": "12345678900",
  "procedimento_abreviacao": "CAMPI",
  "convenio_nome": "Unimed"
}

Se um alias não casar nenhum registro, casar vários, ou conflitar com um *_id que você também enviou, o gateway devolve um 422 acionável (RESOLUTION_NOT_FOUND, RESOLUTION_AMBIGUOUS ou PARAM_CONFLICT) — cada item traz a fonte onde descobrir o valor correto. Veja Códigos de erro § Resolução de alias e a referência detalhada de endpoints.

Q19) Quais são os valores válidos para sexo, tipo (arquivo) e status_laudo?

Esses domínios fechados são constantes do gateway (iguais para todo tenant) e estão expostos no nó data.enums do GET /v1/config — consulte por contrato em vez de descobrir por tentativa/erro (400):

  • sexoMAS, FEM, OUT (POST/PATCH /v1/pacientes)
  • arquivo_tipoexame, laudo (?tipo= de GET /v1/exames/{id}/arquivo)
  • status_laudoPENDENTE, EMITIDO, ASSINADO (filtro/saída de exames)
  • prioridade1 (Normal), 2 (Alta), 3 (Urgente) — POST /v1/exames
  • requer_laudo0, 1POST /v1/exames

As listas espelham exatamente a validação do gateway: enviar um valor fora do enum devolve 400 com a mesma lista de valores aceitos. Veja também a referência do GET /v1/config.