Skip to main content
Once you’ve tested against a developer account, work through this before flipping your integration on against a real clinic. Every box links to the page that explains the reasoning — this page is deliberately just the checklist.

Credentials

  • One key per integration, named for what it does — “Booking widget”, “Nightly export” — not one shared key across several systems. Requests and audit trails are per key (the rate limit is shared across the account), and a name that says what a key is for means revoking it later is surgical, not a guess about what else might break. See Best practices → Credentials.
  • Scopes are minimal. Read-only unless the integration writes; a read-only key can’t write anything by construction (403 key_read_only), which is the cheapest access control available. See Authentication → API keys.
  • Stored server-side only. Never in a browser, a mobile app bundle or a public repository — phk_ keys are recognised by secret scanners, but the fix for a leak is still rotation, not detection. See Authentication → Rotation, expiry and audit.

Error handling

  • 422 is handled by field, reading errors rather than guessing from message. message is written for a human and can be reworded without notice; the attribute keys in errors are the stable part. See Errors → Handling errors well.
  • Only 429 and 5xx are retried, with exponential backoff and jitter, and the same Idempotency-Key reused on every retry of a given POST so a retried write can’t create the record twice. 409 is retried only after the state that caused it changes; 422, 401 and 403 are never retried. See Errors → Retry taxonomy.
  • The code is logged, not just the status. rate_limited, appointment_rule, billing_rule, key_read_only and the rest are stable identifiers your alerting and dashboards should key off — see the full error code catalogue.

Pacing

  • 120 requests per minute, shared across the account’s API keys (AI connectors have their own separate MCP bucket), spent steadily rather than in bursts — firing the whole budget in the first second of every run and idling for the rest of the minute is the surest way to see 429s (for you and for every other integration on the account) that a paced integration never would. See Rate limits → Pacing an integration.
  • Retry-After is honoured on every 429, and X-RateLimit-Remaining is watched in normal operation — a value trending toward zero outside of known bulk runs usually means something should be a filter or a cache instead of a poll. See Best practices → Rate limits and retries.

Data hygiene

  • Records you create carry your external id in metadata (prefixed with your app name, e.g. crm_id) — so you can find, de-duplicate and cross-reference them without keeping a separate mapping table. See Metadata.
  • The clinic-local vs UTC rule is applied consistently. created_at/updated_at (and webhook occurred_at) are UTC, for windowing; everything the practice sees — starts_at/ends_at, invoice_date, payment_date, availability slots — is clinic-local with no offset, and is sent the same way when you write it. Get this backwards once and every booked time is off by the clinic’s UTC offset. See Best practices → Time.

Monitoring

Sync correctness

  • Incremental runs use an updated_at watermark with overlap — filter updated_at=gte:<watermark>, sort updated_at:asc, walk by cursor, and set the next watermark to when the run started (with a few minutes of overlap), not when it finished. See Best practices → Pagination.
  • Deletions are handled via deleted_entities, not inferred from a record disappearing from a list — walk it since your last sync and remove or archive the matching ids on your side. See Querying → Deleted records.

Go-live day

  • Start with one clinic. If your integration serves many accounts, turn it on for one first, watch Developers → API Logs and your own error rates while it runs for real, and only then roll out to the rest. A problem caught on one clinic is a problem the others never see.