Overview
A well-behaved integration is mostly defined by how it handles the unhappy paths: what it does with a 422, how it backs off on a 429, and which failures it retries versus surfaces. This is the operational companion to the reference material at /developers/errors and /developers/rate-limits — both of which are generated from the platform's own source, so they can't drift from what the API actually returns.
The error envelope
Errors return a consistent JSON envelope with a machine-readable code alongside the HTTP status. Branch on the code, not the human-readable message — messages can be reworded; codes are the contract. The full catalogue, with meanings, is at /developers/errors.
The statuses that matter, and what to do with each
| Status | Meaning | Your move |
|---|---|---|
| 400 | Malformed request | Fix the request — retrying identical bytes can't succeed |
| 401 | Authentication failed | Check the key — see Authentication and API Keys |
| 403 | Authenticated, but not permitted | A scope or capability gap — don't retry; re-scope |
| 404 | Not found — or not yours | See the tenant-boundary note below |
| 409 | Conflict — the record changed, or a replay | Re-read and reconcile; on a retry, it means the first attempt is still running or already returned a one-time secret |
| 422 | Valid shape, invalid business state | The code tells you which rule — a licence gate, a missing group, a policy |
| 429 | Rate limited | Honour Retry-After, back off, and see below |
| 5xx | Platform-side fault | Retry with backoff and jitter |
The tenant-boundary 404. Records outside your organisation return 404, never 403 — confirming existence would itself leak. Practically: a 404 on an id you're sure of usually means wrong organisation's key, not a missing record.
Retry discipline. Retry network failures, 429s (as directed) and 5xx — with idempotency keys on mutations so a retried create can't double-write. Never blind-retry 4xx: the request is wrong, and repeating it is noise.
Rate limits
Limits are per organisation, sized per tier, and published — kept automatically current — at /developers/rate-limits. A 429 carries Retry-After; treat it as an instruction, not a suggestion.
Building not to hit them in the first place:
- Prefer webhooks to polling — reacting to
check.completedcosts nothing per minute; polling for it does. See Webhooks. - Paginate with cursors and fetch deltas, rather than re-reading whole collections.
- Spread scheduled jobs — the classic self-inflicted 429 is every sync firing at the top of the hour.
Edge cases
- Bursts vs sustained load — a burst that trips the limit recovers in moments if you back off; a sustained-over-limit design needs redesign (or a conversation about your tier).
- Multiple integrations share the organisation's budget — a runaway script starves the well-behaved sync beside it. Separate keys make the audit trail show which consumer is which.
- 422s are informative by design — the business-rule codes name the real-world condition (an unlicensed operator, an unclassified machine), which is exactly what your integration should surface to its users.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Sudden 429s at the same time daily | Synchronised scheduled jobs | Stagger them; add jitter |
| Persistent 404 on a known-good id | Wrong organisation's key | Check which organisation issued the key |
| 409 on a retried create | The first attempt is still running, or it already succeeded and returned a one-time secret | Retry once it finishes; for a lost secret, see Idempotency |
| A 422 you don't understand | Business rule — the code names it | Look it up at /developers/errors |
| Errors with no envelope | Something upstream of the API (proxy, LB) | Check your network path; the API always answers in the envelope |