> ## Documentation Index
> Fetch the complete documentation index at: https://docs.maxcare.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Live EHR reads

> Read a patient's chart straight from the practice's EHR with /v4/ehr

Most of the API serves our synced copy of the practice's data. It is fast, but a note signed or a file
uploaded a few minutes ago may not be there yet. The `/v4/ehr` endpoints read **live from the practice's
EHR** instead, the same way on ModMed (EMA) and EZDerm. They are read-only: nothing you read here is
saved to our copy, and nothing here changes the synced endpoints.

| Use | When |
| - | - |
| Synced endpoints (`/v4/patients`, `/v4/appointments`, `/v4/notes`, …) | Lists, search, change detection, webhooks |
| `/v4/ehr/*` | You need what the EHR says right now: a just-signed note, a file uploaded today, today's balance |

## Patient reads

All patient routes live under `GET /v4/ehr/patients/{patientId}/…`. `patientId` is our `pat_` id (find
a patient the EHR knows but we have not synced yet with `POST /v4/patients/match`).

| Route | Returns | Scope |
| - | - | - |
| `appointments` | Every appointment, past and future. Optional `from`, `to` (real `YYYY-MM-DD` dates, else `400`), `status`. | `read:appointments` |
| `encounters` | Every visit, any note status. Optional `from`, `to` (real `YYYY-MM-DD` dates, else `400`), `statuses`. | `read:encounters` |
| `encounters/{ref}` | One visit | `read:encounters` |
| `encounters/{ref}/status` | The visit's note status | `read:encounters` |
| `encounters/{ref}/soap-note` | The visit note | `read:soap_notes` |
| `encounters/{ref}/addenda` | Addenda signed after the note | `read:soap_notes` |
| `encounters/{ref}/final-pdf` | The final note PDF (bytes) | `read:soap_notes` |
| `documents` | The chart's files | `read:patient_documents` |
| `documents/{ref}/content` | One file's bytes | `read:patient_documents` |
| `medical-history?encounterRef=` | Allergies, medications and history sections for that visit | `read:clinical_data` |
| `insurances` | Active insurance policies | `read:insurance_policies` |
| `insurances/{policyRef}/prior-authorizations` | Prior authorizations on one policy | `read:prior_authorizations` |
| `lab-results` | Lab and pathology results, paged (`page`, `pageSize` ≤ 100) | `read:clinical_data` |
| `alerts` | Practice-management alerts | `read:clinical_data` |
| `sticky-note` | The chart's sticky note | `read:clinical_data` |
| `consents?encounterRef=` | Consents still to sign | `read:patient_documents` |
| `consents/{consentRef}/pdf` | One consent form (bytes) | `read:patient_documents` |
| `ledger` | Balance and open charges | `read:bills` |
| `merge-history` | Charts merged into or out of this one; `relationship` and `status` are ModMed's own words, passed through as is | `read:patients` |
| `demographics` | The EHR's patient record. `include=address,contact` needs `read:patient_pii`. | `read:patients` |

### `id` and `ref`

Every item in a list carries two identifiers:

* `id` — our id (`apt_`, `nte_`, `pol_`, …) when our synced copy already has the record, otherwise `null`.
* `ref` — an opaque token that always works for the sub-routes above. It is tied to this patient: a ref
  from one patient's chart returns `404` on another patient's route.

Sub-routes accept either. Store `id` when you want to join with synced data; pass `ref` back to read further.

## Other reads

| Route | Returns | Scope |
| - | - | - |
| `GET /v4/ehr/capabilities` | Which reads each of your organization's EHRs supports | any read scope |
| `GET /v4/ehr/insurance-policy-types?instanceId=` | The EHR's insurance policy types | `read:insurance_policies` |
| `GET /v4/ehr/reference-data?instanceId=` | Language, race and ethnicity code lists | `read:patients` |
| `GET /v4/ehr/catalog/{allergens\|medications\|conditions\|surgeries}?instanceId=&q=` | Catalog search (`q` ≥ 2 characters, up to 50 results) | `read:clinical_data` |

## Responses

Every JSON read returns the usual envelope with a `meta` block:

```json theme={null}
{
  "code": "success",
  "data": {
    "appointments": [{ "id": "apt_…", "ref": "ehr_…", "status": "scheduled", "startsAt": "2026-11-04T14:00:00Z" }],
    "meta": {
      "source": "live",
      "ehr": "modmed",
      "fetchedAt": "2026-10-27T15:04:05Z",
      "completeness": "complete",
      "actingAs": "user"
    }
  }
}
```

* `completeness` is `lower_bound` when the EHR's list was cut short; what you got is real, but there may be more.
* `actingAs` is `user` when the read ran on the signed-in user's own EHR login (send
  [`X-Session-Token`](https://docs.maxcare.ai/guides/appointment-finder#acting-as-the-logged-in-user)), otherwise `system`.

### Slow reads: `202`

A JSON read that takes longer than 8 seconds answers `202 Accepted` with a `Location` header
(`/v4/ehr/reads/{readId}`) and `Retry-After`. Poll that URL: it answers `202` while the read is running
and the full response once it is done. Results are kept for 10 minutes and only your app, organization
and signed-in user can read them. If the read was interrupted (for example by a deploy) the poll answers
`502 ehr_unavailable`: start the read again. EZDerm practice logins are slow (one request every 2 seconds), so
expect `202` on EZDerm unless you send `X-Session-Token`.

File routes (`final-pdf`, `documents/{ref}/content`, `consents/{consentRef}/pdf`) never answer `202`: they
stream the bytes within 55 seconds and answer `413` above 25 MB.

### Capabilities

Not every EHR offers every read. Unsupported reads answer `501 capability_unsupported` before any call to
the EHR; read `GET /v4/ehr/capabilities` once and hide those features instead of calling them.
Today EZDerm does not offer `lab-results`, consent PDFs or `merge-history`.

Each instance also reports `connected`: whether the practice's own connection to that EHR is live. When it
is `false` (the connection is paused or signed out), reads without `X-Session-Token` answer
`503 ehr_unavailable`; a signed-in user's own EHR login may still read.

## Limits

Live reads draw from your app's [share of the practice's EHR rate](https://docs.maxcare.ai/guides/rate-limits.md#ehr-rate-share).
Over it, reads answer `429` with `Retry-After`. Facility-limited API keys cannot use `/v4/ehr`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.