Arquivo do paciente
Um arquivo do paciente é um documento anexado à ficha dele: um laudo, um resultado de exame, um relatório, um termo assinado. No Nilo Care esses arquivos ficam na aba Arquivos da ficha do paciente — Pacientes › [nome do paciente] › Arquivos —, onde a equipe de cuidado os abre, baixa, renomeia e exclui.
No FHIR o recurso é o Media, e esta integração o usa
para as duas pontas: enviar um PDF e consultar o que o paciente tem.
Este recurso não tem atualização. Ao contrário de todos os outros, o mesmo POST não
cria e atualiza: se o identifier que você mandar já corresponder a um arquivo existente, a
chamada é recusada. Cada arquivo enviado precisa de uma chave nova.
Também não há como apagar nem renomear um arquivo por esta API. Pela integração, só criar e consultar; excluir e renomear são ações da equipe no Nilo Care.
Campos
A coluna No Nilo Care traz o rótulo com que o dado aparece para a equipe; são rótulos, não caminhos de navegação.
Campos que a Nilo não usa
O Media canônico traz muito mais do que esta integração lê: type, modality, view,
encounter, createdDateTime, issued, operator, reasonCode, bodySite, device,
height, width, duration, frames, note, partOf, basedOn — e, dentro de
content, o hash. Nenhum deles é lido, e por isso nenhum aparece na referência do recurso.
A referência não os declara, mas a API não recusa quem os manda — e nenhum deles tem
efeito. O content.hash, em particular, passa e não é conferido: não conte com ele para
validar a integridade do envio; use o content.size, que é conferido.
Os campos de topo, no entanto, ficam guardados no recurso e voltam nas leituras
seguintes. Se você mandar um type ou um createdDateTime, eles vão reaparecer nas suas
consultas — e até responder a uma busca por aquele campo — sem nunca terem significado nada
para a plataforma. Parece dado da Nilo, e não é. Omita-os.
Enviar um arquivo
A resposta é o recurso gravado, sem envelope:
Guarde o id: é por ele que se faz a leitura direta. Ao lado do seu identifier, a resposta
traz o identificador Nilo do arquivo, no system
…/NamingSystem/care-api--file-storage.
A resposta não repete o que você enviou: o content volta como a plataforma o registrou —
o caminho de armazenamento em url e o nome do arquivo em title —, sem data,
contentType nem size. É a visão da plataforma, e é o mesmo que uma leitura devolve.
Por URL ou em base64
Há duas formas de mandar o conteúdo, e a escolha muda o nome do arquivo e as validações.
content.url tem precedência. Mandando os dois no mesmo payload, o content.data é
ignorado em silêncio e o arquivo baixado da URL é o que fica. A chamada responde 200.
No envio por URL, o formato é decidido pela extensão no caminho da URL, e não pelo
contentType. A lista aceita é .pdf, .jpg, .jpeg, .png, .gif, .doc, .docx,
.xml, .txt, .mp3, .ogg, .oga, .wav e .mp4.
Duas consequências que pegam quem só espera PDF:
- URL sem extensão no caminho é recusada — um endereço como
https://arquivos.acmesaude.com.br/laudos/77001, ou uma URL assinada cujo nome do objeto não termina em.pdf, não passa. A recusa sai comocode: exception, semexpression; - por URL, dá para enviar coisa que não é PDF. A restrição a
application/pdfvale só para o envio pordata. Se a sua integração só deve mandar documentos, garanta isso do seu lado — a API não vai barrar um.jpg.
O endereço tem de estar acessível publicamente no momento da chamada: é a plataforma que
faz o download, e ela não recebe as suas credenciais. Uma URL assinada com prazo curto pode
expirar antes. Certificado TLS inválido e resposta que não seja 2xx também recusam a
chamada, com code: exception.
O nome do arquivo
As regras abaixo valem só para o envio por data — é o único caminho em que o
content.title sobrevive. No envio por URL o nome é montado pela plataforma como
nome do paciente + data, e o título que você mandou é descartado.
No envio por data, o título passa por uma normalização antes de virar nome de arquivo:
- acentos são transliterados (
ó→o); - tudo que não for letra, número,
.,_ou-vira_; - uma extensão
.pdfjá presente no título não é duplicada; - o nome é cortado em 100 caracteres;
.,_e-nas pontas são removidos.
O que é conferido no envio por data
Nesta ordem, e todas as recusas são 400:
- O
identifieré casado antes de tudo. Chave já usada é recusada aqui, sem que o paciente ou o conteúdo cheguem a ser olhados. Antes disso, o recurso ainda passa pela validação de forma do FHIR, que recusa comcode: structure. - O paciente é resolvido em seguida. Arquivo de paciente que não existe não é decodificado nem sobe — não há resíduo de uma chamada recusada.
content.contentTypepresente e igual aapplication/pdf.content.dataé base64 válido. Espaços e quebras de linha são tolerados: um base64 quebrado em 76 colunas funciona.- O conteúdo decodificado não é vazio.
- O conteúdo decodificado não passa de 10485760 bytes (10 MiB).
content.size, se você mandou, bate exatamente com o tamanho decodificado.- O conteúdo é um PDF: os primeiros bytes precisam ser
%PDF-. Um arquivo de outro tipo comcontentType: application/pdfé recusado aqui.
O limite de 10 MiB vale para o conteúdo depois de decodificado, não para o tamanho do
corpo da requisição — em base64 o payload é cerca de um terço maior. A mensagem do erro
too-long sempre informa o limite exato em bytes do seu ambiente.
O paciente
O paciente vem em subject.identifier, e é só por aí:
subject.reference sozinho não resolve o paciente, mesmo apontando para um Patient que
existe. Sem subject.identifier a chamada é recusada, e o erro sai como code: exception,
sem expression — veja Erros. Mande sempre o identificador.
O identificador pode ser o do seu sistema ou o identificador Nilo do paciente; nos dois casos
ele tem de resolver para um paciente do seu ambiente. Não resolvendo, a recusa é
Patient does not exist.
Buscar
A resposta é sempre um Bundle do tipo searchset:
Busca sem resultados não é erro: volta 200 com um Bundle cujo entry é uma lista vazia.
Trate a ausência de resultados pela lista vazia, não esperando um 404.
Parâmetros de busca suportados
A busca útil é a dos arquivos de um paciente — um GET sem nenhum filtro devolve os
arquivos de todos os pacientes do seu ambiente, página por página, o que raramente é o que
se quer:
Os demais parâmetros canônicos do Media existem e não encontram nada aqui, porque a
Nilo não preenche o campo correspondente: type, modality, view, created, encounter,
operator, device, site e based-on. A exceção é o campo que você mesmo tenha
enviado: ele fica no recurso e passa a ser encontrável — mais um motivo para omitir o que a
plataforma não usa.
Qual identificador a referência do paciente carrega depende da sua implantação. Na
configuração que usa identificadores externos nas referências, subject traz o identificador
do seu sistema e o filtro acima funciona como está. Sem ela, vem o identificador Nilo do
paciente, e é esse system que você tem de usar no filtro. Confira numa resposta de leitura
qual dos dois está lá antes de montar a busca em volume.
A paginação é por _count e _page_token; havendo página seguinte, o Bundle traz a URL
pronta em link.
A busca não devolve só o que você enviou
A consulta expõe todos os arquivos do paciente, e não só PDF. Anexos que chegaram por
outros canais — foto, áudio e vídeo trocados na conversa com o paciente, por exemplo — também
saem como Media quando estão associados ao paciente. E como o content.contentType não é preenchido na leitura, não há
campo que diga o tipo: se a sua integração só trata documentos, olhe a extensão no
content.title.
Arquivos criados dentro do Nilo Care aparecem na consulta com apenas o identificador Nilo,
no system …/NamingSystem/care-api--file-storage. Só os que vieram por integração trazem
também a sua chave. É por essa diferença que você separa uns dos outros.
Arquivo que não esteja associado a um paciente não é exposto por esta API.
content.url na leitura não é um link de download. Para os arquivos enviados por
integração é o caminho de armazenamento — algo como
patients/{id}/document/{timestamp}_arquivo.pdf —, e um GET nesse valor não baixa nada. O
formato varia conforme a origem do arquivo, e em alguns casos é um endereço completo; não
construa a sua integração em cima dessa diferença.
Esta API não entrega o conteúdo dos arquivos: ela expõe o registro deles. Para obter o arquivo em si, use a fonte que você já tem — no envio por URL, o seu próprio endereço de origem.
Ler por ID
Diferente da busca, esta leitura não devolve um Bundle — mas também não devolve o recurso
nu. A resposta é um envelope com fullUrl, search e resource, e o arquivo está em
resource. Ler content ou subject na raiz da resposta não encontra nada.
A leitura por ID responde 404 quando o id não existe — inclusive quando o arquivo existia
e foi excluído no Nilo Care.
Valores aceitos
content.contentType
Um só: application/pdf. Qualquer outro valor é recusado com not-supported, e o conteúdo
ainda é conferido byte a byte contra o formato — declarar PDF não basta, tem de ser PDF.
A restrição vale para o envio por data. No envio por URL quem decide é a extensão da
URL, e ela aceita mais formatos — veja Por URL ou em base64. E a
leitura devolve arquivos de qualquer formato, porque devolve tudo que o paciente tem.
status
Um só: completed. É o que a leitura sempre devolve, é o único que a referência declara e é
o que você deve enviar. Os outros valores do FHIR R4 não são usados: a plataforma não guarda
situação de arquivo, e o valor devolvido é sempre completed.
Efeitos colaterais
Reenviar um identifier já usado não atualiza — recusa. Não há como trocar o paciente nem
o conteúdo de um arquivo já enviado: para isso, mande um arquivo novo com chave nova e peça à
equipe que exclua o anterior no Nilo Care.
O nome, esse sim, muda depois: a equipe pode renomear o arquivo no Nilo Care, e o nome novo
passa a ser o que a leitura devolve em content.title. Não trate o title como o valor que
você enviou.
Se o arquivo foi excluído no Nilo Care, o registro deixa de existir para esta API — a
consulta não o devolve mais, e a chave que ele usava volta a estar livre. Reenviar aquele
mesmo identifier depois disso cria um arquivo novo, em vez de ser recusado.
Falhando o registro do arquivo, o conteúdo que já subiu é apagado — nesse caso a chamada recusada não deixa arquivo órfão.
Um erro depois do registro não desfaz o arquivo. O apagamento acima cobre a falha do
registro em si; se a chamada falhar num passo posterior, você recebe 400 mas o arquivo
já está na ficha do paciente. E como nada foi gravado do lado FHIR, repetir a chamada com
a mesma chave não é reconhecido como reenvio: cria um segundo arquivo. Depois de um erro,
confira a ficha antes de repetir.
Este endpoint nunca responde 204: não há remoção de arquivo por integração.
Erros
Recusa é 400, e o corpo é um OperationOutcome: issue[].expression aponta o campo culpado
e issue[].details.text explica o motivo.
Os payloads completos estão na aba Referência, em POST /fhir/resources/Media.
As quatro últimas linhas da tabela não são validações — são falhas não tratadas, e a
primeira delas é o caso mais comum deste recurso: o reenvio de uma chave. O 400 sai com
code: exception, sem expression, e com uma mensagem de erro de linguagem em vez de uma
explicação do campo.
Nenhuma delas é contrato: não tente interpretar o texto. Trate code: exception como
“payload recusado, motivo não classificado” e confira a chave, o subject e a URL enviados.

