Skip to navigation

Paciente

O Patient carrega os dados demográficos e administrativos de quem recebe cuidado. É o recurso central da integração: quase todo outro recurso — atendimento, condição, diretriz aplicada — referencia um paciente.

Campos

A coluna No NiloCare traz o rótulo com que o dado aparece para a equipe de cuidado. São rótulos, não caminhos de navegação: a tela pode ser reorganizada, o rótulo é o que permite conferir se o dado chegou onde você esperava.

CampoObrigatórioO que significaNo NiloCare
identifiersim, ao menos umChaves do paciente. É por aqui que a API decide entre criar e atualizar—
identifier (CPF)sim por padrão 3CPF, exatamente 11 dígitosCPF
name[use=official]sim na criaçãoNome legal. Vale o text; na falta dele, given + familyNome paciente
name[use=usual]nãoNome social ou apelidoNome Social (para pessoas trans, travestis e transexuais)
birthDatenãoData de nascimentoData de Nascimento
gendernão, mas reenvie 4Sexo administrativo. Só male e female são gravadosSexo
maritalStatusnãoEstado civilEstado civil
telecom[system=phone]nãoCelular do paciente, com DDI + DDD + número (ex.: 5511987654321). Passa por validação de WhatsAppCelular com Whatsapp
telecom[system=email]nãoE-mail de contatoEmail
address[0].line[0]nãoLogradouroLogradouro
address[0].line[1]nãoNúmero. Ausente, é gravado como S/NNúmero
address[0].line[2]nãoComplementoComplemento
address[0].districtnãoBairroBairro
address[0].citynãoCidadeCidade
address[0].statenãoEstadoEstado
address[0].postalCodenãoCEPCEP
address[0].countrynãoPaís—
deceasedDateTimenãoData e hora do óbito—
managingOrganizationnãoUnidade de cuidado responsável pelo pacienteUnidade de cuidado
contained[Group]sim, com ressalva 1Grupos a que o paciente pertenceGrupos de pacientes
activenãoSe o cadastro está em uso. Traduz para o status do pacienteStatus
extension patient-statusnãoStatus do paciente pelo id de um status do seu care providerStatus
extension patient-isLeadnãotrue = pré-cadastro, ainda pendente de aceite dos termos de usoJá aceitou os termos de uso para ser paciente?
extension patient-sendWelcomingMessagesim, com ressalva 2Dispara ou não a mensagem de boas-vindas ao cadastrarEnviar mensagem de boas vindas?
extension patient-createOnboardingSchedulingsim, com ressalva 2Libera o paciente para o primeiro agendamentoLiberar agendamento de onboarding?
extension patient-genderIdentitynãoIdentidade de gênero, num vocabulário próprioIdentidade de gênero
extension patient-religionnãoEspiritualidade declarada, em texto livreEspiritualidade
extension patient-cadavericDonornãoSe o paciente é doador de órgãosDoador de órgãos
communicationsó respostaIdioma de contato. Sempre pt-BR—
extension patient-pathssó respostaAtalho para a ficha do paciente no NiloCare—

1 Todo paciente pertence a pelo menos um grupo, mas o payload pode omiti-lo se a sua unidade de cuidado tiver grupo padrão configurado. Veja Grupos.
2 Obrigatórias no envio, e preenchidas com o padrão da sua unidade de cuidado quando omitidas. Sem valor enviado e sem padrão configurado, a escrita é recusada.
3 O system do CPF vem configurado como identificador único do paciente por padrão, e nessa configuração o CPF é obrigatório. Ele só é opcional se a sua implantação usar outro identificador único — veja CPF como identificador único, adiante.
4 gender é o único campo do recurso que não é preservado numa atualização parcial: omiti-lo grava other por cima do sexo atual. Reenvie-o em toda escrita — veja Sexo.

A URL completa de cada extensão está em Extensões — nesta página elas aparecem só pelo nome final.

Sexo é de preenchimento obrigatório no cadastro pela interface, mas gender é opcional na API — e essa diferença tem uma consequência que foge à regra da atualização parcial. Um Patient enviado sem gender grava other, inclusive num paciente que já tem sexo registrado. Reenvie gender em toda escrita, mesmo quando o objetivo é atualizar outro campo. Veja Sexo.

Cadastrar ou atualizar

Não há endpoint separado para criar e atualizar. O mesmo POST faz os dois, e quem decide é o identifier: se já existir um paciente com aquele par system + value, ele é atualizado; se não existir, é criado.

POST
/fhir/resources/Patient
curl -X POST https://landing-zone-api.nilo.services/fhir/resources/Patient \
-H "x-api-key: <apiKey>" \
-H "Content-Type: application/json" \
-d '{
"identifier": [
{
"system": "https://servicos.receita.fazenda.gov.br/servicos/cpf/",
"value": "39053344705",
"use": "official"
},
{
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709",
"use": "usual"
}
],
"resourceType": "Patient",
"extension": [
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/Group",
"valueIdentifier": {
"system": "https://www.acmesaude.com.br/integracao/grupo/",
"use": "usual",
"value": "diabeticos-tipo-2"
}
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-sendWelcomingMessage",
"valueBoolean": true
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-createOnboardingScheduling",
"valueBoolean": true
}
],
"name": [
{
"family": "Silveira",
"given": [
"João"
],
"use": "official"
}
]
}'

