In FHIR R4, a Bundle is a container for resources. Two Bundle types look similar on the wire but behave very differently on the server: transaction and batch. Pick the wrong one and you either lose atomicity you needed — or pay for a rollback you did not.
Both are posted to the server base URL:
POST [base] Content-Type: application/fhir+json
with Bundle.type set to transaction or batch, and each entry carrying a request (method + url). Sources: FHIR R4 Bundle, RESTful API — batch/transaction.
| transaction | batch | |
|---|---|---|
| Atomic? | Yes — all succeed or the whole set fails | No — each entry stands alone |
| HTTP outcome | 200 + transaction-response, or 4xx/5xx with an OperationOutcome (no partial apply) | 200 if the batch was processed; check each entry’s response status |
| Response Bundle.type | transaction-response | batch-response |
| Inter-entry references | Supported (including urn:uuid: fullUrls rewritten to new ids) | Creating references between new entries in the same batch is non-conformant |
Conditional references (Patient?identifier=…) | Allowed (transaction only) | Not for resolving create-time links |
Harbor Engine’s FHIR practice ticket creates a Patient and an Encounter together. That must be a transaction: if Encounter create fails, the Patient must not linger alone.
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:11111111-1111-1111-1111-111111111111",
"resource": {
"resourceType": "Patient",
"identifier": [{ "system": "http://penford.example/mrn", "value": "PMC-10042" }],
"name": [{ "family": "Nguyen", "given": ["Alex"] }]
},
"request": { "method": "POST", "url": "Patient" }
},
{
"fullUrl": "urn:uuid:22222222-2222-2222-2222-222222222222",
"resource": {
"resourceType": "Encounter",
"status": "in-progress",
"class": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB" },
"subject": { "reference": "urn:uuid:11111111-1111-1111-1111-111111111111" }
},
"request": { "method": "POST", "url": "Encounter" }
}
]
}On success the server returns a Bundle with "type": "transaction-response" and per-entry response.status / location. On failure you get an OperationOutcome, not a half-applied pair.
Use batch when entries must not depend on each other — for example, fetching several Patients by id in one round trip, or posting independent Observations that should not roll back together. The overall HTTP status can be 200 even when some entries return 404 or 422; always read entry.response.status.
Try a sample in the free FHIR viewer (Insights explain Bundle type and entries), or practice in the Sandbox.