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:
chronyousystemd-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.
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_idda 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 diferente →
422 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 — aguardeRetry-After: 2segundos 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
nullou""— é resetado (apaga o valor atual). - Sub-objeto
enderecoouconveniopresente — substitui inteiro o atual. Não há sub-merge na v1 — campos não enviados dentro do sub-objeto ficam vazios. - Sub-objeto
{}ounull— zera 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_cpf→paciente_idconvenio_nome/convenio_codigo_ans→convenio_idprocedimento_abreviacao/procedimento_codigo_tuss→procedimento_idmedico_exame_crm/medico_laudo_crm→medico_exame_id/medico_laudo_idunidade_nome→unidade_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):
sexo→MAS,FEM,OUT(POST/PATCH/v1/pacientes)arquivo_tipo→exame,laudo(?tipo=deGET /v1/exames/{id}/arquivo)status_laudo→PENDENTE,EMITIDO,ASSINADO(filtro/saída de exames)prioridade→1(Normal),2(Alta),3(Urgente) —POST /v1/examesrequer_laudo→0,1—POST /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.