message, a stable machine-readable code, and — for input problems — errors keyed by attribute:
Error code catalogue
Branch on the status code, thecode, and the attribute keys in errors. message text is written for humans and may change; do not parse it. Codes are stable within the current version — new ones may be added, existing ones are not renamed.
Every code below is emitted by name by the API — none are aspirational.
401 — Unauthorized
403 — Forbidden
404 — Not Found
405 — Method Not Allowed
409 — Conflict
422 — Unprocessable Entity
Validation
Referenced record not found —
errors names the attribute; the reference is missing, deleted, or not on this account
Clinical notes
Appointments & booking
Patients & referrals
Payments & billing
Files
Idempotency
413 — Payload Too Large
429 — Too Many Requests
500 — Internal Server Error
503 — Service Unavailable
Every other HTTP status the platform can produce uses the same
{message, code} envelope: a 403 from a permission gate is permission_denied, 413 is payload_too_large, 503 is service_unavailable, and code is http_error only when no more specific code applies.
Handling errors well
Retry taxonomy — branch on status, not on whether the call “felt” transient:- Log the
code, not themessage.messageis written for a human and may be reworded without notice;codeis the stable contract your branching logic and your alerts should key off. StoreX-Request-Idalongside it — it’s what support needs to pull the exact request from Developers → API Logs. - Don’t parse
messageto detect a specific failure — theerrorsobject (for422) andcode(for everything) already tell you exactly what happened, in a shape that won’t drift under you. - Idempotency keys make retries safe, not fast — see Idempotency keys for the full replay behaviour (24-hour window, same-key-same-body semantics).
Diagnosing
Every response includes anX-Request-Id. The same id appears against the request on the account’s Developers → API Logs page — status, latency and the caller — and support can trace it from there.
Rate limits
X-RateLimit-Limit and X-RateLimit-Remaining are on every response. The limit is per account: every API key on the account shares one REST budget (120 requests a minute), so a burst from one integration spends the same budget as every other. AI connectors have a separate budget of their own and never eat into it.