Personal Health Records, published by HL7 International / Patient Empowerment. This guide is not an authorized publication; it is the continuous build for version 1.0.0-ballot2 built by the FHIR (HL7® FHIR® Standard) CI Build. This version is based on the current content of https://github.com/HL7/personal-health-record-format-ig/ and changes regularly. See the Directory of published versions
| Page standards status: Informative |
Systems MAY wish to implement standard APIs for generating a .phr or .sphr file. Standard API queries that have been used in other systems are listed below.
# export everything from a single-user system as a FHIR Bundle
GET /Bundle/$phr-export
# export everything from a single-user system as an NDJSON Bulkd Data file
GET /Bundle/$phr-export?outputFormat=ndjson
# export everything from a single-user system as a PHR file
GET /Bundle/$phr-export?outputFormat=phr
# export everything from a single-user system as a SPHR file with security
GET /Bundle/$phr-export?outputFormat=sphr
# export everything from a specific date to current from a single-user system
GET /Bundle/$phr-export?start=2010
# export everything in a specific date range from a single-user system
GET /Bundle/$phr-export?start=2010&end=2020-06
# export everything for a specific patient in a multi-user system
GET /Bundle/$phr-export?patient=Patient/12345
# post a record to another system to be imported (NDJSON format)
POST /Bundle/$import
<<<<<<< HEAD
Systems MUST post the API endpoints they use in the system's CapabilityStatement.
=======
Import Considerations:
Export operations support multiple response formats:
| Format | MIME Type | Description |
|---|---|---|
| Bundle | application/fhir+json |
Standard FHIR Bundle resource |
| NDJSON | application/x-ndjson |
Newline-delimited JSON, one resource per line |
| PHR | application/x-ndjson |
Same as NDJSON with .phr extension |
| SPHR | application/zip |
Encrypted zip containing .phr file(s) plus supporting documents |
Systems MUST advertise supported operations in their CapabilityStatement:
{
"resourceType": "CapabilityStatement",
"rest": [{
"mode": "server",
"operation": [
{
"name": "phr-export",
"definition": "http://hl7.org/fhir/uv/phr/OperationDefinition/phr-export"
},
{
"name": "import",
"definition": "http://hl7.org/fhir/uv/phr/OperationDefinition/phr-import"
}
]
}]
}
SMART Health Links (SHLinks) provide a mechanism for patients to share their health records via QR codes or short URLs. A SMART Health Link encodes an API endpoint URL along with a decryption key, enabling secure, convenient data sharing without requiring pre-established technical connectivity.
A SMART Health Link QR code encodes a URL in the format:
shlink:/eyJ1cmwiOiJodHRwczovL...
When scanned, the QR code provides access to an API endpoint that returns encrypted health data. The decryption key is embedded in the link itself.
A SMART Health Link URL payload contains:
{
"url": "https://example.org/api/shl/abc123",
"key": "rxTgYlOaKJPFtcEd0qcceN8wEU4p94SqAwIWQe6uX7Q",
"exp": 1735689600,
"flag": "LP",
"label": "Patient Health Summary"
}
| Field | Description |
|---|---|
url |
API endpoint where encrypted payload can be retrieved |
key |
Base64url-encoded decryption key (256-bit AES-GCM) |
exp |
Optional expiration time (Unix timestamp) |
flag |
L = long-term, P = passcode required, U = single use |
label |
Human-readable description |
SMART Health Links use A256GCM (AES-256 in Galois/Counter Mode). The payload retrieved from the URL is a JWE (JSON Web Encryption) that can be decrypted using the embedded key.
When sharing via SMART Health Link:
While the .phr format is primarily designed for file-based storage and exchange, systems may also transmit PHR data directly over HTTP connections using streaming techniques.
When transmitting .phr content over HTTP, use the following headers:
Content-Type: application/x-ndjson
Content-Disposition: attachment; filename="patient-record.phr"
X-PHR-Version: 1.0
For FHIR-aware systems:
Content-Type: application/fhir+ndjson
NDJSON format is well-suited for streaming because each line is a complete, parseable JSON object. Receivers can process records as they arrive without waiting for the complete transmission, enabling:
Server (streaming response):
// Node.js example
response.setHeader('Content-Type', 'application/x-ndjson');
response.setHeader('Transfer-Encoding', 'chunked');
for await (const resource of patientResources) {
response.write(JSON.stringify(resource) + '\n');
}
response.end();
Client (streaming consumption):
// Process each line as it arrives
const readline = require('readline');
const rl = readline.createInterface({ input: response });
rl.on('line', (line) => {
const resource = JSON.parse(line);
processResource(resource);
});
| Aspect | PHR Format | Bulk Data IG |
|---|---|---|
| File structure | Single heterogeneous .ndjson | Separate file per resource type |
| Typical use | Single patient export | Multi-patient population export |
| Resource ordering | Patient-centric, mixed types | Grouped by resourceType |
| Streaming | Well-suited | Designed for batch |
The PHR format uses a single file with mixed resource types for patient-centric simplicity, while Bulk Data separates by type (Observation.ndjson, Condition.ndjson, etc.) for scalability with large populations.
For streaming transfers, errors may occur mid-stream. Recommended approach:
{"resourceType":"OperationOutcome","issue":[{"severity":"error","code":"processing","diagnostics":"Error at line 847: invalid reference"}]}
Include OperationOutcome resources inline to indicate processing errors while allowing the stream to continue for partial data recovery.
PHR systems should support flexible filtering to allow patients and applications to retrieve specific subsets of data.
All standard FHIR search parameters apply. Common patterns for PHR queries:
# Resources modified since a date
GET /Observation?_lastUpdated=gt2025-01-01
# Resources within a date range
GET /Observation?date=ge2024-01-01&date=le2024-12-31
# Specific resource types
GET /Condition?patient=Patient/123
# By category
GET /Observation?category=vital-signs
GET /Observation?category=laboratory
GET /Observation?category=activity
Filter by originating system:
GET /Observation?_source=urn:ehr:hospital-xyz
GET /Observation?_source=urn:device:fitbit
GET /Observation?_source=urn:phr:patient-entered
Filter for active/current data (suitable for IPS generation):
GET /Condition?clinical-status=active
GET /MedicationStatement?status=active
GET /AllergyIntolerance?clinical-status=active
Distinguish verified vs unverified data:
GET /Condition?verification-status=confirmed
GET /Observation?_tag=clinician-verified
When using the $phr-export operation:
# Export only specific resource types
GET /Patient/123/$phr-export?_type=Condition,MedicationStatement,AllergyIntolerance
# Export data from a specific time period
GET /Patient/123/$phr-export?_since=2024-01-01&_until=2024-12-31
# Export only clinical data (exclude device/activity data)
GET /Patient/123/$phr-export?_profile=clinical
# Export only for sharing (IPS-compatible subset)
GET /Patient/123/$phr-export?_profile=ips-compatible
PHRs may implement patient-controlled sharing filters using Consent resources:
{
"resourceType": "Consent",
"id": "sharing-preferences",
"status": "active",
"scope": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/consentscope",
"code": "patient-privacy"
}]
},
"provision": {
"type": "deny",
"provision": [
{
"type": "permit",
"class": [
{"code": "Condition"},
{"code": "MedicationStatement"},
{"code": "AllergyIntolerance"}
]
}
]
}
}
For large result sets:
GET /Observation?_count=100&_offset=0
Response includes pagination links:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1250,
"link": [
{"relation": "self", "url": "...?_count=100&_offset=0"},
{"relation": "next", "url": "...?_count=100&_offset=100"}
]
}
Request only specific elements to reduce payload size:
GET /Observation?_elements=code,valueQuantity,effectiveDateTime