Skip to main content
Three endpoints answer “when can this patient be seen?”. They read the same EHR but answer different questions: All three need the read:schedule scope, and all three accept the optional X-Session-Token header.
Book from /schedule/slots, not from /schedule/finder. A finder slot can already be held by another booking session in Max AI, or taken by a booking accepted but not yet written to the EHR. /schedule/slots subtracts both. A booking is re-checked against the EHR either way, so the cost of booking from the finder is a 409 slot_unavailable, not a double-book.

Which EHRs support what

Ask GET /v4/schedule/capabilities once, at configuration time:
(Other capability fields omitted.)
  • appointmentFinder: false means both finder endpoints return 501 capability_unsupported for this organization. Today ModMed has a finder and EZDerm does not. /schedule/slots works on both.
  • availabilitySearchOptions lists the finder parameters this EHR honours, by public name. A timeFrame mode 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/slots answer 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" and meta.strategy: "native".
  • remainingCapacity is the EHR’s open seats minus Max AI holds and minus bookings Max AI has accepted but not yet written to the EHR.
  • templateIds is empty: finder slots are not tied to a template id you can pin.
The 5-open-days cap is handled for you. EMA’s finder returns at most 5 open days per provider per call. For a longer window Max AI calls again from the day after the earliest cut-off, up to 8 calls in total (about 40 open days per provider). Each call takes roughly 0.2–0.8 s. On EZDerm the default grid still comes from Max AI’s capacity ledger (sourceKind: "mirror", strategy: "synthesized"); consistency=strong asks the EHR.

Completeness

Branch on meta.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 facilityId here.
  • A pair is eligible, not necessarily open: a provider can take the type at a facility and still have no open slots.
  • ehrSearchThreshold is 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:
Max AI verifies the token (signed for your app and this organization, valid for 120 seconds) and then picks the EHR login:
  1. The user’s own EHR login (their personal connector), when it is live, has a session and is on the same EHR instance.
  2. Otherwise the organization’s system connector, exactly as without a token.
Every response says which:
  • 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.resolvedBy changes to the system value.
  • It is null for appointments Max AI did not write.
Writes made through the user’s own login are attributed to that user in the EHR’s own history.

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.
Booking on ModMed is checked the same way, with or without a 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.
  • afterFirstAppointment and bufferMinutes are 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=true still had overrideAllowed: false.
  • A finder slot does not identify the calendar template behind it, so templateIds is empty on ModMed slots.