Pular para o conteúdo principal

Enviando um recurso FHIR

Com o token de acesso em mãos (veja Autenticação), o próximo passo é enviar o registro de saúde. Aqui há uma diferença importante em relação a APIs comuns: o HubSaúde confirma o recebimento na hora, mas processa o conteúdo depois. Seu sistema envia, recebe um comprovante e consulta esse comprovante até o processamento terminar.

Como funciona o envio em duas etapas​

O envio é um POST /fhir/{tipo}, ou PUT, PATCH e DELETE em /fhir/{tipo}/{id} para alterar e remover. Quatro cabeçalhos são obrigatórios: Content-Type: application/fhir+json, Accept, Prefer: respond-async e Idempotency-Key. Faltando qualquer um deles, a requisição volta com 400.

Primeiro, o comprovante​

A resposta é 202 com corpo vazio, o equivalente a "recebi e vou processar". O cabeçalho Content-Location traz o endereço de uma Task, um recurso FHIR próprio para acompanhar pedidos em andamento (não é o paciente nem o resultado final), e Retry-After sugere quanto esperar antes de consultar.

Depois, o resultado​

Consulte com GET o endereço recebido em Content-Location (/Task/{id}) até que Task.status indique conclusão. Em criação, atualização e correção, Task.output.resultReference aponta para o recurso final. Em remoção, o sucesso vem como valueBoolean=true.

Se a rede falhar​

Quando você não souber se o envio chegou, reenvie com a mesma Idempotency-Key. O HubSaúde reconhece a repetição, não duplica nada e devolve o mesmo 202, acrescentando Idempotency-Replayed: true quando a operação já terminou.

Autenticação vinculada a certificado​

Se o token foi emitido numa conexão com certificado de cliente, ele carrega o campo cnf e fica atrelado a esse certificado (veja Cache e mTLS). Isso quer dizer que o cabeçalho Authorization: Bearer sozinho não basta: a chamada a /fhir/* também precisa ser mTLS, apresentando o mesmo certificado de cliente usado para obter o token — não precisa ser a mesma conexão TCP, só o mesmo certificado. Caso contrário a resposta é 401, acompanhada de WWW-Authenticate: Bearer error="invalid_token" e de um OperationOutcome explicando a recusa.

Criando um Patient​

O exemplo abaixo monta um paciente com o mínimo de informação, envia e consulta o resultado. Repare que são chamadas HTTP diretas: os SDKs oficiais cuidam apenas da autenticação, e o envio dos dados fica por sua conta.

import httpx
import time
import uuid

paciente = {
"resourceType": "Patient",
"identifier": [{"system": "urn:oid:2.16.76.1.3.3", "value": "123456789"}],
"name": [{"use": "official", "text": "Maria da Silva"}],
"birthDate": "1990-01-01",
}

headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/fhir+json",
"Accept": "application/fhir+json",
"Prefer": "respond-async",
"Idempotency-Key": str(uuid.uuid4()),
}

# O mesmo certificado de cliente usado para obter o token (RFC 8705)
with httpx.Client(cert=("certificado.pem", "chave-privada.pem")) as http:
resp = http.post("https://hub.saude.go.gov.br/fhir/Patient", json=paciente, headers=headers)
if resp.status_code != 202:
raise RuntimeError(f"Envio recusado: {resp.status_code} {resp.text}")
task_url = resp.headers["Content-Location"]

while True:
task = http.get(task_url, headers={"Authorization": f"Bearer {token}"}).json()
if task["status"] not in ("accepted", "in-progress"):
break
time.sleep(1)

if task["status"] != "completed":
raise RuntimeError(f"Task não concluída ({task['status']}): {task}")

print(task["output"][0]["valueReference"])

Testando localmente antes de produção​

Vale exercitar tudo isso na sua máquina antes de apontar para o ambiente real. O HubSaúde CLI sobe dois ambientes locais, cada um resolvendo um problema diferente.

Simulador: o fluxo completo​

hubsaude sim start sobe uma cópia local do HubSaúde em HTTPS na porta 8443, exigindo mTLS como a produção. É onde você testa autenticação e envio de ponta a ponta. Os dados ficam apenas em memória e desaparecem ao reiniciar.

Servidor FHIR: o formato do recurso​

hubsaude server start sobe um servidor HAPI FHIR local, sem autenticação, útil para conferir se o recurso está bem formado antes de enviá-lo para produção. Use hubsaude server start --reset para começar do zero.

Erros comuns​

StatusCausa
400 OperationOutcome(required)Header obrigatório ausente (Accept, Prefer, Idempotency-Key, Content-Type)
406Accept incompatível
415 OperationOutcome(not-supported)Content-Type malformado ou incompatível
409Replay concorrente da mesma Idempotency-Key (requisição original ainda em voo)
413 OperationOutcome(too-long)Payload excede o limite
Task.status=rejectedJSON bem formado, mas FHIR inválido. Confira antes no Validador

As regras de cada tipo de recurso, como quais campos são obrigatórios em um BRPaciente, ficam nos Guias de Implementação, onde você consulta a versão vigente de cada perfil.