Skip to main content
Version: 9.2.0

Export view

This operation executes one or more ViewDefinitions against FHIR data and exports the results to files. This implementation follows the SQL on FHIR specification.

Use this operation for large datasets that need to be exported asynchronously. For smaller, synchronous queries, see the run view operation.

Endpoint

POST [base]/$viewdefinition-export

Parameters

NameCardinalityTypeDescription
view.name0..*stringOptional names for exported views. Used in output filenames.
view.viewResource1..*ResourceViewDefinition resources to execute.
clientTrackingId0..1stringClient-provided tracking identifier for correlation.
_format0..1stringOutput format: ndjson (default), csv, or parquet.
header0..1booleanInclude header row in CSV output. Defaults to true.
patient0..*idFilter to resources for the specified patient(s).
group0..*idFilter to resources for patients in the specified Group(s).
_since0..1instantOnly include resources where meta.lastUpdated is at or after this time.

Asynchronous processing

The view export operation uses the FHIR Asynchronous Request Pattern. Include a Prefer: respond-async header to initiate asynchronous processing.

Kick-off request

POST [base]/$viewdefinition-export HTTP/1.1
Content-Type: application/fhir+json
Accept: application/fhir+json
Prefer: respond-async

{
"resourceType": "Parameters",
"parameter": [
{
"name": "view",
"part": [
{
"name": "name",
"valueString": "patient_demographics"
},
{
"name": "viewResource",
"resource": {
"resourceType": "ViewDefinition",
"name": "patient_demographics",
"resource": "Patient",
"status": "active",
"select": [
{
"column": [
{"name": "id", "path": "id"},
{"name": "family", "path": "name.first().family"},
{"name": "given", "path": "name.first().given.first()"},
{"name": "gender", "path": "gender"},
{"name": "birth_date", "path": "birthDate"}
]
}
]
}
}
]
}
]
}

Kick-off response

HTTP/1.1 202 Accepted
Content-Location: [base]/$exportstatus/[job-id]

Polling

Poll the URL from Content-Location until you receive a 200 OK response with the export manifest.

  • 202 Accepted — Export still in progress. Check X-Progress header for status.
  • 200 OK — Export complete. Response body contains the manifest.

Response manifest

When the export completes, the response contains a JSON manifest:

{
"transactionTime": "2025-01-15T10:30:00.000Z",
"request": "https://pathling.example.com/fhir/$viewdefinition-export",
"requiresAccessToken": true,
"output": [
{
"name": "patient_demographics",
"url": "https://pathling.example.com/fhir/$result?job=abc123&file=patient_demographics.ndjson"
}
],
"error": []
}

Manifest fields

FieldDescription
transactionTimeThe time the export was initiated
requestThe original kick-off request URL
requiresAccessTokenWhether authentication is required to download result files
outputArray of exported files with view name and download URL
errorArray of OperationOutcome files for any errors

Output formats

Format_format valueContent typeDescription
NDJSONndjsonapplication/x-ndjsonNewline-delimited JSON. Default format.
CSVcsvtext/csvComma-separated values. Use header=false to exclude headers.
Parquetparquetapplication/vnd.apache.parquetApache Parquet columnar format. Efficient for large datasets.

Multiple views

Export multiple views in a single request by including multiple view parameters:

{
"resourceType": "Parameters",
"parameter": [
{
"name": "view",
"part": [
{"name": "name", "valueString": "patients"},
{"name": "viewResource", "resource": {"resourceType": "ViewDefinition", "..."}}
]
},
{
"name": "view",
"part": [
{"name": "name", "valueString": "conditions"},
{"name": "viewResource", "resource": {"resourceType": "ViewDefinition", "..."}}
]
}
]
}

Each view produces a separate output file in the manifest.

Filtering

Patient filter

Use the patient parameter to restrict results to resources associated with specific patients:

{
"resourceType": "Parameters",
"parameter": [
{
"name": "view",
"part": [
{"name": "viewResource", "resource": {"resourceType": "ViewDefinition", "..."}}
]
},
{"name": "patient", "valueId": "patient-123"},
{"name": "patient", "valueId": "patient-456"}
]
}

Group filter

Use the group parameter to restrict results to resources for patients who are members of the specified Group:

{
"resourceType": "Parameters",
"parameter": [
{
"name": "view",
"part": [
{"name": "viewResource", "resource": {"resourceType": "ViewDefinition", "..."}}
]
},
{"name": "group", "valueId": "cohort-study-group"}
]
}