O system é o namespace do seu sistema e o value é a chave do paciente lá dentro. Reenviar o mesmo payload não gera duplicata, o que torna seguro reprocessar uma carga.

Criar um paciente pode disparar uma mensagem de boas-vindas por WhatsApp e liberar o primeiro agendamento — comportamento controlado pelas extensões obrigatórias, que assumem o padrão da sua unidade quando você as omite. Antes da primeira carga em volume, confirme esses padrões. Veja Efeitos colaterais.

Trocar o system de um paciente já integrado faz a Nilo tratá-lo como um paciente novo. O histórico fica partido em dois registros e não há como juntá-los depois pela API.

Um cadastro mais completo aceita CPF, contato, endereço, unidade de cuidado e as extensões Nilo:

Requisição — Cadastro completo
{
"resourceType": "Patient",
"active": true,
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709"
},
{
"use": "official",
"system": "https://servicos.receita.fazenda.gov.br/servicos/cpf/",
"value": "39053344705"
}
],
"name": [
{
"use": "official",
"text": "João Pedro Silveira",
"family": "Silveira",
"given": [
"João",
"Pedro"
]
},
{
"use": "usual",
"text": "Joana Silveira"
}
],
"gender": "male",
"birthDate": "1974-12-25",
"maritalStatus": {
"coding": [
{
"code": "M",
"display": "Married",
"system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus"
}
]
},
"telecom": [
{
"system": "phone",
"value": "5511987654321",
"use": "mobile"
},
{
"system": "email",
"value": "joao.silveira@example.com",
"use": "home"
}
],
"address": [
{
"use": "home",
"type": "both",
"line": [
"Rua das Acácias",
"120",
"Apto 42"
],
"district": "Pinheiros",
"city": "São Paulo",
"state": "São Paulo",
"postalCode": "01415000",
"country": "Brasil"
}
],
"managingOrganization": {
"type": "Organization",
"identifier": {
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/sorting-hat-api--care-unit",
"use": "usual",
"value": "4"
}
},
"extension": [
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-isLead",
"valueBoolean": false
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-sendWelcomingMessage",
"valueBoolean": true
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-createOnboardingScheduling",
"valueBoolean": true
},
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-genderIdentity",
"valueCodeableConcept": {
"coding": [
{
"code": "male",
"display": "male",
"system": "http://hl7.org/fhir/gender-identity"
}
]
}
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-religion",
"valueCodeableConcept": {
"text": "Católica"
}
},
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-cadavericDonor",
"valueBoolean": true
}
],
"contained": [
{
"resourceType": "Group",
"type": "person",
"actual": true,
"name": "Diabéticos tipo 2",
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/grupo/",
"value": "diabeticos-tipo-2"
}
]
}
]
}

A resposta devolve o recurso como ficou gravado:

Response
{
"identifier": [
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/hippocrates-api--patient",
"value": "48210",
"use": "usual"
},
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/care-api--patient-v2-token",
"value": "0f1c2d3e-4a5b-6c7d-8e9f-a0b1c2d3e4f5",
"use": "usual"
},
{
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709",
"use": "usual"
},
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/care-api--patient-v2",
"value": "31502",
"use": "usual"
}
],
"resourceType": "Patient",
"active": true,
"contained": [
{
"resourceType": "Group",
"identifier": [
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/sorting-hat-api--cohort",
"value": "904",
"use": "usual"
},
{
"system": "https://www.acmesaude.com.br/integracao/grupo/",
"value": "diabeticos-tipo-2",
"use": "usual"
}
],
"type": "person",
"actual": true,
"name": "Diabéticos tipo 2"
}
],
"communication": [
{
"language": {
"coding": [
{
"code": "pt-BR",
"display": "Portuguese (Brazil)",
"system": "urn:ietf:bcp:47"
}
]
},
"preferred": true
}
],
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-genderIdentity",
"valueCodeableConcept": {
"coding": [
{
"code": "non-disclose",
"display": "does not wish to disclose",
"system": "http://hl7.org/fhir/gender-identity"
}
]
}
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-cohort",
"valueIdentifier": {
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/sorting-hat-api--cohort",
"use": "usual",
"value": "904"
}
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-sendWelcomingMessage",
"valueBoolean": true
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-createOnboardingScheduling",
"valueBoolean": true
},
{
"extension": [
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-paths-home",
"valueUrl": "https://acme.nilocare.app/patients/48210"
}
],
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-paths"
}
],
"gender": "other",
"id": "ba200cfa-dae0-46cf-81a0-008e3f7414b4",
"meta": {
"lastUpdated": "2026-08-06T13:04:12.905000Z",
"versionId": "MTc4NjAyMTQ1MjkwNTAwMDM2Nw"
},
"name": [
{
"family": "Silveira",
"given": [
"João"
],
"text": "João Silveira",
"use": "official"
}
]
}

