Ingest and Query a Patient
Full walkthrough: ingest a FHIR bundle and a CDA document, browse the virtual file system, and read a condition story end-to-end.
Ingest and Query a Patient
In this tutorial you will:
- Ingest a FHIR R4 bundle for a patient
- Ingest a CDA document for the same patient
- Check ingest status and confirm entity resolution ran
- Browse the virtual file system to discover what was extracted
- Read a condition story
By the end you will have a working Clinia Context Engine workflow and understand how source records become a navigable clinical graph.
Time: ~15 minutes.
Prerequisites
- A Clinia Context Engine workspace with a workspace ID and an OAuth access token. See Manage Credentials for how to obtain one. Replace
<workspace-id>and<access-token>in the commands below.
Steps
Ingest a FHIR R4 bundle
Pick a registry key for the patient. This is the identifier you control. It does not need to match Patient.id inside the bundle.
Create the patient before the first ingest:
curl -X PUT "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient" \
-H "Authorization: Bearer <access-token>"This call is idempotent, so it's safe to run before or after your first ingest.
No PHI? Use the synthetic bundle
Download the
Jeanne Tremblay synthetic FHIR R4 bundle
to follow along without real patient data. It covers 10 encounters spanning 2017–2026: COPD GOLD stage 3, hypertension, dyslipidemia, knee osteoarthritis, and an acute COPD exacerbation.
curl -X POST "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/ingestions/fhir/r4" \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d @fhir-bundle.jsonThe call answers 202 with a receipt:
{
"ingestionId": "c1m2k3n4p5q6r7s8t9u0v1w2",
"source": "fhir/r4",
"mimeType": "application/json",
"receivedAt": "2026-09-10T14:22:31.004Z",
"status": "pending",
"staged": { "facts": 12, "events": 8, "narratives": 1 },
"intake": [
{
"unit": "resource",
"received": 42,
"entries": [
{ "kind": "Condition", "count": 4, "outcome": "structured" },
{ "kind": "Observation", "count": 30, "outcome": "structured" },
{ "kind": "DocumentReference", "count": 1, "outcome": "text_only" },
{ "kind": "ImagingStudy", "count": 7, "outcome": "ignored", "reason": "unsupported" }
]
}
],
"startedAt": null,
"completedAt": null,
"failure": null
}202 means accepted, not finished — extraction continues after the response, so nothing is queryable yet. Keep the ingestionId; you will poll it in a moment.
intake accounts for all 42 resources you sent. Nothing is discarded silently: an ignored entry names the kind and says why, and it does not fail the submission.
Ingest a CDA document
Multiple ingest calls for the same registry key accumulate sources. Entity resolution re-runs after each one, merging what it can across all sources.
curl -X POST "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/ingestions/cda/r2" \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/cda+xml" \
--data-binary @summary.xml{
"ingestionId": "d2n3l4o5q6r7s8t9u0v1w2x3",
"source": "cda/r2",
"mimeType": "application/cda+xml",
"receivedAt": "2026-09-10T14:23:07.512Z",
"status": "pending",
"staged": { "facts": 9, "events": 4, "narratives": 3 },
"intake": [
{
"unit": "section",
"received": 12,
"entries": [
{ "kind": "10160-0", "label": "Medications", "count": 1, "outcome": "structured" },
{ "kind": "11348-0", "label": "Past Medical History", "count": 1, "outcome": "structured" },
{ "kind": "8648-8", "label": "Hospital Course", "count": 1, "outcome": "text_only" }
]
}
],
"startedAt": null,
"completedAt": null,
"failure": null
}A second submission, so a second receipt — each one is the handle to its own request. CDA receipts carry intake too, counted in sections rather than resources. Patient demographics (id, name) are resolved from whichever source contains a Patient resource or CDA recordTarget.
Wait for both submissions to settle
Both calls returned before their work finished, so ask what is still outstanding:
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/ingestions?status=pending,processing" \
-H "Authorization: Bearer <access-token>"meta.total is the number of submissions still to settle. Poll until it reaches zero:
{
"data": [],
"meta": { "total": 0, "page": 0, "perPage": 100 }
}To watch one submission instead, read its receipt by id — GET …/ingestions/{ingestionId} — and wait for status to leave pending and processing. It settles at succeeded, partial (searchable, but some content is missing) or failed. Ingestion explains what each outcome means and when a resend is the right response.
The two submissions staged 21 facts between them (12 + 9), but the resolved graph holds fewer: entity resolution recognised duplicate pairs across the FHIR and CDA sources and merged them into single nodes. staged counts what each request contributed, so it never shows that merge — the reconciliation summary is what reports it.
Browse the virtual file system
Navigate the resolved graph through the VFS. Start at the root to see what categories were populated:
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/vfs?path=/" \
-H "Authorization: Bearer <access-token>"Then drill into active conditions:
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/vfs?path=/conditions/active" \
-H "Authorization: Bearer <access-token>"{
"path": "/patient/jeanne-72f-copd/conditions/active",
"type": "directory",
"children": [
{
"name": "chronic_obstructive_lung_disease",
"type": "directory",
"preview": "GOLD stage 3, active since 2014"
},
{ "name": "hypertension", "type": "directory", "preview": "Active since 2011" },
{ "name": "dyslipidemia", "type": "directory", "preview": "Active since 2013" }
]
}The path in the response uses the VFS patient ID (jeanne-72f-copd) extracted from the ingested record. It is not the registry key (jeanne-tremblay) used in the request URL. These are the same value only if your FHIR bundle's Patient.id matches the registry key you chose.
Each condition slug is derived from the primary display name: lowercased, spaces and special characters replaced with underscores. If the same condition appeared in both the FHIR bundle and the CDA document, it is a single entry here, not two.
Inspect what files are available under a condition:
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/vfs?path=/conditions/active/type_2_diabetes_mellitus" \
-H "Authorization: Bearer <access-token>"{
"path": "/patient/jeanne-72f-copd/conditions/active/chronic_obstructive_lung_disease",
"type": "directory",
"children": [
{ "name": "_story.md", "type": "file", "preview": "longitudinal condition narrative" },
{ "name": "_narratives", "type": "directory", "preview": "0 narratives" }
]
}Read a condition story
Read the full narrative for the condition. The story assembles onset, current medications, monitoring labs, and complications from the merged entity graph:
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/read?path=/conditions/active/type_2_diabetes_mellitus/_story.md" \
-H "Authorization: Bearer <access-token>"{
"content": "# Chronic Obstructive Lung Disease\n\n**Status:** Active since 2014, GOLD stage 3\n**Codes:** SNOMED 13645005 · ICD-10 J44.1\n**Sources:** 2 sources (FHIR Bundle, Spirometry_2025.xml)\n\n## Current Treatment\n| Medication | Dose | Since |\n|-----------|------|-------|\n| Tiotropium | 18mcg daily | 2015 |\n| Budesonide/Formoterol | 200/6mcg BID | 2018 |\n| Albuterol | 100mcg PRN | 2014 |\n..."
}For a shorter summary, pass format=compact:
curl "https://<workspace-id>.w.clinia.cloud/v1/patients/demo-patient/read?path=/conditions/active/type_2_diabetes_mellitus/_story.md&format=compact" \
-H "Authorization: Bearer <access-token>"What you built
You ingested two sources for the same patient, let the engine resolve and merge duplicate entities across them, and retrieved a unified clinical narrative, without writing any custom parsing logic.
The key concepts at play:
- Registry key (
jeanne-tremblay) is the identifier you control; it's used in all REST ingest and metadata routes - VFS patient ID (
idfromGET /v1/patients/{registryKey}) is what appears in VFS paths. It comes from the ingested record, so it is readable once the submission has settled, and may differ from the registry key - Entity resolution merged duplicate conditions, medications, and labs across sources
- VFS slugs are stable across re-ingests of the same data
- Condition stories assemble cross-entity context (medications, labs, complications) into a single readable document
Next steps
- Connect an AI Agent to let a language model query this patient through MCP tools
- Entity Resolution to understand how the 4-layer cascade decides what to merge
- Audit entity resolution decisions for a focused recipe on reading provenance traces