API & webhooks
LabStar’s end-of-life took custom reports with it, and this is the honest replacement: read your own data with your own tools. Everything here is set up at Lab → API & webhooks. Keys Create a key per integration and give it only the scopes it needs — `cases:read`, `cases:write`, `invoices:read`, `directory:read`. The key is shown once; we store a one-way hash and genuinely cannot show it again, so if it is lost, make a new one and revoke the old. Revoking takes effect on the next request. Each key’s row shows when it was last used, which is the quickest way to find one nothing is using any more. A key can never carry more access than the person who made it. If you do not have access to invoices yourself, `invoices:read` is not offered and asking for it is refused by name rather than quietly dropped — a key silently missing a scope fails later, inside someone else’s integration. Calling the API Base URL `https://api.casetop.io/api/public/v1`. Send the key as a bearer token: ```
curl https://api.casetop.io/api/public/v1/cases?limit=50 \
-H "Authorization: Bearer ct_live_..."
``` Start with `GET /me` — it returns your lab and the scopes on the key, which confirms you pasted the right secret. Then: `GET /cases` (filter by `stage`, `dentist_id`, `account_id`, `updated_since`), `GET /cases/{id}` (with its units and stage history), `POST /cases`, `GET /invoices` and `GET /invoices/{id}` (with line items), `GET /dentists`, `GET /accounts`, `GET /products`. Paging Lists return `{ data, has_more, next_cursor }`. Pass `next_cursor` back as `?cursor=` until `has_more` is false. It is cursor paging rather than page numbers on purpose: a page boundary stays correct while new cases arrive, so a job walking 100,000 cases never skips one. Up to 200 rows per page; 120 requests a minute per key. Creating a case `POST /cases` needs a `dentist_id` and takes the same fields the app does — `patient_ref`, `restoration_type`, `material`, `shade`, `tooth_numbers`, `due_date`, `notes`, and `units` for a multi-unit case. It runs the same path as typing one in: your case numbering, your assignment rules, your pipeline, the credit-hold check and the AI pre-flight all apply. A case created this way is an ordinary case on your board. Webhooks Add an https endpoint and we POST JSON to it when something happens. Events: `case.created`, `case.stage_changed`, `case.shipped`, `case.on_hold`, `intake.received`, `invoice.issued`, `invoice.paid`. Tick none to receive all of them. `case.shipped` is sent in addition to `case.stage_changed`, so you can watch for “work is done” without knowing your lab’s stage names. The body is `{ "event": …, "created_at": …, "data": { … } }`. Reply with any 2xx; anything else (or a timeout past 10 seconds) is a failure and we retry after 1 minute, 5, 30, 2 hours and 6 hours before giving up on that delivery — about nine hours, so an afternoon of downtime at your end loses nothing. An endpoint that fails 25 deliveries in a row is switched off and tells you why on the page; fix it and turn it back on. Send test delivers a sample payload and shows you the exact response. Verifying the signature Every request carries `X-Casetop-Signature: t=<unix seconds>,v1=<hex>`, where the hex is HMAC-SHA256 of `"<t>.<raw body>"` using that endpoint’s signing secret. Sign the raw body, before any JSON parsing. Reject anything where `t` is more than a few minutes old — the timestamp is inside the signed material precisely so a captured request cannot be replayed later. Compare with a constant-time function, not `==`. Rotating the secret takes effect immediately, so update your end first. ```
import hmac, hashlib
ts, sig = [p.split("=", 1)[1] for p in header.split(",")]
expected = hmac.new(secret.encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, sig)
``` What is not here yet The public API is read-mostly today: you can create cases, and everything else is read. Editing invoices, advancing stages and managing the catalog go through the app. If you need one of those, tell us what you are building — that is how this list gets shorter.
This page uses JavaScript to render its full interactive content. Enable JavaScript, or continue reading at
https://casetop.io/docs/api-and-webhooks.