Guarde o id: é por ele que se faz a leitura direta do paciente.

Repare que a resposta traz mais identificadores do que você enviou. A Nilo devolve o seu identifier junto com os dela — o id interno do paciente (…/NamingSystem/hippocrates-api--patient), o token (…/NamingSystem/care-api--patient-v2-token), o id legado (…/NamingSystem/care-api--patient-v2) e o CPF, quando houver. Todos servem para buscar; o seu continua sendo o único que você precisa guardar.

A atualização é parcial. Campo ausente ou enviado como null preserva o valor atual — não existe, por esta rota, como apagar um dado já gravado. São três as exceções: o nome social, removido quando o name com use: usual vem com period.end preenchido; a lista de grupos, substituída por inteiro quando contained é enviado; e gender, que omitido grava other por cima do valor atual — veja Sexo.

Buscar

GET
/fhir/resources/Patient
curl -G https://landing-zone-api.nilo.services/fhir/resources/Patient \
-H "x-api-key: <apiKey>" \
-d _count=50 \
-d _lastUpdated=eq2013-01-14 \
-d active=true \
--data-urlencode address=Acácias \
--data-urlencode "address-city=São Paulo" \
-d address-country=Brasil \
-d address-postalcode=01415000 \
--data-urlencode "address-state=São Paulo" \
-d address-use=home \
-d birthdate=1974-12-25 \
-d death-date=ge2024-01-01 \
-d deceased=true \
--data-urlencode email=joao.silveira@example.com \
-d family=Silveira \
-d gender=male \
--data-urlencode given=João \
--data-urlencode identifier=https://www.acmesaude.com.br/integracao/paciente/|507823709 \
-d language=pt-BR \
--data-urlencode "name=João Silveira" \
--data-urlencode organization=Organization/6e6a1b40-1e2a-4f1e-9f0e-2b1c3d4e5f60 \
-d phone=5511987654321 \
-d phonetic=Silveyra \
--data-urlencode telecom=phone|5511987654321

A resposta é sempre um Bundle do tipo searchset, mesmo quando há um único resultado:

Response
{
"resourceType": "Bundle",
"type": "searchset",
"entry": [
{
"fullUrl": "https://landing-zone-api.nilo.services/fhir/resources/Patient/ba200cfa-dae0-46cf-81a0-008e3f7414b4",
"resource": {
"identifier": [
{
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709",
"use": "usual"
}
],
"resourceType": "Patient",
"active": true,
"id": "ba200cfa-dae0-46cf-81a0-008e3f7414b4",
"name": [
{
"family": "Silveira",
"given": [
"João"
],
"use": "official"
}
]
},
"search": {
"mode": "match"
}
}
],
"link": [
{
"relation": "next",
"url": "https://landing-zone-api.nilo.services/fhir/resources/Patient?_page_token=Cjj3YopYuf"
}
]
}

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.

Response
{
"resourceType": "Bundle",
"type": "searchset",
"entry": [],
"link": []
}

Parâmetros de busca suportados

NomeTipoDescriçãoExpressão
identifiertokenIdentificador do paciente, em system|valuePatient.identifier
activetokenSe o cadastro está ativoPatient.active
namestringQualquer parte de qualquer nomePatient.name
familystringParte do sobrenomePatient.name.family
givenstringParte do primeiro nomePatient.name.given
phoneticstringNome por correspondência fonéticaPatient.name
birthdatedateData de nascimentoPatient.birthDate
gendertokenSexo administrativoPatient.gender
telecomtokenQualquer contatoPatient.telecom
phonetokenTelefone de contatoPatient.telecom.where(system='phone')
emailtokenE-mail de contatoPatient.telecom.where(system='email')
addressstringQualquer campo do endereçoPatient.address
address-citystringCidadePatient.address.city
address-statestringEstadoPatient.address.state
address-postalcodestringCEPPatient.address.postalCode
address-countrystringPaísPatient.address.country
address-usetokenFinalidade do endereço — a Nilo grava sempre homePatient.address.use
organizationreferenceUnidade de cuidado, no formato Organization/{id} *Patient.managingOrganization
death-datedateData do óbito(Patient.deceased as dateTime)
deceasedtokenSe está marcado como falecidoPatient.deceased.exists() and Patient.deceased != false
languagetokenIdioma — a Nilo grava sempre pt-BRPatient.communication.language

* O {id} do filtro organization é o id da unidade no store FHIR, não o identificador que você usa no seu sistema. Pegue-o do managingOrganization.reference de um paciente já lido.

Estado civil, grupos e as extensões Nilo não são filtráveis: o FHIR R4 não define search parameters para eles no Patient. E não há caminho pelo outro lado: o grupo de pacientes não carrega a lista de membros. Para saber quem está num grupo, percorra os pacientes e leia o contained de cada um.

A busca mais comum na prática é pelo identificador do seu próprio sistema, que usa a sintaxe system|value:

