All three need the
read:schedule scope, and all three accept the optional
X-Session-Token header.
Which EHRs support what
AskGET /v4/schedule/capabilities once, at configuration time:
appointmentFinder: falsemeans both finder endpoints return 501capability_unsupportedfor this organization. Today ModMed has a finder and EZDerm does not./schedule/slotsworks on both.availabilitySearchOptionslists the finder parameters this EHR honours, by public name. AtimeFramemode or option missing from it is a 400 that names the parameter.availabilityCompleteness: "complete"means this EHR’s availability is read live, so an untruncated/schedule/slotsanswer is the whole truth. See completeness.
Bookable slots: GET /v4/schedule/slots
How ModMed slots are produced
On EHRs read live (ModMed, through EMA’s appointment finder), every slot search asks the EHR; there is no cached grid. The response says so:meta.sourceKind: "live_read"andmeta.strategy: "native".remainingCapacityis the EHR’s open seats minus Max AI holds and minus bookings Max AI has accepted but not yet written to the EHR.templateIdsis empty: finder slots are not tied to a template id you can pin.
sourceKind: "mirror", strategy: "synthesized"); consistency=strong asks the
EHR.
Completeness
Branch onmeta.completeness, never on strategy:
lower_bound cannot cause a double-book; it only under-offers. For an empty
lower_bound grid, say “nothing bookable online” rather than “the practice is
full”, or narrow the window and search again.
nextAvailableByProvider
The earliest bookable day per provider inside [from, to], computed after holds
are subtracted, so every listed day has at least one slot in slots. A provider with
nothing bookable in the window is absent. It never reports a day beyond to; to
look further out, widen the window.
The EHR’s view: GET /v4/schedule/finder
One call to the EHR’s appointment finder, with every option it offers. Ids are
mapped to Max AI ids; nothing else is changed.
Parameters
A mode’s own parameters are required, and another mode’s parameters are refused:
timeFrame=specific_date&from=2026-11-02 is a 400 naming from.
Modes
On ModMed every mode returns at most 5 open days per provider. A capped group
comes back with
truncated: true. Unlike /schedule/slots, the finder makes no
follow-up calls: move or narrow the window to see more.first_available: each provider’s earliest openings, starting at that
provider’s own first open day (which can be weeks out).
specific_date: one clinic day. Never truncated.
time_frame: like first_available, but starting offset offsetUnit from
today.
date_range: every provider at the listed facilities between from and to.
Response
The finder never returns EHR-internal ids or staff names.
Provider-location pairs: GET /v4/schedule/finder/provider-locations
facilityId(optional, repeatable or comma-separated) narrows the pairs.- Pairs are limited to the facilities your API key may see. Unlike the other two
endpoints, a facility-limited key may omit
facilityIdhere. - A pair is eligible, not necessarily open: a provider can take the type at a facility and still have no open slots.
ehrSearchThresholdis the EHR’s own value, passed through. Its meaning is unknown; do not build on it.
Acting as the logged-in user
Send the App Bridge token of the user who is using your app:- The user’s own EHR login (their personal connector), when it is live, has a session and is on the same EHR instance.
- Otherwise the organization’s system connector, exactly as without a token.
- No header: always the system connector. The API key creator’s login is never used.
- A token that does not verify (bad signature, another app, another
organization, expired): 401
authorizer_unverified. Mint a fresh token and retry; tokens are short-lived by design. - There is no user-id header. Only a verified token can name a user, so one app key cannot act as any user it chooses.
Holds and writes take the same header
POST /v4/schedule/holds, POST /v4/appointments, reschedule and cancel accept
X-Session-Token with the same verification and the same rule. Their response
meta carries actingAs and resolvedBy.
The appointment resource carries writtenBy.resolvedBy: which EHR login wrote
its last change. It is visible on GET /v4/appointments/{id} too.
- While the write is queued, it is the login the write is intended for.
- Once it lands, it is the login that actually wrote it.
- If the user’s personal login stops working before the queued write runs (session
expired, disconnected, no scheduling rights), Max AI writes through the system
connector instead and
writtenBy.resolvedBychanges to the system value. - It is
nullfor appointments Max AI did not write.
Holds
POST /v4/schedule/holds reserves a slot while the patient finishes booking. Pass
the hold’s id as holdId when you create the appointment.
- Holds are advisory (
advisory: true): they stop two Max AI booking sessions from selling the same seat, but staff booking directly in the EHR can still take it. - On EHRs read live (ModMed) the hold first reads the slot’s open seats from the EHR, then subtracts bookings Max AI has accepted but not yet written and other active holds.
- 409
slot_unavailable: the EHR no longer shows the slot, or Max AI holds and pending bookings already use all its seats. Search again. - 502
ehr_unavailable: the EHR could not be read in time. Nothing was held; safe to retry. Holds fail closed: without the EHR’s answer nothing guards the seat.
holdId.
Errors and limits
- Live searches run at interactive priority, ahead of background syncs.
- A live search stops when your client disconnects, so abandoning a request does not keep loading the EHR.
- There is no caching: each request asks the EHR.
Known unknowns
These are vendor behaviours not yet proven. Do not build on them.afterFirstAppointmentandbufferMinutesare accepted by ModMed but showed no effect on the test days available. Their meaning is unverified.ehrSearchThreshold(300 on every tenant seen) has no known meaning.- Every slot observed with
allowOverride=truestill hadoverrideAllowed: false. - A finder slot does not identify the calendar template behind it, so
templateIdsis empty on ModMed slots.
