Processing Asynchronous Bundles | Medplum
On this page
Feature flag required
Asynchronous batch processing requires the async-batch feature flag on your Medplum Project. For Medplum-hosted environments, contact support@medplum.com to have it enabled.
Medplum can process a FHIR batch Bundle in the background when you ask for the asynchronous request pattern. When the batch request includes the Prefer: respond-async HTTP header, the server will respond immediately with a 202 Accepted status code and a status URL in the Content-Location response header. You should poll that URL until the job finishes, reading the status of the AsyncJob resource tracking the operation on each response. When the job succeeds, you can download the resulting batch-response Bundle payload from a linked Binary resource.
When to use asynchronous batches
Use Prefer: respond-async when:
- Large imports or migrations — The synchronous batch path is limited by the server’s general JSON body size (
maxJsonSize, often smaller) and request timeouts. The async entrypoint accepts a larger bundle body (up tomaxBatchSize, commonly 50 MB in default server configs) and runs work off the HTTP thread. - Avoiding FHIR interaction quota — The operations performed inside the background job do not consume your per-user FHIR interaction load quota the way a synchronous batch does. This is often the right choice for seeding environments, backfills, or other high-volume writes that would otherwise exhaust quota. See Rate limits for the distinction between FHIR quota and other safeguards.
- Long-running work — Work continues in a worker after the client receives
202 Accepted, so you are not tied to a single HTTP request’s lifetime.
Avoid async when:
- Callers need the
batch-responseimmediately — Data is committed only as the job runs; until then, treat the system as “in progress.” - You require a synchronous
transaction— Async transaction bundles are rejected when thetransaction-bundlesproject feature is enabled (see Limitations).
Step-by-step: submit, poll, read results
Build a batch
Bundle
Same rules as a normal batch:resourceType: "Bundle",type: "batch", and oneentryper operation. Details and examples live in FHIR Batch Requests.POSTthe bundle to the FHIR base URL withPrefer: respond-async
Issue aPOSTto your FHIR base URL (for examplehttps://api.medplum.com/fhir/R4) with:Content-Type: application/fhir+jsonAuthorization: Bearer …Prefer: respond-async
Example:
curl -X POST 'https://api.medplum.com/fhir/R4' \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/fhir+json" \ -H "Prefer: respond-async" \ -d @my-bundle.jsonRead
202 Acceptedand the status URL
A successful enqueue returns:- HTTP
202 Accepted - Response header
Content-Locationset to the status URL - A small
OperationOutcomebody whose first issue’sdiagnosticsfield is the same absolute URL
Example response body:
{ "resourceType": "OperationOutcome", "id": "accepted", "issue": [ { "severity": "information", "code": "informational", "details": { "text": "Accepted" }, "diagnostics": "https://api.medplum.com/fhir/R4/job/0192a3b4-c5d6-7890-abcd-ef1234567890/status" } ] }Save that URL (from
diagnosticsorContent-Location): it is the polling location for the rest of the workflow.- HTTP
Poll
GET …/fhir/R4/job/{id}/status
Medplum exposes the FHIR asynchronous status interaction at:GET [base]/fhir/R4/job/{job-id}/statusBehavior:
HTTP status AsyncJob.status(typical)Meaning 202accepted(or still in progress)Keep polling after a short backoff. 200completedJob finished successfully; see below for output.200errorJob failed; outputusually contains anoutcomeor error details.When the job finishes successfully, the
200response body is anAsyncJobwithstatus: "completed". Itsoutputis aParametersresource with a parameter namedresultswhose value is aReferenceto aBinary(for exampleBinary/abc). ThatBinarystores the fullbatch-responseBundleas FHIR JSON—the same shape you would have received from a synchronous batch.{ "resourceType": "AsyncJob", "id": "0192a3b4-c5d6-7890-abcd-ef1234567890", "status": "completed", "output": { "resourceType": "Parameters", "parameter": [ { "name": "results", "valueReference": { "reference": "Binary/abc" } } ] } }GETtheBinaryand parse the response bundleGETtheresultsreference fromoutput(for exampleBinary/abc). Medplum follows the Binary read rules: theAcceptheader chooses the response shape.AcceptheaderResponse application/fhir+json(or starts with that value)The Binaryresource as FHIR JSON, including base64-encodeddata. Base64-decodedataand parse the JSON as aBundlewithtype: "batch-response".Omitted, or any value that does not start with application/fhir+jsonThe stored file bytes directly. For async batch results, the stored content is FHIR JSON, so the body is the batch-responseBundle—parse it as JSON with no base64 step.Omitting
Acceptis fine and is often simpler for async batches: you receive thebatch-responsedocument directly. UseAccept: application/fhir+jsononly when you need the fullBinaryresource (for example to readcontentTypeor other metadata).Example:
# Recommended for async batch results: raw batch-response JSON curl 'https://api.medplum.com/fhir/R4/Binary/abc' \ -H "Authorization: Bearer $ACCESS_TOKEN"# Optional: Binary resource wrapper (base64 data field) curl 'https://api.medplum.com/fhir/R4/Binary/abc' \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept: application/fhir+json"Inspect each
entry.responseas documented under Response structure.
Rate limits and quotas
- FHIR interaction load (quota points) — Operations inside the background batch do not count toward FHIR quota; each status poll and result
Binaryread does (1 point per Read). Asynchronous batches are still the recommended way to run large writes that would exhaust quota synchronously. See rate limits — Avoiding quota with async batch requests. - HTTP request rate limits — Global per-IP request rate limits still apply to each HTTP call (the initial
POST, every poll, and theBinaryread). Plan backoff on polls to stay well under those caps.
Access control
Ensure the caller’s access policies allow:
- Creating and reading the enqueued
AsyncJob - Reading the outcome
Binarythat stores thebatch-response
If either is blocked, polling or downloading results will fail with an HTTP 403/404 error status even though the job ran.
Limitations
- Transaction bundles — If your project has the
transaction-bundlesfeature enabled, Medplum rejectstype: "transaction"bundles submitted withPrefer: respond-async. Use a synchronous transaction or split work into abatchasync bundle instead. - No automatic retry of failed batches — If the worker hits an unrecoverable error, the
AsyncJobmoves to a terminal error state; you must fix the bundle or data issue and submit a new job.