curl --request GET \
--url 'https://landing-zone-api.nilo.services/fhir/resources/Patient?identifier=https://www.acmesaude.com.br/integracao/paciente/|507823709' \
--header 'x-api-key: SUA_API_KEY'

Paginação e recorte por data

Além dos search parameters do Patient, a busca aceita três parâmetros de controle:

NomeDescrição
_lastUpdatedRecorta por data de última alteração, com os prefixos FHIR de comparação (eq, ge, le). Enviado duas vezes, delimita um período
_countQuantos recursos por página
_page_tokenSeleciona a página seguinte

A resposta não traz contagem total. Para percorrer todas as páginas, siga o link com relation: next até ele deixar de vir — a URL já embute o _page_token da página seguinte, e é o único jeito confiável de paginar:

"link": [
{
"relation": "next",
"url": "https://landing-zone-api.nilo.services/fhir/resources/Patient?_page_token=Cjj3YopYuf"
}
]

Ler por ID

GET
/fhir/resources/Patient/:id
curl https://landing-zone-api.nilo.services/fhir/resources/Patient/ba200cfa-dae0-46cf-81a0-008e3f7414b4 \
-H "x-api-key: <apiKey>"

A leitura por ID responde 404 quando o id não existe.

A leitura por ID não devolve o recurso na raiz da resposta. Ela devolve uma entrada com a mesma forma das entradas de Bundle.entry — fullUrl, resource e search — e o paciente está em resource. É a diferença mais fácil de errar entre esta rota e a busca: a busca envolve as entradas num Bundle, esta devolve uma entrada solta, mas em nenhuma das duas o Patient está no topo.

Response
{
"fullUrl": "https://landing-zone-api.nilo.services/fhir/resources/Patient/ba200cfa-dae0-46cf-81a0-008e3f7414b4",
"resource": {
"identifier": [
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/hippocrates-api--patient",
"value": "48210",
"use": "usual"
},
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/care-api--patient-v2-token",
"value": "0f1c2d3e-4a5b-6c7d-8e9f-a0b1c2d3e4f5",
"use": "usual"
},
{
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709",
"use": "usual"
},
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/care-api--patient-v2",
"value": "31502",
"use": "usual"
}
],
"resourceType": "Patient",
"active": true,
"birthDate": "1974-12-25",
"contained": [
{
"resourceType": "Group",
"identifier": [
{
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/sorting-hat-api--cohort",
"value": "904",
"use": "usual"
},
{
"system": "https://www.acmesaude.com.br/integracao/grupo/",
"value": "diabeticos-tipo-2",
"use": "usual"
}
],
"type": "person",
"actual": true,
"name": "Diabéticos tipo 2"
}
],
"communication": [
{
"language": {
"coding": [
{
"code": "pt-BR",
"display": "Portuguese (Brazil)",
"system": "urn:ietf:bcp:47"
}
]
},
"preferred": true
}
],
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-genderIdentity",
"valueCodeableConcept": {
"coding": [
{
"code": "non-disclose",
"display": "does not wish to disclose",
"system": "http://hl7.org/fhir/gender-identity"
}
]
}
},
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-cohort",
"valueIdentifier": {
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/sorting-hat-api--cohort",
"use": "usual",
"value": "904"
}
},
{
"extension": [
{
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-paths-home",
"valueUrl": "https://acme.nilocare.app/patients/48210"
}
],
"url": "https://landing-zone-api.nilo.services/fhir/resources/StructureDefinition/patient-paths"
}
],
"gender": "male",
"id": "ba200cfa-dae0-46cf-81a0-008e3f7414b4",
"meta": {
"lastUpdated": "2026-08-06T13:04:12.905000Z",
"versionId": "MTc4NjAyMTQ1MjkwNTAwMDM2Nw"
},
"name": [
{
"family": "Silveira",
"given": [
"João"
],
"text": "João Silveira",
"use": "official"
}
]
},
"search": {
"mode": "match"
}
}

Valores aceitos

Sexo

gender segue os códigos administrativos do FHIR, mas só dois sobrevivem à gravação:

EnviadoGravadoNo NiloCare
malemaleMasculino
femalefemaleFeminino
other, unknownother—
ausenteother—

A última linha é a pegadinha: gender é o único campo do Patient que não segue a regra da atualização parcial. Omitido, ele não preserva o sexo atual — grava other por cima. Um payload de correção de endereço, ou o exemplo Adicionar CPF desta página, apaga o sexo do paciente se não trouxer gender.

Leia o paciente antes de atualizá-lo e reenvie o gender que voltou. Na leitura ele nunca vem nulo, então não há caso em que você não tenha o valor para reenviar.

Identidade de gênero

Para registrar identidade de gênero, use a extensão patient-genderIdentity, que tem vocabulário próprio:

CódigoNo NiloCare
maleHomem
femaleMulher
transgender-maleHomem Trans
transgender-femaleMulher Trans
otherOutro
non-discloseNão informado

Num paciente novo, qualquer outro código — ou a ausência da extensão — é gravado como non-disclose. Num paciente que já existe, o valor atual é preservado.

Estado civil

