Skip to main content
Writes and the change feed described below are live for inventory (any plan with API access); for patients, appointments, clinical notes and drug requests, a write also needs the clinic’s API add-on and an accepted API data agreement — see the index page. Webhooks are set up from the developer dashboard’s Webhooks page — see Verifying a webhook’s signature.
Two different problems share one shape here: a write that might have been sent twice, and a webhook you might have missed. Both are solved the same way — a number or a key you keep, and pass back on your next request.

Idempotency keys — for writes

Every write endpoint (anything that creates, adjusts, or changes a record — receiving stock, adjusting a quantity, transferring between locations, creating an item, uploading a code list) accepts an Idempotency-Key header.
Generate a fresh key (a UUID is a good choice) once, when the action is first attempted — never a new one on every retry. If your request times out, or you’re not sure whether it was received, retry with the same key:
  • If the original write never reached the server, the retry is the first attempt — it goes through normally.
  • If the original write already succeeded, the retry returns the same result it returned the first time (idempotent_replay: true in the response) and changes nothing a second time. You will never end up with a duplicated stock movement or a doubled adjustment from retrying.
A write endpoint that requires this header refuses a request with none — there is no way to opt out of it on a route where a duplicate would be a real problem (money, stock).

The sequence number — for webhooks

Every webhook carries a seq — a number that only ever goes up, scoped to the connection that received it. Store the highest seq you’ve successfully processed. It’s your one source of truth for “have I seen this already”, independent of whether the delivery itself arrived once, twice, or out of order under retry.

The change feed — catching up after downtime

If your endpoint was down, disabled, or you simply missed a delivery, don’t try to reconstruct what changed from memory — page through the change feed instead, starting after the last seq you have. The feed is per clinic — after_seq is the query parameter:
Keep paging with next_after until it stops advancing. The feed only ever returns event types your key’s current scopes cover — if a scope was removed since an event was queued, that event simply will not appear, the same as if it had never happened for you.
The change feed has a retention window (30 days, in the current design) — it is a catch-up mechanism for a short outage, not a permanent audit log of everything that ever happened.

See also