Guides
Retries, versioning and limits
Conventions that hold on every endpoint, so you can write them into your client once instead of per call.
- For
- Anyone writing a client rather than making one call
- You will need
- Nothing — read it before you write the retry loop
- About
- 8 minutes to read
Retrying safely
A request that times out leaves you with no way to know whether it landed. Retrying might create a second thing; not retrying might create nothing. Send an Idempotency-Key and the choice goes away:
curl -X POST https://api.integrable.cloud/api/bots \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Idempotency-Key: 8f14e45f-ea8d-4b1c-9a1e-3c9a0d2b7e11" \
-H "Content-Type: application/json" \
-d '{"name":"Support","system_prompt":"You are a support assistant."}'The first request with that key runs. Every replay returns the first response unchanged, with Idempotent-Replay: true set so you can tell the two apart — which matters when the response contains an id and you are trying to work out whether you just created it.
- One key per action, not per session. Reusing a key with a different body is refused with
422rather than replaying the wrong answer — that combination is nearly always a bug in the caller, and hiding it behind a plausible response is worse than failing. - Keys last 24 hours. Comfortably longer than any sane retry, short enough to bound the storage.
- A failed request releases its key. Fix the payload and retry with the same key and it runs normally. You are never stuck being handed back the error you got last time.
- A request still in flight returns
409. The original is still running. Wait and retry rather than treating it as a failure.
Versioning
Every response carries the dated version of the contract that served it:
X-API-Version: 2026-09-03A date rather than v1, because the useful question is not “which version” but “which shape was this client written against” — and only a date answers that.
Additive changes — a new field, a new endpoint, a new enum value — ship without a version change. Write your client to ignore fields it does not recognise and those will never break you.
When something is genuinely being removed, responses start carrying:
Deprecation: true
Sunset: Wed, 11 Mar 2026 00:00:00 GMTBoth are machine-readable standards (RFC 9745 and RFC 8594), so log or alert on them and you will hear about a removal from your own monitoring rather than from a failing call.
Rate limits
Per key, published on every response that consumed a budget. Read them and back off before you are blocked:
RateLimit-Limit: 120
RateLimit-Remaining: 4
RateLimit-Reset: 37
RateLimit-Policy: 120;w=60Endpoints with no limit send no headers. A header claiming an unlimited budget would be worse than none — you would compute a backoff from a number that means nothing.
Pagination
List endpoints return items plus a cursor. Follow the cursor rather than incrementing a page number: an offset makes the database read and discard every row it skips, so page 40 costs far more than page 1 and eventually times out.
{
"items": [ ... ],
"next_cursor": "eyJrIjoiMjAyNi0wOS0wM1QxMDowMDowMFoiLCJpZCI6Ii4uLiJ9",
"has_more": true
}A cursor that has been truncated or corrupted returns the first page rather than an error — starting over is both the friendlier failure and the safer one.
Errors
Errors carry two envelopes in one body: a nested error object, and the RFC 9457 members a generated SDK or an API gateway understands without custom mapping.
{
"error": {
"code": "not_found",
"message": "Bot not found",
"request_id": "7cb7f7862a82425d8e4c3fb8a497dfe6"
},
"type": "https://integrable.cloud/docs/errors/not_found",
"title": "Not Found",
"status": 404,
"detail": "Bot not found",
"instance": "/api/bots/01a0652b-3713-7ea1-a6c9-2e895389ec34"
}title is the class of failure and never varies between occurrences — group by it. instance is the specific call. request_id is what to quote at support.
Reading headers from a browser
All of the above are listed in the CORS Access-Control-Expose-Headers response, so browser code can actually read them cross-origin — including RateLimit-*, X-API-Version, Idempotent-Replay and X-Request-ID.
Something here wrong or missing? Tell us — the documentation and the API are maintained by the same person, so a correction is a fix rather than a ticket.