maritalStatus reconhece quatro códigos de v3-MaritalStatus e um de v3-NullFlavor:

CódigoNo NiloCare
SSolteiro(a)
MCasado(a)
DDivorciado(a)
WViúvo(a)
UNKOutro

Num paciente novo, qualquer outro código cai em UNK, e é UNK que volta na leitura. Num paciente que já existe, um código não reconhecido preserva o estado civil atual em vez de sobrescrevê-lo.

Efeitos colaterais

Uma escrita de Patient faz mais do que gravar o cadastro.

patient-sendWelcomingMessage com valueBoolean: true envia uma mensagem de WhatsApp ao paciente no momento do cadastro. Numa carga inicial de milhares de pacientes, isso são milhares de mensagens. A extensão é obrigatória, mas assume o padrão da sua unidade quando omitida — não deixe esse padrão decidir por você numa migração.

patient-createOnboardingScheduling com valueBoolean: true libera o paciente para agendar o primeiro atendimento assim que o cadastro entra. Vale a mesma cautela.

Enviar address cria ou atualiza o endereço do paciente como registro próprio. Só o primeiro item de address é lido; os demais são descartados sem aviso.

address.line é posicional na escrita — line[0] logradouro, line[1] número, line[2] complemento — mas a leitura omite os itens vazios em vez de devolvê-los como string vazia. Num paciente sem logradouro, o número volta em line[0]; reenviar essa resposta sem tratar grava o número como logradouro. Ao montar uma escrita a partir de uma leitura, remonte line pelos rótulos, não pela posição em que veio.

Enviar contained substitui a lista de grupos do paciente pela lista enviada — não acrescenta. Para adicionar um grupo, reenvie os que o paciente já tem mais o novo; para removê-lo, reenvie a lista sem ele. Veja Grupos.

Regras de escrita e validações

Recusa é sempre 400 com um OperationOutcome: issue[].expression aponta o campo culpado e issue[].details.text explica o motivo.

Resposta 400 — CPF inválido
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "structure",
"expression": [
"Patient.identifier"
],
"details": {
"text": "Invalid CPF: must contain exactly 11 digits with no dots, commas or other characters."
}
}
]
}

Um payload que contenha a URL de outro ambiente da Nilo é recusado — a mensagem fala de “identifier from a Nilo environment that is not the current environment”. Como as URLs das extensões Nilo e os system dos identificadores internos embutem o host do ambiente, é o erro esperado de quem copia um payload lido em produção e o reenvia em homologação. Ao migrar um exemplo entre ambientes, troque o host em todas as URLs, ou remova os identificadores e extensões que a Nilo devolveu e mande apenas os seus.

O que depende da implantação

Três decisões são configuradas por unidade de cuidado no momento da implantação, e mudam o que a API exige de você. Se não souber como a sua está, fale com o time antes de montar a carga.

ConfiguraçãoEfeito
Namespaces de identificadorDefine quais system são aceitos para encontrar um paciente já cadastrado, e em que ordem de prioridade
Unidade de cuidado padrãoUsada quando o payload não traz managingOrganization e o paciente ainda não existe
Grupo, boas-vindas e agendamento inicial padrãoPreenchem o que o payload omitir

Identificadores

Ao menos um identifier é obrigatório, e o system enviado precisa estar entre os namespaces habilitados para a sua unidade — é por ele que a Nilo decide entre criar e atualizar. Enviar só identificadores de system desconhecido é recusado, em vez de criar um paciente duplicado:

Resposta 400 — Nenhum identificador utilizável
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "business-rule",
"expression": [
"Patient.identifier"
],
"details": {
"text": "Unable to use any of the provided identifiers to match an existing resource, which can lead to duplicates."
}
}
]
}

CPF como identificador único

Por padrão, o system do CPF é o identificador único do paciente. É essa configuração que torna o CPF obrigatório no cadastro: enquanto ela vale, um payload sem CPF não tem por onde ser reconhecido e é recusado com o mesmo OperationOutcome da seção anterior — mesmo que traga o identificador do seu próprio sistema.

Enquanto o CPF for o identificador único da sua implantação, não há como cadastrar um paciente sem CPF por esta API.

Nem toda operação tem o CPF de todo paciente. Se a sua precisa cadastrar sem ele — usando o identificador do seu sistema como chave —, essa configuração pode ser trocada, mas não pela API: abra um ticket no Suporte pedindo a mudança do identificador único da sua unidade de cuidado.

Depois da troca, o CPF passa a ser opcional e continua aceito como identificador adicional, com as mesmas regras de formato e de use descritas abaixo.

CPF

O CPF é um identifier como os outros, mas com tratamento próprio: use o system https://servicos.receita.fazenda.gov.br/servicos/cpf/ e envie exatamente 11 dígitos, sem pontos nem traços.

Para acrescentá-lo a um paciente que já existe, mande o identificador que encontra o paciente, o CPF e o gender atual do paciente:

Requisição — Adicionar CPF
{
"resourceType": "Patient",
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709"
},
{
"use": "official",
"system": "https://servicos.receita.fazenda.gov.br/servicos/cpf/",
"value": "39053344705"
}
],
"gender": "male"
}

