Skip to main content
Every read endpoint serves a cache of the clinic’s EHR, refreshed continuously by background syncs. Most of the time that cache is minutes fresh and you never think about it. When you know something changed in the EHR — a biller tells you a claim was corrected, your user is looking at a stale ledger — you can ask for one targeted re-sync instead of waiting for the next background pass:
The response is 202 Accepted with a queued sync request. The re-sync runs asynchronously — logging into an EHR and re-reading a record is a multi-second operation on a good day — so you then either poll GET /sync-requests/{id} or subscribe to the sync_request.completed webhook, and re-read the resource once the request is terminal.

Supported resource types

resourceId is the id the resource’s own endpoint returns — prefixed (clm_…, bil_…, pat_…, nte_…, apt_…) from v3, raw UUID earlier. The prefix must match resourceType. No new scopes: a re-sync only refills data your app can already read, so it requires the resource’s existing read scope.
Appointments are coarser. There is no single-appointment sync; an appointment request re-sweeps the connector’s schedule window (roughly one week back to six weeks ahead). Because of that it has a longer, per-connector cooldown — and since the schedule already re-syncs itself every few minutes, you rarely need it. An appointment outside the window that sweep covers (roughly one week back to six weeks ahead) is rejected with a 400 rather than accepted into a re-sync that could not reach it.

Request lifecycle

status moves queued → completed | failed. Treat the first terminal status you see as the outcome. Until the platform has finished recording it, that status is derived live from the underlying sync runs, so if those runs briefly become unreadable a later poll can report queued again — even after an earlier poll returned a terminal status and a completedAt. Polling again resolves it; the request itself never moves backwards.
  • completed — every sync run the request fanned out finished. Re-read the resource’s own endpoint for the refreshed data.
  • failederrorKind says why, as a closed marker:
    • child_run_failed — a sync run failed, or was interrupted mid-flight. The data may be partially refreshed; retry after the cooldown.
    • cancelled_before_start — the sync runs were discarded while still queued, so nothing this request dispatched ever ran. Usually an equivalent sync already held the lane (in which case that run is doing the same work), but a worker restart looks identical, so treat the resource as possibly still stale. It costs you no daily-cap slot, but it does hold the cooldown (5 minutes, or 30 per connector for an appointment). Re-read the resource, and retry after the cooldown if it is still stale.
    • stale — no terminal outcome inside the watch window (30 minutes). The re-sync may still land late; treat the request as spent and re-read the resource before retrying.
A request that cannot be started at all never reaches errorKind: it is refused synchronously with a 4xx or a 502, and no request id is issued. Two kinds, which differ in what they cost you:
  • Refused before the request is recorded — an unknown resource, a paused connector, an appointment outside the sweep window. Nothing is stored, so there is no cooldown and no cap slot spent; retrying returns the same 4xx until the underlying problem is fixed.
  • Refused while dispatching, once the request exists — the resource has no EHR identity, or the dispatch itself failed. These hold the cooldown, so an immediate retry returns 409.
resourceUpdatedAt on the 202 is the resource’s own change stamp before your request, so you can tell whether the re-sync changed anything: re-read the resource and compare. It is the same field that resource’s endpoint returns — updatedAt for claim, bill, note and patient; lastSyncedAt for appointment, which exposes that instead and has no updatedAt. It is read before anything is dispatched, so a re-sync that finishes faster than the 202 returns cannot slip past it. Two caveats: it is a property of the resource at request time rather than of the request, so it is returned only on the 202 (always null on GET); and it moves only when the data actually changed, so it is a change detector, not a completion signal — use the request’s status or the webhook for that. A sync request belongs to the app that created it: GET /sync-requests/{id} and the completion webhook are both scoped to that app, and another app in the same organization gets a 404.

Completion webhook

If your app has webhooks configured, a terminal request also emits sync_request.completed, delivered only to the app that made the request:
The webhook is best-effort (like every data event); polling is the correctness path. Ids in webhook payloads are raw UUIDs — pass them straight to GET /v{N}/sync-requests/{id}, which accepts either form.

Many resources at once

POST /v4/sync-requests/batch queues up to 25 re-syncs in one round trip. Kinds may be mixed. Use it when a sweep of your own has found several resources worth re-checking; it saves round trips, nothing else.
Every item is processed independently, under exactly the same guardrails as the single-resource endpoint: its own scope check, its own cooldown, and its own slot of the 500-per-organization-per-24h ceiling. A batch of 25 spends 25 slots, not one — batching buys round trips, not budget.
Partial success is the normal case. The 202 says the batch was processed, not that every item queued. Each result carries either request or error, never both and never neither — check every one. An item refused by its cooldown does not affect the others.
  • Results come back in the order you sent them, with resourceId echoed back verbatim in the form you sent it, so you can match them to your input.
  • error.statusCode is the status that item would have received on its own — 400, 403, 404, 409, 429, 502 — with the same message. One status is reported per item rather than for the request: a 500 item is the same unhandled failure the single-resource endpoint would answer with, scoped to that item so the rest of the batch still runs. Retry that item; the others are unaffected.
  • A 503 item means the batch ran out of its time budget before that item was started. An item is not one EHR call — a claim fans out to as many as four — so a large batch of the heavier kinds can outrun the budget. Nothing ran for a 503 item: it holds no cooldown and spent no daily-cap slot, so send it again straight away, in a smaller batch. The items that did queue are still in the response with their ids, which is the point of stopping rather than timing out.
  • Queued items are ordinary sync requests: poll each one’s GET /sync-requests/{id}, and sync_request.completed fires once per item, never once per batch.
  • More than 25 items is a 400 naming the limit — never a silent truncation. Send the rest in another batch.
  • The response is held until every item has been dispatched, so a full batch can take several seconds.
  • Sending the same resource twice in one batch is allowed; the second occurrence is refused by its own cooldown, like any other repeat.

Guardrails

Re-syncs spend a real clinic’s EHR rate budget, so they are deliberately bounded:
  • Cooldown — one re-sync per resource per 5 minutes (409), and a request still running also 409s until it finishes or goes stale. Appointments cool down per connector per 30 minutes, since one request sweeps that connector’s whole schedule.
  • Refunds — a request rejected before it is even recorded (an unknown or un-syncable resource, a paused connector) costs you nothing at all. Once a request exists, the daily-cap slot is refunded only when nothing it dispatched reached the EHR — a cancelled_before_start, or a refusal that enqueued no work — and the cooldown is held either way, so retrying straight away returns 409.
  • Daily cap500 API re-syncs per organization per rolling 24h (429), shared across resource types and apps.
  • The global rate limit applies on top.
A 409 tells you a refresh is already underway or just happened — poll the resource instead of retrying the POST.

When not to use this

  • Routine change detection. Poll with updatedSince delta queries or subscribe to webhooks; background syncs keep the cache fresh without you spending re-syncs.
  • Bulk refreshes. The batch endpoint exists for a short list you have already narrowed down — it spends one cap slot per item, so it is not a way to refresh everything. If you believe a whole organization’s data is stale, contact us instead of scripting a loop over your cooldown budget.