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 id of 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.
GET/fhir/R4/Patient/01965e2a-8c4d-7000-9001-0000000000a2/$everything?start=2026-01-01&_count=50
curl "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:

GET/fhir/R4/Patient?_count=1

Set your sandbox token above to run this request.

Then run $everything against it:

GET/fhir/R4/Patient/{{patientId}}/$everything?_count=50

Set 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.tag of scope-filtered is a fast machine signal that the result is incomplete by scope — check it before treating the Bundle as the whole record.
  • The information outcome names the withheld types (type names only — never PHI). Add the missing read scopes to the key if you need them.
  • A warning outcome (not shown above) names truncated types — a type had more rows than _count. Narrow start/end or raise _count, then re-read for the rest. A separate warning flags 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 OK resourceType is Bundle, type is searchset.
  • The first match entry is the Patient, and total equals the number of match entries.
  • Every type you hold read scope for is present (within your date window and _count).
  • If meta.tag is scope-filtered, the information outcome 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 ": ".

404 Not Found HPB-00102patient not found. The id does not name a patient in

your organization (a patient outside your organization is invisible, not forbidden). Confirm the id and the token's organization.

403 Forbidden HPB-00104insufficient scope. The token lacks the baselinesystem/Patient.rs the operation gates on. (Lacking a per-type read scope does not

cause a 403 — that type is withheld in-band instead.)

400 Bad Request HPB-00101validation error. The patient id is malformed, or a

parameter is invalid. Use a well-formed UUID and YYYY-MM-DD dates.

Next recipes