Como a atualização é parcial, o resto do cadastro fica intacto — nome, grupo e as extensões obrigatórias não precisam ser reenviados. O gender é a exceção: omiti-lo grava other por cima do sexo do paciente, e por isso ele acompanha o payload mesmo não sendo o dado que se quer mudar. Veja Sexo. O CPF volta na resposta como mais um identificador:

Resposta 200 — Paciente com CPF
{
"resourceType": "Patient",
"id": "ba200cfa-dae0-46cf-81a0-008e3f7414b4",
"meta": {
"versionId": "MTc4NjA5NDU2MTMxMDAwMDM2Nw",
"lastUpdated": "2026-08-07T09:22:41.310000Z"
},
"active": true,
"identifier": [
{
"use": "usual",
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/hippocrates-api--patient",
"value": "48210"
},
{
"use": "official",
"system": "https://servicos.receita.fazenda.gov.br/servicos/cpf/",
"value": "39053344705"
},
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709"
},
{
"use": "usual",
"system": "https://landing-zone-api.nilo.services/fhir/resources/NamingSystem/care-api--patient-v2",
"value": "31502"
}
],
"name": [
{
"use": "official",
"text": "João Silveira",
"family": "Silveira",
"given": [
"João"
]
}
],
"gender": "male"
}

O use: official não é decorativo: é ele que autoriza escrever por cima de um CPF já gravado. Um CPF com qualquer outro use só entra se o paciente ainda não tiver CPF nenhum — é assim que se envia um dado de baixa confiança sem sobrescrever o que já foi verificado. Se o payload trouxer mais de um identificador de CPF, apenas o primeiro utilizável é lido.

Note que o exemplo acima manda o identificador do seu sistema junto com o CPF. O CPF sozinho só encontra o paciente se o system da Receita estiver entre os namespaces habilitados para a sua unidade de cuidado.

Quando o system do CPF está habilitado para a sua unidade, ele passa a ser também a chave externa do paciente no NiloCare, à frente do identificador do parceiro. Isso vale para pacientes já cadastrados: a chave externa deles é trocada na escrita seguinte.

Nome

Um paciente novo precisa de um name com use: official que tenha text, given ou family; a Nilo prefere o text e, na falta dele, junta given e family. Um name com use: usual vira o nome social. Se o payload trouxer mais de um name do mesmo use, vale o último da lista.

Para remover o nome social, reenvie o name com use: usual e um period.end — qualquer data serve, é a presença do campo que apaga o valor:

Requisição — Remover nome social
{
"resourceType": "Patient",
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709"
}
],
"name": [
{
"use": "official",
"text": "João Pedro Silveira",
"family": "Silveira",
"given": [
"João",
"Pedro"
]
},
{
"use": "usual",
"text": "Joana Silveira",
"period": {
"end": "2026-03-01"
}
}
]
}

Datas

O FHIR aceita datas parciais que o NiloCare não representa. Uma birthDate enviada como 2022-12 é completada para 2022-12-01 ao ser gravada. Envie a data completa quando você a tiver, para que a leitura devolva o que você espera.

Grupos

Todo paciente pertence a pelo menos um grupo. Há três formas de indicar isso, e elas não se combinam:

FormaMúltiplos gruposQuando usar
contained com um ou mais GroupsimRecomendada. A única que representa mais de um grupo
extensão …/fhir/resources/Group, com o identificador de um GroupnãoUm grupo só, sem montar o recurso contido
extensão …/StructureDefinition/patient-cohort, com o id Nilo do gruponãoLegada. Mantida por compatibilidade

A URL completa de cada uma está em Extensões.

O Group referenciado precisa já existir — cadastre-o antes de referenciá-lo aqui. Um identificador que não resolve recusa a escrita inteira, e mandar grupo em contained e em extension ao mesmo tempo também.

Enviar contained substitui a lista inteira de grupos do paciente. Para acrescentar um grupo, reenvie os grupos que o paciente já tem junto com o novo:

Requisição — Atualizar grupos
{
"resourceType": "Patient",
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/paciente/",
"value": "507823709"
}
],
"contained": [
{
"resourceType": "Group",
"type": "person",
"actual": true,
"name": "Diabéticos tipo 2",
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/grupo/",
"value": "diabeticos-tipo-2"
}
]
},
{
"resourceType": "Group",
"type": "person",
"actual": true,
"name": "Hipertensos",
"identifier": [
{
"use": "usual",
"system": "https://www.acmesaude.com.br/integracao/grupo/",
"value": "hipertensos"
}
]
}
]
}

Para remover, reenvie a lista sem o grupo que sai. Omitir contained por completo preserva os grupos atuais — não os apaga. E num paciente que já existe as duas extensões de grupo não têm efeito: só a criação as considera.

Sem grupo em contained, sem extensão de grupo e sem grupo padrão configurado para a unidade, a criação é recusada:

