Data Export
When a practitioner submits a report, Tiro.health persists it to your data tenant and signals your system to fetch the result. This page covers how that callback works and how to retrieve the structured data.
Data export is the final step of a Capture API integration. It follows Data Import and the context launch: the user completes the form, clicks submit, and control returns to your application together with everything it needs to pull the report back.
Overview
The submission flow has three parts:
- User clicks submit. The practitioner completes their work in Tiro.health and submits.
- Data is persisted. The report is saved to your data tenant as a
completedQuestionnaireResponse with a human-readable narrative, and its structured artifacts — a Composition and any resources the template extracts — are persisted and attached to the Task as outputs. - Redirect and retrieval. The user's browser is redirected to a URL you configured. That redirect is both the return path for the user and the trigger for your backend to fetch the data.
The submit callback is a browser redirect, not a server-to-server webhook. Your system learns that a report is ready when the user lands back on your configured redirect URL — there is no push notification.
The Submit Callback
On submit, Tiro.health redirects the browser to your configured URL and appends the references of the resources that were just created as query parameters:
GET https://your-system.example.com/submit?response=QuestionnaireResponse/11427&task=Task/456
| Parameter | Description |
|---|---|
response | Reference to the completed QuestionnaireResponse — the report. |
task | Reference to the Task that was fulfilled, when the report was launched from a task. |
Extract the response reference from the query string, then call back into the FHIR API to retrieve the data. The redirect only tells you that a report is ready and which one — you still fetch the content yourself.
Configuring the Redirect
Set post_submit_redirect when you create the session with POST /sessions. The redirect is configured per session, so the return location can depend on where the launch came from.
A matching post_cancel_redirect controls where the user is sent if they cancel instead of submitting.
Fetch the QuestionnaireResponse
Once you have the response id from the redirect, fetch the QuestionnaireResponse. Use _include to pull the referenced Patient and Encounter into the same Bundle so you don't need extra round-trips.
The human-readable report is available directly on the response as the generated text narrative (XHTML), so most integrations that only need the report text can stop here.
Common parameters
_id— the QuestionnaireResponse id from the redirect._include=QuestionnaireResponse:subject— include the Patient._include=QuestionnaireResponse:encounter— include the Encounter.
Request
curl -G https://reports.tiro.health/fhir/r5/QuestionnaireResponse \
--data-urlencode "_id=11427" \
--data-urlencode "_include=QuestionnaireResponse:subject" \
--data-urlencode "_include=QuestionnaireResponse:encounter" \
-H "Authorization: Basic {{apikey}}" \
-H "Content-Type: application/fhir+json"
Response (searchset)
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [
{
"fullUrl": "https://reports.tiro.health/fhir/r5/QuestionnaireResponse/11427",
"resource": {
"resourceType": "QuestionnaireResponse",
"id": "11427",
"questionnaire": "http://templates.tiro.health/templates/unreadable-unique-id|1.0.1",
"status": "completed",
"subject": { "reference": "Patient/123" },
"encounter": { "reference": "Encounter/456" },
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">...report narrative...</div>"
}
}
}
]
}
You can also read the report reference back off the Task. Fetching GET /Task/{id} returns the completed
QuestionnaireResponse as a questionnaire-response output, which is useful
when your system tracked the Task id from the import step.
Fetch the Composition
For a structured, document-style view of the report, request the Composition. It is produced by structured-data extraction when the report is submitted, stored as its own resource, and linked to the Task as a composition output — so you retrieve it through the Task, using _include.
How the Composition comes into existence
On submit, three things happen server-side:
- The QuestionnaireResponse is set to
completed. - Extraction runs against the template's blueprints and persists the resulting resources — a Composition, plus any
Observations or other resources the template declares — together with aProvenancerecord pointing back at the QuestionnaireResponse. - Those persisted resources are attached to the Task as
compositionoutputs.
The Composition therefore carries its own id, assigned at extraction time. It is not the QuestionnaireResponse id, and a report re-submitted (re-extracted) gets a new Composition id.
Retrieving it
Search the Task and pull the outputs into the same Bundle:
_include=Task:output:Composition— the extracted Composition(s) only._include=Task:output— every extracted resource and the QuestionnaireResponse.
Filter with _id using the Task id from the redirect, or with your own identifier if you tracked the Task from the import step — convenient in integration engines (e.g. MIRTH) that key off their own identifier. _include cannot be combined with _graph.
Deprecated: GET /Composition/{id} does not serve extracted Compositions.
It reads the legacy report tables and interprets the id as a report id, so for
reports created through the Capture API it returns an empty or unrelated
document. Use the Task _include path above.
What the Composition contains
status—final.type— the template's own coding, or LOINC11506-3(Progress note) when the template declares none.title— the report title, rendered from the template.section[]— one entry per template block, each with atitleand a generated narrative (text.div, XHTML). Sections nest where the template nests groups.
Sections are identified by title and position, in template order. The generated blueprint does not emit section.code, so don't key your mapping on codes unless your template's blueprint pins them explicitly — talk to us if you need that.
Language
The Composition and its section narratives are generated in the language the clinician selected while completing the report. Sections without content in that language are dropped rather than falling back, so you always receive one consistent language version per report — never a mix.
Request
curl -G https://reports.tiro.health/fhir/r5/Task \
--data-urlencode "_id=456" \
--data-urlencode "_include=Task:output:Composition" \
-H "Authorization: Basic {{apikey}}" \
-H "Content-Type: application/fhir+json"
Response (searchset)
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [
{
"fullUrl": "https://reports.tiro.health/fhir/r5/Task/456",
"search": { "mode": "match" },
"resource": {
"resourceType": "Task",
"id": "456",
"status": "completed",
"output": [
{
"type": {
"coding": [{
"system": "http://fhir.tiro.health/CodeSystem/task-output",
"code": "questionnaire-response"
}]
},
"valueReference": { "reference": "QuestionnaireResponse/11427" }
},
{
"type": {
"coding": [{
"system": "http://fhir.tiro.health/CodeSystem/task-output",
"code": "composition"
}]
},
"valueReference": { "reference": "Composition/8842" }
}
]
}
},
{
"fullUrl": "https://reports.tiro.health/fhir/r5/Composition/8842",
"search": { "mode": "include" },
"resource": {
"resourceType": "Composition",
"id": "8842",
"status": "final",
"type": {
"coding": [{
"system": "http://loinc.org",
"code": "11506-3",
"display": "Progress note"
}]
},
"title": "CT scan report",
"section": [
{
"title": "Findings",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">...section narrative...</div>"
}
}
]
}
}
]
}
Tiro.health does not expose a $document operation. The Composition and the
QuestionnaireResponse text narrative are the rendered outputs available
through this API; PDF rendering is a separate operation that is not part of
the data-export contract.
Native Applications
For desktop and native integrations, the redirect can use a custom URI scheme instead of an HTTP URL to return the user to your application:
- Windows apps can register URI activation handlers.
- macOS apps can define custom URL schemes.
When the report runs inside an embedded browser, intercept the redirect navigation to capture the response reference programmatically rather than following it as a page load.