SQL on FHIR, published by HL7 International / FHIR Infrastructure. This guide is not an authorized publication; it is the continuous build for version 3.0.0-ballot built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/HL7/sql-on-fhir/ and changes regularly. See the Directory of published versions
| Official URL: http://hl7.org/fhir/uv/sql-on-fhir/OperationDefinition/SQLExport | Version: 3.0.0-ballot | ||||
| Standards status: Trial-use | Maturity Level: 2 | Computable Name: SQLExport | |||
| Other Identifiers: OID:2.16.840.1.113883.4.642.40.77.33.5 | |||||
Export one or more ViewDefinitions, SQLQuery Libraries and SQLView Libraries as a single asynchronous job, using the FHIR Asynchronous Interaction Request Pattern.
Export one or more subjects - ViewDefinitions, SQLQuery Libraries and SQLView Libraries, in any mixture - as a single asynchronous job, using the FHIR Asynchronous Interaction Request Pattern.
Use Cases:
Endpoint:
The operation is invoked at the system level only. Subjects are named by the
repeating subject parameter rather than by the request path.
| Endpoint | Subjects named by |
|---|---|
POST [base]/$sql-export |
subject parameter (1..*), each repetition naming a ViewDefinition, SQLQuery or SQLView by subjectCanonical, subjectReference or subjectResource |
Execution Flow:
Prefer: respond-async header202 Accepted with Content-Location polling URL303 See Other with the result URL in the Location header200 OKoutput.location URLs in the manifestKey Features:
subject repetition and each producing one manifest entrypatient, group and _since are stated once and apply to every subjectcontext parameter supplies artifacts the server cannot itself resolve, once for the whole job however many subjects depend on themsubject repetition carries its own parameters resourceclientTrackingId is echoed in the manifest, correlating the job with the client's own recordsWhere the exported files are delivered is not constrained by this
specification. Servers commonly write them to object storage or to a local file
system and expose them through the output.location download URLs; the manifest
is the only interface this specification defines. Any storage product named
elsewhere on this page is an informative example, not a requirement.
Language: en
URL: [base]/$sql-export
| Use | Name | Scope | Cardinality | Type | Binding | Documentation |
| IN | subject | 1..* | One or more artifacts to export, in any mixture of ViewDefinitions, SQLQuery Libraries and SQLView Libraries. Each repetition names a single subject and produces exactly one output entry in the manifest. A request supplying no subject is rejected with 400 Bad Request, as is a request in which two repetitions would produce the same output name. | |||
| IN | subject.name | 0..1 | string | Name for this subject's output entry in the manifest. Where it is omitted the server uses the subject's own name element, and where the subject declares none, a server-generated identifier. Output names are unique across the job. | ||
| IN | subject.subjectCanonical | 0..1 | canonical (View Definition, SQL Query Library, SQL View Library) | Canonical URL of the ViewDefinition, SQLQuery Library or SQLView Library to export, optionally with a |version suffix pinning a version. Exactly one of subjectCanonical, subjectReference and subjectResource is supplied in each repetition. | ||
| IN | subject.subjectReference | 0..1 | Reference (View Definition, SQL Query Library, SQL View Library) | Literal location of the subject to export: a relative URL on this server, or an absolute URL. Not a canonical URL; use subjectCanonical for that. Exactly one of subjectCanonical, subjectReference and subjectResource is supplied in each repetition. | ||
| IN | subject.subjectResource | 0..1 | CanonicalResource (View Definition, SQL Query Library, SQL View Library) | Inline ViewDefinition, SQLQuery Library or SQLView Library to export. Exactly one of subjectCanonical, subjectReference and subjectResource is supplied in each repetition. The declared type is CanonicalResource because ViewDefinition is a logical model rather than a FHIR resource; see Common Operation Behavior (operations-common.html#declared-type). | ||
| IN | subject.parameters | 0..1 | Parameters | Input parameter values for this subject, bound by name to the parameters the Library declares (Library.parameter.name). Permitted only where this repetition's subject is a SQLQuery or SQLView; supplying it where the subject is a ViewDefinition is rejected with 400 Bad Request, because a ViewDefinition declares no parameters. | ||
| IN | context | 0..* | CanonicalResource (View Definition, SQL View Library) | Supporting artifacts the server cannot itself resolve, supplied inline and matched by canonical URL against the dependencies in the subjects' transitive relatedArtifact graphs. Applies to the job as a whole rather than to one subject, so an artifact several subjects depend on is supplied once. Accepts inline resources only; there is no context by canonical URL, because a URL is exactly what the server has already failed to resolve. A context entry produces no output entry. See Common Operation Behavior (operations-common.html#context). | ||
| IN | clientTrackingId | 0..1 | string | Client-provided tracking identifier for the export job, echoed in the manifest. | ||
| IN | _format | 0..1 | code | Export Output Format Codes (Extensible) | Output format for the exported files (csv, ndjson, parquet, json). Optional; if omitted, the server uses ndjson irrespective of Accept. Requesting fhir is rejected with 400 Bad Request, because an export produces flat files. See Common Operation Behavior (operations-common.html). | |
| IN | header | 0..1 | boolean | Include CSV headers (default true). Applies only when csv output is requested. | ||
| IN | patient | 0..* | Reference | Restrict the FHIR resources feeding every subject in the job, before projection, to the supplied patient(s). Where a subject is a SQLQuery or SQLView, that means before the SQL executes. An unresolvable patient is rejected with 400 Bad Request. See Common Operation Behavior (operations-common.html#patient-filter). | ||
| IN | group | 0..* | Reference | Restrict the FHIR resources feeding every subject in the job, before projection, to members of the supplied group(s). Where a subject is a SQLQuery or SQLView, that means before the SQL executes. An unresolvable group is rejected with 400 Bad Request. See Common Operation Behavior (operations-common.html#group-filter). | ||
| IN | _since | 0..1 | instant | Include only resources whose state changed after this instant. See Common Operation Behavior (operations-common.html#since-filter). | ||
| IN | source | 0..1 | string | External data source to read from instead of the server's own data (for example a URI or bucket name). Where a subject is a SQLQuery or SQLView, this is where the ViewDefinition tables the query selects from are found. | ||
| OUT | exportId | 1..1 | string | Server-generated identifier assigned to the export job. | ||
| OUT | clientTrackingId | 0..1 | string | Echoed client tracking identifier when provided. | ||
| OUT | status | 1..1 | code | Export Status Codes (Required) | Status of the export (accepted, in-progress, completed, cancelled, failed). | |
| OUT | location | 1..1 | uri | URL to poll for export status updates. | ||
| OUT | cancelUrl | 0..1 | uri | Optional URL for cancelling the export. | ||
| OUT | _format | 0..1 | code | Export Output Format Codes (Extensible) | Format of the exported files (echoed from input if supplied). | |
| OUT | exportStartTime | 0..1 | instant | Timestamp when the export job began. | ||
| OUT | exportEndTime | 0..1 | instant | Timestamp when the export job completed. | ||
| OUT | exportDuration | 0..1 | integer | Duration of the export in seconds. | ||
| OUT | estimatedTimeRemaining | 0..1 | integer | Estimated seconds remaining until completion. Interim polling responses only. | ||
| OUT | output | 0..* | Output information for each exported subject. Exactly one entry per subject repetition, and none for a context entry. Neither manifest order nor computation order is guaranteed; clients correlate entries by name. | |||
| OUT | output.name | 1..1 | string | Name assigned to this subject's output, derived from subject.name, else the subject's own name element, else a server-generated identifier. | ||
| OUT | output.location | 1..* | uri | Download URL(s) for the exported file(s). |
GET; a GET on the operation endpoint is rejected with
400 Bad Request.This operation follows the FHIR Asynchronous Interaction Request Pattern; the asynchronous flow is specified once in Common Operation Behavior - Asynchronous Delivery:
Prefer: respond-async header and one or more subject parameters202 Accepted with Content-Location header pointing to status URL202 Accepted while export is in progress (MAY include interim results)303 See Other with a Location header carrying the result URL and an empty body200 OK with the manifest Parameters resource (exportId, status, output, …), and a failed export returns the error status code with an OperationOutcomeoutput.location URLs in the manifestThe result resource for this operation is the manifest Parameters resource described under Output Parameters. Clients SHALL treat the status and result URLs as opaque values.
sequenceDiagram
participant C as Client
participant S as Server
rect rgb(240, 248, 255)
Note over C,S: Step 1: Kick-off
C->>S: POST /$sql-export<br/>Prefer: respond-async<br/>Body: Parameters{subject, subject, _format, ...}
S-->>C: 202 Accepted<br/>Content-Location: /status/abc123<br/>Body: Parameters{exportId, status: accepted}
end
rect rgb(245, 245, 245)
Note over C,S: Step 2: Polling (repeat while in progress)
C->>S: GET /status/abc123
S-->>C: 202 Accepted<br/>Retry-After: 10, X-Progress: 45%<br/>Body: Parameters{status: in-progress}
end
rect rgb(240, 255, 240)
Note over C,S: Step 3: Completion
C->>S: GET /status/abc123
S-->>C: 303 See Other<br/>Location: /result/abc123<br/>(empty body)
end
rect rgb(255, 255, 240)
Note over C,S: Step 4: Result fetch
C->>S: GET /result/abc123
S-->>C: 200 OK<br/>Body: Parameters{status: completed, output: [{name, location}]}
end
rect rgb(255, 248, 240)
Note over C,S: Step 5: Download
C->>S: GET /export/abc123/bp_summary.csv
S-->>C: 200 OK<br/>Content-Type: text/csv<br/>Body: patient_id,systolic,...
end
sequenceDiagram
participant C as Client
participant S as Server
C->>S: DELETE /status/abc123
S-->>C: 202 Accepted
C->>S: GET /status/abc123 (subsequent poll)
S-->>C: 404 Not Found
sequenceDiagram
participant C as Client
participant S as Server
C->>S: GET /status/abc123
S-->>C: 303 See Other<br/>Location: /result/abc123<br/>(empty body)
C->>S: GET /result/abc123
S-->>C: 500 Internal Server Error<br/>Body: OperationOutcome{severity: error, diagnostics: ...}
One invocation is one job: one set of subjects, one set of filters, one supplied
context, one manifest. Four guarantees hold across it.
One snapshot. The server SHALL compute every subject in the job against a single consistent view of the data. Two outputs of one job can therefore be joined on a shared key without a skew window, whatever changes to the data occur while the job runs. This is what makes one job different from two jobs submitted together.
No ordering. Neither the order of the output
entries in the manifest nor the order in which subjects are computed is
guaranteed, and servers MAY compute subjects in parallel. The shared
snapshot is the only consistency guarantee offered. Clients correlate manifest
entries with the subjects they requested by
output.name, never by position.
One resolution per canonical URL. A canonical URL appearing as a dependency of more than one subject is resolved once for the job, and every subject depending on it sees the same resolved artifact; see Supporting artifacts. Whether that artifact is then materialized once or several times is not constrained, consistent with what this specification already leaves to implementations.
One output per subject. The manifest carries exactly one output entry per
subject repetition and none for any other supplied artifact. A context entry
is a supporting artifact rather than a subject, so it never produces one.
The operation can export data from:
source parameterpatient, group and _since restrict the data every subject in the job sees.
They are stated once for the whole job - there is no way to scope one subject
differently from another - and carry the same meaning here as on
$sql-run, specified once in
Filtering:
patient - restrict to the patient compartments of the supplied patients (details)group - restrict to members of the supplied Groups (details)_since - restrict to resources whose state changed after the supplied instant (details)The filter applies to the FHIR resources feeding a view before projection. Where a subject is a SQLQuery or SQLView, that means it applies to the resources feeding that subject's dependency views, before the SQL executes: the SQL sees tables already narrowed to the requested scope, rather than being expected to express the filter itself.
Prefer: respond-async (required) - Requests an asynchronous responseAccept (recommended) - Specifies the format of the kick-off responseAccept (recommended) - Specifies the format of interim status responses and error responses on the status URL; the completing poll returns 303 See Other with an empty bodyAccept (recommended) - Specifies the representation of the result: the manifest Parameters resource on success, or the OperationOutcome on failureEach request's headers apply to that request's response. Because
completion is delivered as 303 See Other with an empty body, the Accept
header that governs the representation of the manifest is the one sent on the
result GET, not the one sent on the completing status poll. This allows a
client to negotiate a different representation for interim status responses
(e.g. minimal JSON) than for the final manifest if it chooses.
subject Parameter (1..*)Each repetition names a single artifact to export - a ViewDefinition, a SQLQuery
Library or a SQLView Library - and produces exactly one output entry in the
manifest. One job may name any mixture of the three. At least one subject is
required; a request supplying none is rejected with 400 Bad Request.
| Part Name | Type | Min | Max | Description |
|---|---|---|---|---|
| name | string | 0 | 1 | Name for this subject's output entry. Details |
| subjectCanonical | canonical | 0¹ | 1 | Canonical URL of the subject. Details |
| subjectReference | Reference | 0¹ | 1 | Literal location of the subject on the server. Details |
| subjectResource | ViewDefinition | SQLQuery | SQLView² | 0¹ | 1 | Inline subject resource. Details |
| parameters | Parameters | 0 | 1 | Input parameter values for this subject. Details |
¹ Exactly one of subjectCanonical, subjectReference or subjectResource is
required per subject repetition. See
Naming each subject.
² Declared as CanonicalResource in the OperationDefinition; see
Why the declared type is CanonicalResource.
parameters binds to the parameters the subject's Library declares, so it is
permitted only where that repetition's subject is a SQLQuery or SQLView.
Supplying it where the subject is a ViewDefinition is rejected with
400 Bad Request, because a ViewDefinition declares no parameters.
context ParameterA subject's dependencies are named by its relatedArtifact entries and are
normally resolved by the server. Where the server cannot resolve one - typically
because the artifact exists only on the client - the client supplies it inline
with context.
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| context | ViewDefinition | SQLView² | 0 | * | Inline supporting artifact, matched to a dependency by canonical URL. Details |
context applies to the job as a whole rather than to one subject, so an
artifact several subjects depend on is supplied once and
resolved once. The matching, precedence and error rules are
specified once in
Supporting artifacts and apply identically here
and on $sql-run. That section governs; in
outline, the supplied entries are matched by canonical URL against every
dependency of every subject in the request, a supplied entry outranks an artifact
the server could itself resolve, an entry that cannot be bound or matches nothing
is rejected with 400 Bad Request, and a dependency neither supplied nor
resolvable is rejected with 404 Not Found.
A context entry is a supporting artifact, not an export subject: it produces no
output entry in the manifest, which carries one entry per subject and nothing
else.
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| clientTrackingId | string | 0 | 1 | Client-provided tracking ID for the export job, echoed in the manifest |
| _format | code | 0 | 1 | Output format: csv, ndjson, parquet, json. Details |
| header | boolean | 0 | 1 | Include CSV headers (default true). Applies only when csv output is requested |
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| patient | Reference | 0 | * | Filter by patient reference. Details |
| group | Reference | 0 | * | Filter by group membership. Details |
| _since | instant | 0 | 1 | Include only resources whose state changed after this instant. Details |
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| source | string | 0 | 1 | External data source (e.g., URI, bucket name). If absent, uses server data |
resource and _limit are not offered on this operation, and fhir is not an
available output format; see
Parameters that do not apply to every operation.
A server that does not support a parameter declares that through the mechanism described in Declaring partial operation support, and rejects a request supplying it as specified there.
Each subject repetition names the artifact to export in exactly one of three
ways, each with its own part so that the intended meaning is carried by the
part's type rather than inferred from the shape of a string. All three admit a
ViewDefinition, a
SQLQuery Library or a
SQLView Library, so the naming form is chosen
independently of the subject's kind:
| Part | Type | Names the subject by |
|---|---|---|
subject.subjectCanonical |
canonical |
Its canonical URL, optionally with a \|version suffix pinning a version (e.g. http://example.org/Library/bp-summary\|2.1.0). Absent a suffix, the server selects a version according to FHIR's canonical resolution rules |
subject.subjectReference |
Reference |
A literal location: a relative URL on this server (e.g. Library/patient-bp-query) or an absolute URL (e.g. http://example.org/fhir/Library/patient-bp-query). This is not a canonical URL |
subject.subjectResource |
ViewDefinition | SQLQuery | SQLView² | Carrying the artifact itself in the request |
² subjectResource is declared as CanonicalResource in the
OperationDefinition. ViewDefinition is a logical model in this guide rather than
a FHIR resource, so ViewDefinition is not a value parameter.type accepts;
CanonicalResource is the narrowest declared type admitting all three kinds, and
the real constraint is carried by targetProfile. See
Why the declared type is CanonicalResource.
Each subject repetition SHALL supply exactly
one of the three. Supplying none, or more than one, in a single repetition
is rejected with 400 Bad Request and an OperationOutcome naming the problem.
A subject.subjectCanonical or subject.subjectReference the server cannot
resolve is rejected with 404 Not Found and an OperationOutcome. A resolved
artifact conforming to none of the three profiles is rejected with
422 Unprocessable Entity.
How a server resolves a canonical URL or an absolute reference - from a local artifact registry, by dereferencing the URL, or not at all - is an implementation matter. A server that supports only some of these parts declares the subset it supports as described in Declaring partial operation support.
The supported formats (json, ndjson, csv, parquet) and the default are
defined in
Common Operation Behavior and apply to
this operation. The fhir format is available on the run operation only, because
an export produces flat files; requesting it here is rejected with
400 Bad Request.
json, ndjson and
csv by default; servers MAY support parquet, and SHALL document supported
formats in the CapabilityStatement._format is omitted, the server SHALL produce
the export output in ndjson format, irrespective of Accept._format is supplied, its value SHALL take
precedence over Accept (which here negotiates the format of the
status and result responses, not the exported files).patient, group and _since carry the same meaning on both data operations,
and are specified once in
Filtering:
patient,
group and
_since. On this operation they are
stated once and apply to every subject in the job.
Parameter values are passed as a nested Parameters resource within each
subject repetition, following the same pattern as
$sql-run and the
CQL $evaluate operation.
Binding is per subject because one job may carry several subjects, each declaring
its own parameters, so a single top-level set would be ambiguous across them.
See Parameter Types on the
SQLQuery profile for the binding rules and the mapping from
Library.parameter.type to the value[x] element to use. A parameter name the
subject does not declare, or a value whose type does not match the declared type,
is rejected with 400 Bad Request and an OperationOutcome naming the
parameter.
Output parameters form the manifest - the Parameters resource returned
with 200 OK from the result URL after the completing poll's 303 See Other
redirect. They are not present in the 202 Accepted responses returned while
the export is still in progress.
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| exportId | string | 1 | 1 | Server-generated export ID |
| clientTrackingId | string | 0 | 1 | Client-provided tracking ID (echoed from input if provided) |
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| _format | code | 0 | 1 | The format of the exported files (echoed from input if provided) |
| exportStartTime | instant | 0 | 1 | When the export job began |
| exportEndTime | instant | 0 | 1 | When the export job completed |
| exportDuration | integer | 0 | 1 | The actual duration of the export in seconds |
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| output | complex | 0 | * | Output information for each exported subject: exactly one entry per subject, and none for a context entry |
| output.name | string | 1 | 1 | The name of the exported output. Details |
| output.location | uri | 1 | * | URL(s) to download the exported file(s). Details |
During status polling (202 Accepted responses), servers MAY include the following in the response body:
| Name | Type | Min | Max | Description |
|---|---|---|---|---|
| exportId | string | 0 | 1 | Server-generated export ID |
| estimatedTimeRemaining | integer | 0 | 1 | Estimated seconds until completion |
Servers MAY also include partial/interim results during polling. The format of interim responses is implementation-defined.
output.name identifies which subject an entry belongs to. Because the manifest
states no ordering, it is the only way a client correlates an entry with the
subject it requested. The value is determined in three steps:
name was supplied in that subject repetition, the server SHOULD use itname elementOutput names SHALL be unique across the job.
A request in which two subject repetitions would produce the same
output.name is rejected with 400 Bad Request and an OperationOutcome whose
expression names subject, because manifest entries a client cannot tell apart
are of no use to it. Where the client names its subjects explicitly the collision
is visible in the request; where it does not, the collision is between two
subjects whose own name elements agree, and supplying an explicit name on
either resolves it.
For large exports, servers MAY partition the output into multiple files. When partitioning occurs:
output.location parameter can repeat within a single output entryfilename.part1.parquet, filename.part2.parquet)Example of partitioned output:
{
"name": "output",
"part": [
{
"name": "name",
"valueString": "bp_summary"
},
{
"name": "location",
"valueUri": "https://example.com/export/123/bp_summary.part1.csv"
},
{
"name": "location",
"valueUri": "https://example.com/export/123/bp_summary.part2.csv"
}
]
}
Clients SHALL download all parts to obtain the complete dataset.
| Status Code | Meaning |
|---|---|
| 202 Accepted | Kick-off accepted, export still in progress during polling, or cancellation accepted |
| 303 See Other | Export finished, successfully or not; Location header carries the result URL |
| 200 OK | Result URL returns the manifest Parameters; download URLs return the files |
| Status | issue.code |
expression |
Condition |
|---|---|---|---|
400 Bad Request |
required |
- | Prefer: respond-async absent, or the operation invoked with GET |
400 Bad Request |
required |
subject |
No subject supplied |
400 Bad Request |
invalid |
subject |
A repetition supplying none of the three naming forms, or more than one |
400 Bad Request |
invalid |
subject |
Two repetitions that would produce the same output.name |
400 Bad Request |
invalid |
parameters |
Supplied where that repetition's subject is a ViewDefinition |
400 Bad Request |
invalid |
parameters |
A parameter name the subject does not declare, or a value whose type does not match the declared type |
400 Bad Request |
invalid |
context |
An entry with no url, two entries sharing a url, or an entry matching no dependency of any subject |
400 Bad Request |
not-found |
patient |
A patient naming a resource the server cannot find (see Status code for a value that cannot be resolved) |
400 Bad Request |
not-found |
group |
A group naming a resource the server cannot find |
400 Bad Request |
not-supported |
the parameter | A parameter the server does not support (see Declaring partial operation support) |
400 Bad Request |
not-supported |
_format |
A format the server does not support |
400 Bad Request |
invalid |
_format |
fhir requested, which this operation does not offer |
400 Bad Request |
invalid |
_limit |
Supplied, which this operation does not offer |
404 Not Found |
not-found |
subject |
An unresolvable subjectCanonical or subjectReference |
404 Not Found |
not-found |
- | A dependency neither supplied as a context entry nor resolvable by the server |
404 Not Found |
not-found |
- | A status URL for a cancelled job |
422 Unprocessable Entity |
invalid |
subject |
A resolved artifact conforming to none of ViewDefinition, SQLQuery or SQLView |
422 Unprocessable Entity |
invalid |
subject |
A conformant subject that cannot be processed, such as an SQL syntax error or an invalid FHIRPath expression |
429 Too Many Requests |
throttled |
- | Excessive polling; back off exponentially, guided by Retry-After |
500 Internal Server Error |
exception |
- | Unexpected server error; on the result URL, the failure outcome of the job |
All error responses (4xx and 5xx) SHOULD include an OperationOutcome resource providing details about the error.
Where a request carries both an unresolvable subject and an unresolvable filter
value, the subject failure is the more fundamental: the response is
404 Not Found and the OperationOutcome reports both issues.
Invalid requests are rejected synchronously at kick-off - bad or unsupported
parameters, authorization failures, unresolvable subjects, unresolvable
dependencies and unmatched context entries alike. Rejection is never deferred
to the status URL. The status endpoint reflects polling machinery only; it never
communicates the job's outcome, which is why a finished job returns
303 See Other whether it succeeded or failed.
When the server does not support certain parameters, it returns 400 Bad Request:
HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-supported",
"diagnostics": "The server does not support the 'source' parameter"
}
]
}
Two subject repetitions naming the same output leave the client unable to tell
the manifest entries apart:
HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invalid",
"diagnostics": "Two subject repetitions would produce the output name 'demographics'",
"expression": ["subject"]
}
]
}
When a named subject does not exist:
HTTP/1.1 404 Not Found
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-found",
"diagnostics": "Subject with reference 'Library/non-existent' not found",
"expression": ["subject.subjectReference"]
}
]
}
When a supplied parameter value type does not match the declared Library.parameter.type:
HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "invalid",
"diagnostics": "Parameter 'from_date' expects type 'date' but received 'valueString'",
"expression": ["subject.parameters"]
}
]
}
A job carries several subjects, so servers
SHOULD validate all of them before starting the export and report every problem
in one OperationOutcome, rather than failing on the first:
HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-found",
"diagnostics": "Subject 'patient-vitals' not found",
"expression": ["subject[1].subjectCanonical"]
},
{
"severity": "error",
"code": "invalid",
"diagnostics": "Subject 'lab-results' contains an invalid resource type",
"expression": ["subject[2].subjectResource"]
}
]
}
When filtering by patient or group that doesn't exist. A filter value scopes the
data rather than naming what the operation is about, so the rejection is
400 Bad Request; see
Status code for a value that cannot be resolved.
HTTP/1.1 400 Bad Request
Content-Type: application/fhir+json
{
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "not-found",
"diagnostics": "Patient with reference 'Patient/12345' not found",
"expression": ["patient"]
}
]
}
POST [base]/$sql-export with Prefer: respond-async header and one or more subject parameters.202 Accepted status codeContent-Location header with the absolute URL for subsequent status requests (polling location)status parameter set to accepted and location parameter4xx status code and an OperationOutcome resource in the body.202 Accepted with optional Parameters resource for interim statusX-Progress header to indicate completion percentageRetry-After header to indicate when to retry429 Too Many Requests; clients SHOULD apply exponential backoff303 See Other status codeLocation header with the absolute result URLGET:
200 OK with the manifest Parameters resource in the body
containing status = completed, the export metadata, and one output
entry per subject with its download locations500 Internal Server Error) with an OperationOutcome body explaining
the failure; repeated fetches return the same outcome within the validity
windowDELETE request to the status polling URL202 Accepted404 Not Foundoutput.location download URLs SHALL remain valid for at least 24 hours after
export completion:
Expires header to indicate when the URLs expire401 Unauthorized or 403 Forbiddenoutput.location parameters.The motivating case: two ViewDefinitions and one SQLQuery exported as one job, filtered to two patients, as CSV. The three subjects are named by canonical URL, by literal reference, and by canonical URL with bound parameters respectively, showing that the naming form is chosen per subject and independently of the subject's kind.
Step 1: Kick-off Request
POST /$sql-export HTTP/1.1
Host: example.com
Content-Type: application/fhir+json
Prefer: respond-async
Accept: application/fhir+json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
{
"resourceType": "Parameters",
"parameter": [
{
"name": "clientTrackingId",
"valueString": "bundle-2026-08"
},
{
"name": "subject",
"part": [
{ "name": "name", "valueString": "demographics" },
{
"name": "subjectCanonical",
"valueCanonical": "http://example.org/ViewDefinition/patient_demographics|2.1.0"
}
]
},
{
"name": "subject",
"part": [
{ "name": "name", "valueString": "encounters" },
{
"name": "subjectReference",
"valueReference": { "reference": "ViewDefinition/encounters" }
}
]
},
{
"name": "subject",
"part": [
{ "name": "name", "valueString": "bp_summary" },
{
"name": "subjectCanonical",
"valueCanonical": "http://example.org/Library/bp-summary|1.0.0"
},
{
"name": "parameters",
"resource": {
"resourceType": "Parameters",
"parameter": [
{ "name": "min_systolic", "valueInteger": 140 }
]
}
}
]
},
{ "name": "patient", "valueReference": { "reference": "Patient/123" } },
{ "name": "patient", "valueReference": { "reference": "Patient/456" } },
{ "name": "_format", "valueCode": "csv" }
]
}
The two patient filters apply to all three subjects; there is no way to scope
one subject differently from another.
Step 2: Kick-off Response
The server accepts the request and provides one polling location for the whole job:
HTTP/1.1 202 Accepted
Content-Location: https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/status
Content-Type: application/fhir+json
{
"resourceType": "Parameters",
"parameter": [
{
"name": "exportId",
"valueString": "550e8400-e29b-41d4-a716-446655440000"
},
{
"name": "clientTrackingId",
"valueString": "bundle-2026-08"
},
{
"name": "status",
"valueCode": "accepted"
},
{
"name": "location",
"valueUri": "https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/status"
}
]
}
Step 3: Status Poll (In Progress)
GET /fhir/export/550e8400-e29b-41d4-a716-446655440000/status HTTP/1.1
Host: example.com
Accept: application/fhir+json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
HTTP/1.1 202 Accepted
Content-Type: application/fhir+json
Retry-After: 10
X-Progress: 65%
{
"resourceType": "Parameters",
"parameter": [
{
"name": "exportId",
"valueString": "550e8400-e29b-41d4-a716-446655440000"
},
{
"name": "clientTrackingId",
"valueString": "bundle-2026-08"
},
{
"name": "status",
"valueCode": "in-progress"
},
{
"name": "location",
"valueUri": "https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/status"
},
{
"name": "exportStartTime",
"valueInstant": "2026-08-03T14:30:00Z"
},
{
"name": "estimatedTimeRemaining",
"valueInteger": 25
}
]
}
Step 4: Final Status Poll (Completed)
The export has finished, so the status poll returns 303 See Other with the
result URL in the Location header and no body:
GET /fhir/export/550e8400-e29b-41d4-a716-446655440000/status HTTP/1.1
Host: example.com
Accept: application/fhir+json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
HTTP/1.1 303 See Other
Location: https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/result
Step 5: Fetch the Result
The client fetches the result URL; the manifest Parameters resource is
returned:
GET /fhir/export/550e8400-e29b-41d4-a716-446655440000/result HTTP/1.1
Host: example.com
Accept: application/fhir+json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
HTTP/1.1 200 OK
Content-Type: application/fhir+json
Expires: Tue, 04 Aug 2026 14:31:15 GMT
{
"resourceType": "Parameters",
"parameter": [
{
"name": "exportId",
"valueString": "550e8400-e29b-41d4-a716-446655440000"
},
{
"name": "clientTrackingId",
"valueString": "bundle-2026-08"
},
{
"name": "status",
"valueCode": "completed"
},
{
"name": "_format",
"valueCode": "csv"
},
{
"name": "exportStartTime",
"valueInstant": "2026-08-03T14:30:00Z"
},
{
"name": "exportEndTime",
"valueInstant": "2026-08-03T14:31:15Z"
},
{
"name": "exportDuration",
"valueInteger": 75
},
{
"name": "output",
"part": [
{ "name": "name", "valueString": "demographics" },
{
"name": "location",
"valueUri": "https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/demographics.csv"
}
]
},
{
"name": "output",
"part": [
{ "name": "name", "valueString": "encounters" },
{
"name": "location",
"valueUri": "https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/encounters.csv"
}
]
},
{
"name": "output",
"part": [
{ "name": "name", "valueString": "bp_summary" },
{
"name": "location",
"valueUri": "https://example.com/fhir/export/550e8400-e29b-41d4-a716-446655440000/bp_summary.csv"
}
]
}
]
}
One exportId, one echoed clientTrackingId, and exactly three output
entries - one per subject. The manifest states no order, so the client finds each
entry by name. All three were computed against one snapshot, so bp_summary
joins to demographics on the patient key without a skew window.
Step 6: Download Files
The client downloads each file:
GET /fhir/export/550e8400-e29b-41d4-a716-446655440000/bp_summary.csv HTTP/1.1
Host: example.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGc...
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="bp_summary.csv"
patient_id,systolic,effective_date
Patient/123,145,2026-01-15
Patient/456,152,2026-01-20
Pass the artifact inline for an ad-hoc export, with nothing stored on the server:
POST /$sql-export HTTP/1.1
Host: example.com
Content-Type: application/fhir+json
Prefer: respond-async
{
"resourceType": "Parameters",
"parameter": [
{ "name": "_format", "valueCode": "ndjson" },
{
"name": "subject",
"part": [
{ "name": "name", "valueString": "active-patients" },
{
"name": "subjectResource",
"resource": {
"resourceType": "Library",
"meta": { "profile": ["http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/SQLQuery"] },
"type": { "coding": [{ "system": "http://hl7.org/fhir/uv/sql-on-fhir/CodeSystem/LibraryTypesCodes", "code": "sql-query" }] },
"status": "active",
"relatedArtifact": [
{ "type": "depends-on", "resource": "https://example.org/ViewDefinition/patient_view", "label": "p" }
],
"content": [{
"contentType": "application/sql",
"data": "U0VMRUNUIHAuaWQsIHAubmFtZSBGUk9NIHAgV0hFUkUgcC5hY3RpdmUgPSB0cnVl",
"extension": [{
"url": "http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/sql-text",
"valueString": "SELECT p.id, p.name FROM p WHERE p.active = true"
}]
}]
}
}
]
},
{ "name": "_since", "valueInstant": "2026-01-01T00:00:00Z" }
]
}
Two SQLQuery subjects both depend on a ViewDefinition that exists only on the
client. It is supplied once as a context entry, resolved once for the job, and
satisfies both subjects' dependencies:
POST /$sql-export HTTP/1.1
Host: example.com
Content-Type: application/fhir+json
Prefer: respond-async
{
"resourceType": "Parameters",
"parameter": [
{
"name": "subject",
"part": [
{ "name": "name", "valueString": "cohort_bp" },
{ "name": "subjectCanonical", "valueCanonical": "http://example.org/Library/cohort-bp" }
]
},
{
"name": "subject",
"part": [
{ "name": "name", "valueString": "cohort_labs" },
{ "name": "subjectCanonical", "valueCanonical": "http://example.org/Library/cohort-labs" }
]
},
{
"name": "context",
"resource": {
"resourceType": "ViewDefinition",
"url": "https://example.org/ViewDefinition/local_cohort",
"status": "active",
"resource": "Patient",
"select": [
{
"column": [
{ "name": "id", "path": "getResourceKey()", "type": "string" }
]
}
]
}
},
{ "name": "_format", "valueCode": "csv" }
]
}
The completed manifest carries exactly two output entries, cohort_bp and
cohort_labs. The supplied context entry produces none. Had the server been
able to resolve local_cohort itself, the supplied entry would still take
precedence.