Integrating Real Healthcare APIs: Cliniko, Nookal, Snapforms

Patterns for integrating practice-management and form APIs - auth, pagination, rate limits, webhooks - without leaking a single record.
Author

Benedict Thekkel

Published

May 20, 2025

This is a patterns post. It deliberately contains no client data, no endpoints with real records, no keys, and no PII - just the integration shapes that recur across Cliniko, Nookal, and Snapforms. Treat every credential as radioactive and every payload as PHI.

Reference material lives in the WEB_doc and Software_Tools repos.

The three APIs, side by side

Different vendors, same integration muscles. What changes is auth, how they paginate, and whether they push events to you.

API Domain Auth Pagination Rate limits Webhooks
Cliniko Practice management API key (Basic, base64) Link headers / page URLs Per-account throttle Yes
Nookal Practice management API key + signed params Page + page_length Documented ceiling Limited
Snapforms Online forms API key Submission cursor Modest Yes (submission events)

Rule 1: never hardcode keys. Env vars / secret manager only - see Secrets in Django.

A safe integration architecture

The shape that has served me across all three: a thin vendor client, a normaliser into a common internal model, an idempotent upsert, and a queue so a slow vendor never blocks a request.

flowchart LR
  V[Vendor API] -->|paged pull / webhook| C[Vendor client]
  C --> N[Normalise to internal model]
  N --> Q[(Task queue)]
  Q --> U[Idempotent upsert]
  U --> DB[(App DB)]
  V -. webhook .-> WH[Webhook endpoint]
  WH --> Q

Sync: pull vs webhook

Two ways data arrives. Poll for the initial backfill; subscribe to webhooks for deltas. Always reconcile - webhooks get missed.

sequenceDiagram
  participant App
  participant Vendor
  App->>Vendor: GET /records?page=1 (backfill)
  Vendor-->>App: page 1 ... n (cursor)
  Vendor->>App: webhook: record.updated
  App->>App: enqueue + idempotent upsert
  App->>Vendor: nightly reconcile (missed events)

Rate limits are a budget, not a suggestion

Backfills are where you get throttled. Respect Retry-After, add jitter, and cap concurrency per vendor. The chart shows why naive parallelism backfires - past the vendor’s ceiling, throughput collapses into retries.

Checklist that keeps data safe

Concern Practice
Credentials Secret manager / env vars; never in notebooks or git
PII / PHI Minimise, encrypt at rest, never log payloads
Idempotency Upsert on a stable external id; dedupe webhooks
Failure Retry with backoff + jitter; dead-letter queue
Auditing Log that a record synced, never what

More on the mechanics (auth, retries, queues) in the API reference notebooks under Software_Tools and WEB_doc.

Back to top