Merge operation
The $merge operation merges two FHIR resources by executing a client-provided FHIR transaction Bundle. The client controls exactly what changes are made — MDMbox executes them atomically and adds audit records.
How it works
- The client identifies a duplicate pair (source and target)
- The client builds a FHIR transaction Bundle describing the merge (update target, reassign references, delete source)
- MDMbox validates the request, adds audit resources (Task and Provenance), and executes the Bundle as a single transaction
- If anything fails, the entire transaction rolls back — including audit records
Request
POST /api/$merge
Content-Type: application/json
{
"resourceType": "Parameters",
"parameter": [
{"name": "source", "valueReference": {"reference": "Patient/duplicate-123"}},
{"name": "target", "valueReference": {"reference": "Patient/primary-456"}},
{"name": "preview", "valueBoolean": false},
{
"name": "plan",
"resource": {
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"request": {"method": "PUT", "url": "Patient/primary-456", "ifMatch": "3"},
"resource": {
"resourceType": "Patient",
"id": "primary-456",
"name": [{"given": ["John"], "family": "Smith"}],
"birthDate": "1985-03-20"
}
},
{
"request": {"method": "PUT", "url": "Encounter/enc-789", "ifMatch": "1"},
"resource": {
"resourceType": "Encounter",
"id": "enc-789",
"subject": {"reference": "Patient/primary-456"}
}
},
{
"request": {"method": "DELETE", "url": "Patient/duplicate-123"}
}
]
}
}
]
}
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
source | valueReference | Yes | The resource to be merged away (deleted) |
target | valueReference | Yes | The surviving resource |
preview | valueBoolean | No | If true, return the assembled Bundle without executing (default: false) |
plan | resource (Bundle) | Yes | FHIR transaction Bundle with the merge changes |
Plan Bundle
The plan is a standard FHIR transaction Bundle. It typically contains:
- A
PUTentry for the target resource (with updated data) PUTentries to reassign references from related resources (Encounters, Observations, etc.)- A
DELETEentry for the source resource
Use ifMatch headers (ETags) for optimistic locking. If a resource was modified between when the client read it and when the merge executes, the entire transaction rolls back with a 422 Unprocessable Entity response (code conflict).
Preview mode
Set preview to true to see the assembled Bundle (including audit resources) without executing it:
{
"resourceType": "Parameters",
"parameter": [
{"name": "source", "valueReference": {"reference": "Patient/duplicate-123"}},
{"name": "target", "valueReference": {"reference": "Patient/primary-456"}},
{"name": "preview", "valueBoolean": true},
{"name": "plan", "resource": {"resourceType": "Bundle", "type": "transaction", "entry": []}}
]
}
Preview response:
{
"resourceType": "Parameters",
"parameter": [
{
"name": "outcome",
"resource": {
"resourceType": "OperationOutcome",
"issue": [{"severity": "information", "code": "informational",
"details": {"text": "Merge plan is valid and ready to execute"}}]
}
},
{
"name": "bundle",
"resource": {
"resourceType": "Bundle",
"type": "transaction",
"entry": ["... assembled entries including audit resources ..."]
}
}
]
}
Response
On success, the response is a Parameters resource containing:
{
"resourceType": "Parameters",
"parameter": [
{
"name": "outcome",
"resource": {
"resourceType": "OperationOutcome",
"issue": [{"severity": "information", "code": "informational",
"details": {"text": "Merge completed: Patient/duplicate-123 -> Patient/primary-456"}}]
}
},
{
"name": "input-parameters",
"resource": {
"resourceType": "Parameters",
"parameter": [
{"name": "source", "valueReference": {"reference": "Patient/duplicate-123"}},
{"name": "target", "valueReference": {"reference": "Patient/primary-456"}},
{"name": "preview", "valueBoolean": false}
]
}
},
{
"name": "task",
"resource": {
"resourceType": "Task",
"id": "generated-task-id",
"status": "completed",
"code": {"coding": [{"code": "merge"}]},
"businessStatus": {"coding": [{"code": "merged"}]},
"for": {"reference": "Patient/duplicate-123"},
"focus": {"reference": "Patient/primary-456"}
}
},
{
"name": "result",
"resource": {
"resourceType": "Patient",
"id": "primary-456"
}
}
]
}
Audit trail
Every merge creates two audit resources inside the same transaction:
Task — records the merge event:
for— the source (merged away)focus— the target (survivor)businessStatus—mergedcode—merge
Provenance — records what was changed:
entity[]— versioned references to all affected resources before the mergeagent—Device/mdmbox-mergeactivity— MDATA (merge data)
These audit resources enable future unmerge by preserving the pre-merge state of every affected resource. They also power Notifications — downstream systems can subscribe to merge and unmerge events via Topic-Based Subscriptions.
Validation
MDMbox validates the merge request before execution:
Structural validation (400 Bad Request):
- Source and target must be different resources
- Plan must be a transaction Bundle with at least one entry
- No duplicate PUT/DELETE URLs in the plan
State validation (422 Unprocessable Entity):
- Both source and target must exist
- Source must not already be merged (no active merge Task)
- Target must not already be a source in another merge (no circular merges)
Finding related resources
Use the $referencing operation to discover resources that reference a given resource. This is useful when building the merge plan — you need to know which Encounters, Observations, etc. point to the source and need to be reassigned.