Skip to main content
All list endpoints return paginated results. You control pagination using query parameters, and the response includes metadata to help you navigate through pages.

Query Parameters

Example

Response Format

Every paginated response includes a pagination object alongside the data:

Pagination Fields

Iterating Through All Pages

To fetch all records, loop until page equals totalPages:

Tips

  • Use the largest pageSize that makes sense for your use case to minimize the number of requests — up to 500 on /patients, /insurance-policies and /appointments, up to 100 elsewhere.
  • On /bills, pass includeTotals=false if you do not need totalCount. The totals cost a second aggregate over the whole filtered set, recomputed on every page of a walk, and they are the dominant cost of that endpoint. Leave them on if you use the count to decide a walk was complete.
  • The first page is page=1, not page=0.
  • An empty page returns an empty array with totalCount: 0.
  • Some list endpoints support search filters (e.g., ?search=smith on /v3/patients). Pagination applies after filtering.

Delta sync and cursorId

/bills, /patients, /insurance-policies and /appointments accept updatedSince (and updatedUntil) to return only records that changed. Always send a full instant with an explicit UTC offset2026-08-01T00:00:00Z, not 2026-08-01. On /patients, /insurance-policies and /appointments a zone-less value is rejected with a 400. /bills predates that rule and still accepts one, but it is resolved against the database’s timezone rather than as UTC, so the same string can mean a different instant there than on the other three. Send the offset everywhere and the difference never arises. cursorId is available on /patients, /insurance-policies and /appointments. /bills does not have it yet: unknown query parameters are ignored rather than rejected, so sending cursorId there is silently dropped and you get the offset walk described below. Until it does, walk /bills with updatedUntil pinned to the instant your walk started, keep includeTotals on, and treat a totalCount that shrinks between pages as a walk you must not trust — re-read the window instead of advancing your cursor. Do not walk a delta feed with page/offset. The rows are ordered by the same change stamp the sync workers rewrite, so a record on an earlier page that changes mid-walk moves to the end of the results. Everything after it shifts one position toward the front, and the offset for your next page steps over whichever record moved into that slot. That record is never returned, and its stamp is below the cursor your walk ends on, so you never see it again — while the walk itself looks like it completed cleanly. Use cursorId instead, always requesting page 1:
On a cursorId page, pagination.totalCount counts the window that starts at your cursor, so it shrinks as you walk — 250, then 150, then 50. That is expected. Stop on a short page rather than on the count, and do not read the shrink as a signal that the walk is untrustworthy (that signal belongs to the offset-paged /bills walk described above, where a shrinking total means a row left the window and may have been skipped). Two limits to design around:
  • A change feed cannot report a deletion. If you prune local records the source no longer has, you still need a periodic full walk; the delta only replaces the reading.
  • Each feed tracks its own row. /appointments compares lastSyncedAt, which moves only when an appointment’s own columns change — renaming a patient changes patientLastName in the appointment response without the appointment appearing in the feed. Likewise /insurance-policies does not move when payer or plan names change, and /patients does not move for include=contact or include=identity data. Mirror those from their own endpoints.