FHIR Operations & Terminology¶
Use execute_operation for standard FHIR operations (e.g. $everything, $match, $validate) and Medplum-specific operations.
Execute operations¶
Type-level operations¶
result = client.execute_operation(
"Patient",
"match",
params={
"resourceType": "Parameters",
"parameter": [
{
"name": "resource",
"resource": {
"resourceType": "Patient",
"name": [{"family": "Smith", "given": ["John"]}],
"birthDate": "1990-01-15",
},
}
],
},
)
Instance-level operations¶
bundle = client.execute_operation(
"Patient",
"everything",
resource_id="patient-123",
)
GET for simple lookups¶
Many operations support GET with query parameters:
result = client.execute_operation(
"CodeSystem",
"lookup",
params={"code": "12345", "system": "http://loinc.org"},
method="GET",
)
Auto-wrapping Parameters¶
For convenience, wrap_params=True converts a simple dict into a FHIR Parameters resource for you:
result = client.execute_operation(
"Patient",
"custom-operation",
params={"onlyCertainMatches": True, "count": 10},
wrap_params=True,
)
Multi-tenant accounts ($set-accounts)¶
In multi-tenant MSO (Management Services Organization) setups, Medplum uses meta.accounts to assign resources to accounts — typically Organizations — which drive compartment-based access control via AccessPolicies.
$set-accounts and ProjectMembership.access are complementary halves of multi-tenancy:
set_accounts(this section) puts a resource (Patient, Encounter, …) under one or more tenant compartments.merge_project_membership_access(see ProjectMembership Access) gives a user access to those compartments via a parameterized AccessPolicy entry on theirProjectMembership.
set_accounts wraps Medplum’s $set-accounts operation:
# Assign a patient to an organization
client.set_accounts("Patient/patient-123", "Organization/org-456")
# Assign to multiple accounts (e.g., an org and a specific practitioner)
client.set_accounts(
"Patient/patient-123",
["Organization/org-456", "Practitioner/prac-789"],
)
# Propagate account assignments to all related resources
# in the patient’s FHIR compartment (Observations, Encounters, etc.)
client.set_accounts(
"Patient/patient-123",
"Organization/org-456",
propagate=True,
)
# For large compartments, use prefer_async to avoid timeouts
# (only takes effect when propagate is also True)
result = client.set_accounts(
"Patient/patient-123",
"Organization/org-456",
propagate=True,
prefer_async=True,
)
# Server returns an OperationOutcome — wait for completion
job = client.wait_for_async_job(result, timeout=60)
print(job["status"]) # "completed"
# Or check once without waiting
job = client.get_async_job_status(result)
if job["status"] == "completed":
print(job["output"])
How Medplum assigns accounts¶
Most of the time, you don’t need to set accounts yourself. Medplum handles it:
- Creating a Patient via OBO: The server assigns accounts from the
user’s AccessPolicy
compartmentfield automatically. - Creating resources linked to a Patient (Encounter, Observation,
etc.): The server copies the Patient’s
meta.accountsonto the new resource. If the Patient is in multiple orgs, the new resource inherits all of them. - Updating a resource: Accounts are preserved for Patients and re-derived from the linked Patient for other resources. The updater’s org is never added automatically.
When you need to set accounts explicitly¶
Admin/ClientApplication creating a Patient — the server has no
AccessPolicy compartment to auto-assign from, so use accounts= on
create_resource() or call set_accounts() after.
Enrolling a Patient in a second org — an admin calls
set_accounts() with both org refs. Use propagate=True to cascade
to existing related resources. $set-accounts requires projectAdmin
or superAdmin (returns 403 otherwise).
Overriding accounts on update — only admin/ClientApplication
callers can do this via accounts= on update_resource(). The server
ignores client-provided accounts from non-admin callers.
| Scenario | How accounts are set | Notes |
|---|---|---|
| OBO creates Patient | Automatic from AccessPolicy compartment | Single org |
| Any caller creates resource linked to Patient | Inherited from Patient’s accounts | Multi-org if Patient has multiple |
| Admin creates Patient or unlinked resource | accounts= or set_accounts() |
Must be explicit |
| Any caller updates a resource | Preserved (Patient) or re-derived (others) | Never re-compartmentalized to updater’s org |
Admin updates with accounts= |
Overrides accounts | Admin-only |
| Enroll Patient in additional org | Admin calls set_accounts() |
propagate=True to update existing related resources |
| Enroll Patient in new org | Admin calls set_accounts() |
Use propagate=True to update existing related resources |
Helpers for inspecting account assignments¶
from pymedplum import get_resource_accounts, resource_has_account
patient = client.read_resource("Patient", "patient-123")
# Check if a resource is assigned to a specific account
resource_has_account(patient, "Organization/org-456") # True/False
# List all account references on a resource
get_resource_accounts(patient) # ["Organization/org-456", ...]
C-CDA export¶
ccda_xml = client.export_ccda(patient_id="patient-123")
with open("patient_summary.xml", "w") as f:
f.write(ccda_xml)
Terminology helpers¶
Validate against ValueSets¶
is_valid = client.validate_valueset_code(
valueset_url="http://hl7.org/fhir/ValueSet/observation-status",
coding={
"system": "http://hl7.org/fhir/observation-status",
"code": "final",
},
)
Expand ValueSets¶
expansion = client.expand_valueset(
valueset_url="http://hl7.org/fhir/ValueSet/observation-status",
)
for concept in expansion.get("expansion", {}).get("contains", []):
print(f"{concept['code']}: {concept['display']}")
Lookup CodeSystem concepts ($lookup)¶
result = client.lookup_concept(
code="8867-4",
system="http://loinc.org",
)
Translate using ConceptMaps ($translate)¶
result = client.translate_concept(
code="final",
system="http://hl7.org/fhir/observation-status",
target_system="http://example.org/local-codes",
)