Resposta 400 — Grupo não informado
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "structure",
"expression": [
"Patient.extension,contained"
],
"details": {
"text": "Extension with url NiloStructureDefinition.PATIENT_GROUP is required when no contained Group resourceType is present, or contained with resourceType=Group is required when no extension is present."
}
}
]
}

Na leitura, os grupos voltam de duas formas ao mesmo tempo: contained traz todos e a extensão patient-cohort traz apenas o primeiro. Leia os grupos do contained; a extensão está lá por compatibilidade e não representa o conjunto.

Unidade de cuidado

managingOrganization precisa apontar para uma unidade já cadastrada. Omitindo o campo, a Nilo mantém a unidade que o paciente já tem ou, para um paciente novo, usa a unidade padrão do care provider. Sem unidade no payload e sem unidade padrão configurada, a criação falha:

Resposta 400 — Unidade padrão não configurada
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-found",
"expression": [
"Patient.?"
],
"details": {
"text": "A default care unit isn't configured for the care provider."
}
}
]
}

Esta integração representa uma unidade de cuidado por paciente. Um paciente que esteja em mais de uma unidade no NiloCare não sincroniza: a leitura falha em vez de devolver a primeira, e o erro que chega é um 400 genérico, sem apontar a causa. Se a sua base tem pacientes em várias unidades, fale com o time antes de integrá-la.

Telefone

O telefone passa por validação de elegibilidade para WhatsApp, e um número reprovado recusa a escrita inteira. Números de baixa confiança devem ir com use: old ou use: temp: nesse caso o número é apenas descartado, sem derrubar a requisição nem sobrescrever um contato mais atual.

O value precisa trazer DDI + DDD + número, no formato E.164. Espaços, parênteses e hífens são removidos antes da validação. O DDI nunca é presumido: um número sem ele é lido como se os primeiros dígitos fossem o código do país.

value enviadoResultado
5511987654321Aceito
+55 (11) 98765-4321Aceito, lido como +5511987654321
551187654321Aceito. Celular brasileiro sem o nono dígito: a Nilo o inclui
11987654321Recusado — falta o 55, mesmo sendo um celular brasileiro válido

Com mais de um telecom de telefone, um use: old é ignorado quando existe outro número, e o use que corresponde à tag de telefone verificado da sua implantação tem precedência sobre os demais.

Status

Os status de paciente não são um vocabulário fixo: cada care provider tem os seus, definidos na implantação. Não há como descobri-los pela API — peça a lista ao time antes de usar a extensão patient-status.

active e a extensão …/StructureDefinition/patient-status descrevem a mesma coisa por caminhos diferentes. Enviando os dois, eles precisam concordar: um active: true com um status de categoria inativa (ou o contrário) é recusado. O id da extensão também precisa existir entre os status do seu care provider.

Enviando só active num paciente novo, a Nilo escolhe o status padrão ativo ou inativo da unidade. Num paciente que já existe, o status atual é mantido quando concorda com o active enviado, e trocado pelo padrão da categoria apenas quando discorda — um active: false num paciente ativo o move para o status inativo padrão, sem escolher entre os vários status inativos que o seu care provider possa ter.

Enviando nenhum dos dois, um paciente novo nasce com o status padrão ativo e um paciente existente mantém o que já tinha.

Campos ignorados

communication é sempre gravado como pt-BR, independentemente do que for enviado. A extensão …/StructureDefinition/patient-paths é apenas devolvida — é o atalho para a ficha do paciente no NiloCare. Extensões fora do contexto Nilo não têm efeito no cadastro, mas ficam gravadas no recurso e voltam nas leituras seguintes — veja Extensões.

Este endpoint não remove pacientes: a escrita nunca responde 204. A remoção acontece do lado Nilo e é propagada para o store FHIR pela sincronização, não por esta API.

Erros

Toda recusa de escrita é 400, inclusive o cadastro duplicado. O corpo é um OperationOutcome em que issue[].expression aponta o campo culpado e issue[].details.text explica o motivo.

