> ## 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.

# Change feed

> Replay every committed change to patients, identity links, appointments and bills from your own cursor, in commit order, with explicit gaps

`GET /v4/changes` is the organization's change feed. Store the cursor you last
read and resume from it; the feed never reorders behind a cursor, so a resumed
read misses nothing. The `changes.available` webhook only wakes you up — replay
is what recovers a delivery that was lost.

Scopes: `read:patients`; appointment and bill changes also need
`read:appointments` / `read:bills`.

## Request

```
GET /v4/changes?after=184467&limit=500&types=appointment.changed,appointment.removed
```

| Parameter | Rules |
| - | - |
| `after` | The `nextCursor` (or any item `cursor`) you last processed. Omit to start at the oldest retained change. |
| `limit` | 1–1000, default 500. |
| `types` | Comma-separated subset of the types below. Defaults to every type your scopes allow; asking for a type your scopes do not allow is a 403. |

Unknown parameters are a 400.

## Response

```json theme={null}
{
  "code": "success",
  "data": {
    "changes": [
      { "cursor": "184468", "type": "appointment.changed", "resourceId": "apt_9f8e…",
        "patientId": "pat_c561…", "instanceId": "ins_0b6f…", "occurredAt": "2026-10-06T04:31:02.114Z" },
      { "cursor": "184470", "type": "patient.identity_changed", "resourceId": "pat_0d9e…",
        "patientId": "pat_0d9e…", "instanceId": "ins_7a1c…", "occurredAt": "2026-10-06T04:31:05.002Z" }
    ],
    "nextCursor": "184470",
    "hasMore": false
  }
}
```

Cursors are strictly increasing per organization but not consecutive. A change
tells you *what* changed; read the resource for its state (for a group, `POST
/v4/patients/history`). `patientId` is the source chart the change belongs to,
when that chart is mirrored.

## Change types

| Type | Emitted when |
| - | - |
| `patient.changed` | Name, sex, date of birth, MRN, email, phone or address on the chart changes, its `identityReview` status changes, or the patient submits an intake (a contact source). Accounting and statement fields do not count. |
| `patient.identity_changed` | A chart joins or leaves an identity — emitted for every chart still in the group, so merges and splits reach all affected charts. It can also make a reviewed contact source stale: re-read with `include=contact`. A person resolving a pending review also emits `patient.changed` for that chart, since its `identityReview` changed. |
| `patient.contact_source_changed` | A person records which chart supplies the identity's phone, email or mailing address, or that chart's value for a decided channel changes (so the choice may now be stale) — emitted for every chart of the identity. Re-read with `include=contact` to see the new `contactSources`. The charts' `updatedAt` does not change. |
| `appointment.changed` | An appointment is created, rescheduled, re-statused (including cancellation and no-show), or moved between charts, facilities or providers. |
| `appointment.removed` | The EHR no longer has the appointment (`removedFromEhrAt` set), the mirrored row was deleted (e.g. an identity merge of two copies), or it moved to another chart — then `patientId` is the chart it left, and an `appointment.changed` names the chart it joined. An appointment rescheduled into another week can read as removed until that week is next read; it then comes back with `removedFromEhrAt` cleared and an `appointment.changed`. Treat a removal as final only after re-reading. |
| `bill.changed` | A bill is created or its status, date, chart, facility, provider or coding (groups, line items, diagnoses) changes. |
| `bill.removed` | A bill is archived, deleted, or moved to another chart (`patientId` is the chart it left). |

## Ordering and delivery guarantees

* **Committed only.** Changes are written by the database in the same
  transaction as the data change; a rolled-back write leaves no change.
* **Commit order.** A change receives its cursor after it commits, from a single
  sequencer, so once you have read cursor *N* no change with a smaller cursor can
  appear later. A sequence number taken before commit is never exposed. Changes
  sequenced in the same pass may be numbered in any order among themselves.
* **Always store `nextCursor`**, even from an empty page: when `hasMore` is
  false it is the feed head, so a quiet organization's cursor keeps moving and
  never ages past retention.
* **Latency.** Sequencing runs every minute; the appointment sync runs every five.
* **At least once.** The same resource can appear several times; process
  idempotently by `resourceId` and re-read.
* **Retention: 30 days.** A cursor older than that answers **409** with
  `cursor_expired`: re-read current state (e.g. the history endpoint) and resume
  from the cursor in the message. Omitting `after` starts at the oldest retained
  change.

## Webhook

`changes.available` (scope `read:patients`) is sent after each sequencing pass
that produced changes for your organization:

```json theme={null}
{ "type": "changes.available",
  "data": { "organizationId": "…", "newestCursor": "184470" },
  "timestamp": "2026-10-06T04:32:00.000Z" }
```

It is best effort. Poll `GET /v4/changes` on a slower cadence as well.


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