Data Import
Before a practitioner opens Tiro.health, import the clinical context of the report you want them to complete. This lets you pre-fill patient and encounter information so users never have to re-type it.
Data import is the first step of a Capture API integration. Your backend writes a small set of FHIR resources into your data tenant — typically in the background, ahead of the context launch — so that the report is ready to be filled in the moment the user arrives.
Overview
Data import uses the same FHIR API as the rest of the platform, hosted at https://reports.tiro.health/fhir/r5. You can create resources one at a time, but the recommended approach is a single transaction Bundle so that all resources are created atomically and can reference each other.
The goal of the import step is to represent one instruction: "this practitioner should complete this report for this patient." In FHIR terms that is a Task of type complete-questionnaire, pointing at a Patient, with the reporting Practitioner available as a user in the tenant.
Authentication required. All import requests are authenticated. See Authentication for how to obtain credentials.
In EHR-managed data tenants, end-user sessions cannot write Patient, Encounter, Practitioner, or DocumentReference — the EHR owns that data. Machine-to-machine imports with your API key are not affected: what the key can write is determined by its own scopes. Alternatively, context resources can be created at session creation via fhirContext items with role https://tiro.health/fhir/role/new — see FHIR Context.
Minimum Resources
At minimum you import three resources. Only the Patient is strictly required to start a report; importing the Practitioner and Task up front gives the practitioner a ready-to-open entry in their worklist.
| Resource | Required | Purpose |
|---|---|---|
| Patient | Yes | The subject of the report. A single identifier (system + value) is enough; name and other demographics are optional but improve the UI. |
| Practitioner | Recommended | The clinician who will author the report. Importing them ensures they exist as a user in the tenant. A single identifier is enough. |
| Task | Recommended | The complete-questionnaire order that appears in the worklist. References the Patient via for. |
| Encounter | Optional | Links the report to a specific visit. Referenced from the Task via encounter and carried onto the resulting QuestionnaireResponse. |
Tiro.health Tasks carry no practitioner reference — fields like Task.owner
or Task.requester are ignored. Import the Practitioner so the clinician
exists as a user in your tenant: when you
create the session, identify them with
the same identifier (a fhirContext item of type Practitioner), and the
report they submit is attributed to them as QuestionnaireResponse.author.
Import as a Transaction
Post a transaction Bundle to the FHIR API root. Resources reference each other by fullUrl using urn:uuid: placeholders; Tiro.health resolves those to real IDs and rewrites the references for you, regardless of the order the entries appear in.
The Bundle below imports a Patient, a Practitioner, and a draft complete-questionnaire Task in one atomic operation, using the conditional forms so the import can safely run more than once (see Idempotent Imports). If any entry fails, the whole transaction is rolled back.
Because the Task's code.text becomes the title shown in the worklist, set it to something the practitioner will recognise (e.g. "CT scan report").
Import Bundle
curl -X POST https://reports.tiro.health/fhir/r5 \
-H "Authorization: Basic {{apikey}}" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:patient-001",
"resource": {
"resourceType": "Patient",
"identifier": [{
"system": "http://hospital.example.com/patients",
"value": "PAT-12345"
}],
"name": [{"family": "Smith", "given": ["John"]}]
},
"request": {
"method": "PUT",
"url": "Patient?identifier=http://hospital.example.com/patients|PAT-12345"
}
},
{
"fullUrl": "urn:uuid:practitioner-001",
"resource": {
"resourceType": "Practitioner",
"identifier": [{
"system": "http://hospital.example.com/practitioners",
"value": "PRAC-67890"
}],
"name": [{"family": "Jones", "given": ["Sarah"]}]
},
"request": {
"method": "PUT",
"url": "Practitioner?identifier=http://hospital.example.com/practitioners|PRAC-67890"
}
},
{
"fullUrl": "urn:uuid:task-001",
"resource": {
"resourceType": "Task",
"intent": "order",
"status": "draft",
"identifier": [{
"system": "http://hospital.example.com/tasks",
"value": "TASK-54321"
}],
"description": "Complete the CT scan report for John Smith.",
"code": {
"text": "CT scan report",
"coding": [{
"system": "http://fhir.tiro.health/CodeSystem/task-type",
"code": "complete-questionnaire"
}]
},
"for": { "reference": "urn:uuid:patient-001" }
},
"request": {
"method": "POST",
"url": "Task?identifier=http://hospital.example.com/tasks|TASK-54321"
}
}
]
}'
Response (transaction-response)
{
"resourceType": "Bundle",
"type": "transaction-response",
"entry": [
{ "response": { "status": "200", "location": "Patient/1" }, "resource": { "...": "..." } },
{ "response": { "status": "200", "location": "Practitioner/1" }, "resource": { "...": "..." } },
{ "response": { "status": "201" }, "resource": { "resourceType": "Task", "id": "1", "...": "..." } }
]
}
Each response entry carries the full created or updated resource in entry.resource — read the assigned ID from entry.resource.id (the conditional updates also carry a location, but a plain create does not always set one). Store the Task's id if you want to track the report's progress or fetch the result later.
Response entries come back in processing order (resolved by reference dependencies), which is not necessarily the order of your request entries. Match entries by resource.resourceType/resource.id, not by position.
Idempotent Imports
Because imports usually run in the background — and may run more than once for the same patient — use the conditional forms so repeated imports don't create duplicates or fail:
- Patient, Encounter, Practitioner — use a conditional update (
PUT <Resource>?identifier={system}|{value}), as the example above does. It creates the resource when no identifier matches, and updates the existing one in place on re-import — returning200in both cases. A plainPOSTis not idempotent: re-importing a Patient whose identifier already exists fails with409 Conflict, which rolls back the whole transaction. - Task — use a conditional create (
POST Task?identifier={system}|{value}). When a task with that identifier already exists, it is returned unchanged (200) — deliberately not updated, so a re-import never resets a task the practitioner is already working on. When there is no match, the task is created (201).
The FHIR ifNoneExist conditional-create syntax is not supported — it is silently ignored, so an entry relying on it behaves like a plain create.
Matching is always done on the external identifier you provided, so make sure every resource carries a stable identifier from your system. See Store your internal identifiers in FHIR resources.
What Happens Next
A complete-questionnaire Task moves through a short lifecycle as it is prepared and filled in:
draft— imported without a questionnaire. The report is not yet ready to open.ready— a questionnaire (form template) has been attached, either through the API or by the practitioner choosing one in the UI.in-progress— the practitioner has started filling in the form.completed— the form has been submitted and processed.
You can attach the questionnaire yourself by including a questionnaire input on the Task, or let the practitioner pick a template when they open the report. See the Task reference for input types and lifecycle operations.
Once the context is imported, hand the user over to Tiro.health with a context launch, and retrieve the finished report through the Data Export step.