codeexpressionMensagemQuando
structurePatient.<campo>mensagem da validação FHIRO payload não é um Patient válido: tipo errado, valor fora do vocabulário, data malformada. Recusado antes de qualquer regra Nilo
structure—Resource has identifier from a Nilo environment that is not the current environment.O payload traz URLs da Nilo de outro ambiente. Também recusado antes do processamento, e por isso expression vem vazia
requiredPatient.identifierField is requiredO payload não tem identifier
business-rulePatient.identifierUnable to use any of the provided identifiers to match an existing resource, which can lead to duplicates.Há identifier, mas nenhum system habilitado para a sua unidade — não há como decidir entre criar e atualizar
structurePatient.identifierInvalid CPF: must contain exactly 11 digits with no dots, commas or other characters.O CPF não tem exatamente 11 dígitos
requiredPatient.nameField name is requiredPaciente novo sem nenhum name
invalidPatient.nameName field has no values in text, given or familyHá name, mas nenhum com use: official que traga text, given ou family — e não há cadastro anterior de onde herdar o nome
invalidPatient.telecomInvalid phone number: …, ou a mensagem crua da validação de WhatsApp: {"errors":"Invalid phone number +…"}, sem os dois-pontos e com o número já prefixado por +. Um DDI estranho nela (ex.: +28…) indica que faltou o 55O telefone não passou na validação de elegibilidade para WhatsApp e não veio com use: old nem use: temp
not-foundPatient.managingOrganizationThe provided CareUnit could not be found.A unidade de cuidado enviada não existe
not-foundPatient.?A default care unit isn't configured for the care provider.Sem managingOrganization, paciente novo e sem unidade padrão configurada. expression sai como Patient.? porque a falha é da configuração, não de um campo do payload
structurePatient.containedGroup with identifiers (system|value) … does not existsUm Group de contained não está cadastrado
structurePatient.extensionThe provided Group could not be found.O grupo indicado pela extensão não resolve
structurePatient.extension,containedGroup cannot be defined both in extension and contained.Grupo enviado em contained e em extensão
structurePatient.extension,containedExtension with url NiloStructureDefinition.PATIENT_GROUP is required when no contained Group resourceType is present, …Paciente novo sem grupo em contained, sem extensão de grupo e sem grupo padrão configurado
requiredPatient.extensionExtension with url …/patient-sendWelcomingMessage is required.A extensão de boas-vindas não tem valor no payload nem valor a herdar
structurePatient.extensionExtension with url …/patient-createOnboardingScheduling is required.Idem, para a extensão de agendamento inicial
not-foundPatient.extensionPatient status … does not existO id da extensão patient-status não existe entre os status do seu care provider
invalidPatient.extensionThe active field is as …, but in the extension it is passing a status with … categoryactive e a categoria do status enviado se contradizem
conflict—Resource conflicts with other existing resource(s), com a razão em issue[].diagnosticsO cadastro duplica um registro que já existe do lado da Nilo. Repetir a requisição sem mudar o payload devolve o mesmo erro
exception—<Exceção>: <mensagem>Falha não prevista. É o que chega quando o paciente já está em mais de uma unidade de cuidado e o payload omitiu managingOrganization, ou quando o identifier enviado casa com mais de um paciente

A recusa característica deste recurso é a do telefone. Basta o payload trazer algum telecom com system: phone para a validação rodar — e, se nenhum número do payload for aproveitado, é o telefone já gravado que vai à validação. Um número antigo reprovado derruba a escrita de qualquer outro campo.

Resposta 400 — Telefone reprovado na validação
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invalid",
"expression": [
"Patient.telecom"
],
"details": {
"text": "{\"errors\":\"Invalid phone number +21987654321\"}"
}
}
]
}

Aqui o cliente enviou 21987654321, sem o 55. O + foi prefixado ao número como veio, e +21… não é um DDI válido. Reenviado como 5521987654321, o número passa.

O use: old ou use: temp que dispensa a recusa é lido do último telecom de telefone do payload, não necessariamente daquele cujo número foi validado. Com um telefone só dá no mesmo; com vários, mande o número de baixa confiança por último.

Duas extensões obrigatórias falham com code diferente pela mesma causa: patient-sendWelcomingMessage sai como required e patient-createOnboardingScheduling como structure. Trate as duas como campo obrigatório ausente; não use o code para distinguir a natureza do problema.

Elas assumem, num paciente novo, o padrão configurado na unidade de cuidado e, num paciente que já existe, o valor que ele já tem. A recusa acontece quando não há nem um nem outro.

A extensão patient-isLead não é obrigatória, apesar de o OperationOutcome das outras duas sugerir simetria. Omiti-la nunca recusa a escrita.

Um 400 com code: exception pode chegar depois de o paciente já ter sido gravado — a falha acontece na etapa seguinte à gravação. Repetir a requisição é seguro, porque a escrita é upsert, mas não presuma que nada foi criado: confirme com uma busca por identifier.

A mensagem do grupo obrigatório traz um marcador interno no lugar da URL da extensão. A extensão a que ela se refere é …/fhir/resources/Group — veja Extensões.

As recusas de leitura não são OperationOutcome: GET /fhir/resources/Patient/{id} responde 404 com {"detail": "Not found."} quando não existe paciente com aquele id, e a busca responde 400 com um objeto {"errors": [ … ]} quando um parâmetro é inválido.

Os payloads completos de cada recusa estão na aba Referência, em POST /fhir/resources/Patient.

O que a integração não cobre

A ficha do paciente no NiloCare tem campos que não têm representação no Patient e por isso não podem ser preenchidos nem lidos por esta API. Eles só existem pela interface:

  • Tipo sanguíneo e Deficiências

  • Escolaridade, Profissão e Com quem mora

  • Cor ou raça autodeclarada

  • Detalhes pessoais relevantes (texto livre)

  • Nome da mãe

Enviar esses dados dentro de extensões próprias não os faz aparecer na ficha: a extensão é gravada no recurso FHIR e devolvida nas leituras, mas o cadastro do paciente não a lê.

O plano de saúde do paciente não fica no Patient — é o recurso Coverage, gravado à parte e ligado ao paciente por beneficiary.