Fetching a patient's full record
Pull everything you are allowed to see about one patient in a single read. The Patient/$everything operation aggregates the patient's clinical record — Encounter, Observation, Composition, DocumentReference, MedicationRequest, and ServiceRequest, plus the Patient itself — into one searchset Bundle. It is read-only and scope-filtered: each resource type appears only if your token carries read scope for it, and the Bundle tells you, in-band, which types it withheld.
This is the fastest way to hydrate a record without orchestrating six separate searches. The trade-off to understand up front: the result is shaped by your scopes. A token that can read encounters but not documents gets the encounters and a machine-readable note that documents were held back — never a silent omission.
Audience
You build a record-sync, a care-summary view, or a migration that ingests a patient's full chart. You read a Bundle without a viewer, you know what a FHIR reference is, and you want one call that returns as much of a patient's record as your token is entitled to.
You'll need
- A bearer token from HuliPractice (Practice Settings → Integrations → API Keys), or a SMART Backend Services access token. See Bearer Tokens and
POST /auth/token. - system/Patient.rs at minimum — the operation gates on patient read. Then add aread scope for each type you want included:
- system/Encounter.rs and system/Observation.rs (theBAA-gated Clinical information card), plus system/MedicationRequest.rs and system/ServiceRequest.rs.
- system/Composition.rs and system/DocumentReference.rs (theBAA-gated Clinical information card). A type whose read scope is absent is withheld, not an error — the call still succeeds.
- The
idof the patient. Resolve it with a Patient search if you only hold a name. curl, or Node, Python, Java, or Go.
End state
You hold a 200 OK whose body is a searchset Bundle. The patient is the first match entry; each clinical resource you are scoped for follows as further match entries. If any type was withheld for lack of scope, the Bundle carries a meta.tag of scope-filtered and an OperationOutcome entry naming the withheld types.
Steps
1. Export the token and the patient id
export HULI_TOKEN="<paste your bearer token here>"
export PATIENT_ID="01965e2a-8c4d-7000-9001-0000000000a2"
2. Call $everything
GET the operation on the patient instance. Optional parameters narrow the result:
start/end— bound the clinical resources to a date window (YYYY-MM-DD)._type— a comma-separated list to include only specific types (e.g._type=Encounter,Observation). Omit it to include every type you are scoped for._count— the per-type page cap (default 50). When a type has more rows than the cap, the Bundle flags it as truncated.
/fhir/R4/Patient/01965e2a-8c4d-7000-9001-0000000000a2/$everything?start=2026-01-01&_count=50curl "https://api.huli.ai/fhir/R4/Patient/01965e2a-8c4d-7000-9001-0000000000a2/\$everything?start=2026-01-01&_count=50" \
-H "Authorization: Bearer $HULI_TOKEN" \
-H "Accept: application/fhir+json"
const id = process.env.PATIENT_ID!;
const params = new URLSearchParams({ start: '2026-01-01', _count: '50' });
const resp = await fetch(`https://api.huli.ai/fhir/R4/Patient/${id}/$everything?${params}`, {
headers: {
Authorization: `Bearer ${process.env.HULI_TOKEN}`,
Accept: 'application/fhir+json',
},
});
const bundle = (await resp.json()) as {
entry?: { resource: { resourceType: string }; search?: { mode: string } }[];
meta?: { tag?: { code: string }[] };
};
// match entries are the record; outcome entries carry withheld/truncation notices.
const matches = bundle.entry?.filter((e) => e.search?.mode === 'match') ?? [];
const notices = bundle.entry?.filter((e) => e.search?.mode === 'outcome') ?? [];
console.log(matches.map((e) => e.resource.resourceType));
if (bundle.meta?.tag?.some((t) => t.code === 'scope-filtered')) {
console.log('some types withheld:', notices);
}
import os
import requests
patient_id = os.environ["PATIENT_ID"]
resp = requests.get(
f"https://api.huli.ai/fhir/R4/Patient/{patient_id}/$everything",
params={"start": "2026-01-01", "_count": 50},
headers={
"Authorization": f"Bearer {os.environ['HULI_TOKEN']}",
"Accept": "application/fhir+json",
},
timeout=30,
)
bundle = resp.json()
matches = [e for e in bundle.get("entry", []) if e.get("search", {}).get("mode") == "match"]
notices = [e for e in bundle.get("entry", []) if e.get("search", {}).get("mode") == "outcome"]
print([e["resource"]["resourceType"] for e in matches])
if any(t.get("code") == "scope-filtered" for t in bundle.get("meta", {}).get("tag", [])):
print("some types withheld", notices)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PatientEverything {
public static void main(String[] args) throws Exception {
String id = System.getenv("PATIENT_ID");
String url = "https://api.huli.ai/fhir/R4/Patient/" + id
+ "/$everything?start=2026-01-01&_count=50";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + System.getenv("HULI_TOKEN"))
.header("Accept", "application/fhir+json")
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// Parse with a JSON library: match entries are the record, outcome entries
// (search.mode=outcome) carry the withheld/truncation notices.
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
id := os.Getenv("PATIENT_ID")
url := "https://api.huli.ai/fhir/R4/Patient/" + id +
"/$everything?start=2026-01-01&_count=50"
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("HULI_TOKEN"))
req.Header.Set("Accept", "application/fhir+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
// entry[].search.mode is "match" (the record) or "outcome" (withheld/truncation notices).
fmt.Printf("%d\n%s\n", resp.StatusCode, body)
}
First, resolve a real patient id in your sandbox:
/fhir/R4/Patient?_count=1Set your sandbox token above to run this request.
Then run $everything against it:
/fhir/R4/Patient/{{patientId}}/$everything?_count=50Set your sandbox token above to run this request.
3. Read the Bundle — matches, and the in-band notices
Every record resource is an entry with search.mode: "match"; the total counts only those. Any notice — withheld types, truncation — rides as an extra entry with search.mode: "outcome" carrying an OperationOutcome, and does not count toward total.
{
"resourceType": "Bundle",
"type": "searchset",
"total": 3,
"meta": {
"tag": [
{ "system": "https://fhir.huli.ai/r4/CodeSystem/bundle-tags", "code": "scope-filtered" }
]
},
"link": [
{
"relation": "self",
"url": "https://api.huli.ai/fhir/R4/Patient/01965e2a-8c4d-7000-9001-0000000000a2/$everything?start=2026-01-01&_count=50"
}
],
"entry": [
{
"fullUrl": "https://api.huli.ai/fhir/R4/Patient/01965e2a-8c4d-7000-9001-0000000000a2",
"resource": { "resourceType": "Patient", "id": "01965e2a-8c4d-7000-9001-0000000000a2" },
"search": { "mode": "match" }
},
{
"fullUrl": "https://api.huli.ai/fhir/R4/Encounter/01965e2a-8c4d-7000-9060-0000000000e9",
"resource": { "resourceType": "Encounter", "id": "01965e2a-8c4d-7000-9060-0000000000e9" },
"search": { "mode": "match" }
},
{
"fullUrl": "https://api.huli.ai/fhir/R4/Observation/01965e2a-8c4d-7000-9080-0000000000a7",
"resource": { "resourceType": "Observation", "id": "01965e2a-8c4d-7000-9080-0000000000a7" },
"search": { "mode": "match" }
},
{
"resource": {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"diagnostics": "The following resource types were withheld because the access token lacks read scope for them: DocumentReference, MedicationRequest."
}
]
},
"search": { "mode": "outcome" }
}
]
}
Three things to lift from the Bundle:
- The
meta.tagofscope-filteredis a fast machine signal that the result is incomplete by scope — check it before treating the Bundle as the whole record. - The
informationoutcome names the withheld types (type names only — never PHI). Add the missing read scopes to the key if you need them. - A
warningoutcome (not shown above) names truncated types — a type had more rows than_count. Narrowstart/endor raise_count, then re-read for the rest. A separatewarningflags any type that failed to read (partial results), so a single type's outage never fails the whole call.
What to verify
- HTTP status is
200. 200 OKresourceTypeisBundle,typeissearchset. - The first
matchentry is thePatient, andtotalequals the number ofmatchentries. - Every type you hold read scope for is present (within your date window and
_count). - If
meta.tagisscope-filtered, theinformationoutcome lists exactly the types you did not scope for.
What can go wrong
All errors return a FHIR OperationOutcome — {severity, code, diagnostics}, no details object. Branch on the HTTP status and issue[0].code; the Huli code is the prefix of issue[0].diagnostics, split on ": ".
HPB-00102 — patient not found. The id does not name a patient inyour organization (a patient outside your organization is invisible, not forbidden). Confirm the id and the token's organization.
403 ForbiddenHPB-00104 — insufficient scope. The token lacks the baselinesystem/Patient.rs the operation gates on. (Lacking a per-type read scope does notcause a 403 — that type is withheld in-band instead.)
HPB-00101 — validation error. The patient id is malformed, or aparameter is invalid. Use a well-formed UUID and YYYY-MM-DD dates.
Next recipes
- Writing and amending a clinical note — read or amend the
Compositionnotes that appear in the aggregate. - Uploading a document — add the
DocumentReferenceattachments the aggregate surfaces. - Wiring a read-only partner — the per-resource search alternative when you want to page one type at a time instead of one aggregate read.