Time filter

Use _since to only include resources updated after a specific time:

{
"resourceType": "Parameters",
"parameter": [
{
"name": "view",
"part": [
{"name": "viewResource", "resource": {"resourceType": "ViewDefinition", "..."}}
]
},
{"name": "_since", "valueInstant": "2024-01-01T00:00:00Z"}
]
}

Comparison with run view

AspectExport viewRun view
ProcessingAsynchronous with pollingSynchronous
OutputFiles (download via manifest URLs)Streamed response
Multiple viewsYesNo
Parquet formatYesNo
Use caseLarge datasets, batch processingSmall queries, interactive use

Python example

The following Python script demonstrates the complete view export workflow.

Run the script using uv:

uv run view_export_client.py

View export client

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = ["requests"]
# ///
"""Demonstrates the $viewdefinition-export operation with async polling."""

import os
import time
import requests

BASE_URL = "https://pathling.example.com/fhir"
OUTPUT_DIR = "./view_export"


def create_patient_view():
"""Create a ViewDefinition for Patient demographics."""
return {
"resourceType": "ViewDefinition",
"name": "patient_demographics",
"resource": "Patient",
"status": "active",
"select": [
{
"column": [
{"name": "id", "path": "id"},
{"name": "family", "path": "name.first().family"},
{"name": "given", "path": "name.first().given.first()"},
{"name": "gender", "path": "gender"},
{"name": "birth_date", "path": "birthDate"},
]
}
],
}


def create_condition_view():
"""Create a ViewDefinition for Condition data."""
return {
"resourceType": "ViewDefinition",
"name": "conditions",
"resource": "Condition",
"status": "active",
"select": [
{
"column": [
{"name": "id", "path": "id"},
{"name": "patient_id", "path": "subject.getReferenceKey()"},
{"name": "code", "path": "code.coding.first().code"},
{"name": "display", "path": "code.coding.first().display"},
{"name": "onset", "path": "onset.ofType(dateTime)"},
]
}
],
}


def kick_off_export(views, output_format="ndjson"):
"""Initiate a view export."""
url = f"{BASE_URL}/$viewdefinition-export"

# Build Parameters resource with view definitions.
parameters = {
"resourceType": "Parameters",
"parameter": [],
}

for view in views:
parameters["parameter"].append({
"name": "view",
"part": [
{"name": "name", "valueString": view.get("name", "unnamed")},
{"name": "viewResource", "resource": view},
],
})

parameters["parameter"].append({
"name": "_format",
"valueString": output_format,
})

headers = {
"Content-Type": "application/fhir+json",
"Accept": "application/fhir+json",
"Prefer": "respond-async",
}

response = requests.post(url, json=parameters, headers=headers)

if response.status_code == 202:
status_url = response.headers.get("Content-Location")
print(f"Export started, polling: {status_url}")
return status_url
else:
response.raise_for_status()


def poll_status(status_url, timeout=3600):
"""Poll the status endpoint until export completes."""
start = time.time()
interval = 2.0

while time.time() - start < timeout:
response = requests.get(
status_url,
headers={"Accept": "application/fhir+json"},
)

if response.status_code == 200:
print("Export complete")
return response.json()
elif response.status_code == 202:
progress = response.headers.get("X-Progress", "unknown")
print(f"In progress: {progress}")
time.sleep(interval)
interval = min(interval * 1.5, 30.0)
else:
response.raise_for_status()

raise TimeoutError(f"Export timed out after {timeout} seconds")


def download_files(manifest, output_dir):
"""Download all files from the export manifest."""
os.makedirs(output_dir, exist_ok=True)

for item in manifest.get("output", []):
name = item["name"]
url = item["url"]
# Extract extension from URL or default to ndjson.
ext = url.split(".")[-1] if "." in url.split("=")[-1] else "ndjson"
filename = f"{name}.{ext}"
filepath = os.path.join(output_dir, filename)

print(f"Downloading {name}...")
response = requests.get(url)
response.raise_for_status()

with open(filepath, "wb") as f:
f.write(response.content)
print(f" Saved to {filepath}")


def main():
"""Execute the complete view export workflow."""
views = [
create_patient_view(),
create_condition_view(),
]

print(f"Starting view export for {len(views)} views...")
status_url = kick_off_export(views, output_format="ndjson")

manifest = poll_status(status_url)
print(f"Manifest: {manifest}")

download_files(manifest, OUTPUT_DIR)
print("Export complete!")


if __name__ == "__main__":
main()