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:

terminal
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 422 rather 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.
A UUID per action is the easiest thing that works. Generate it before the first attempt and reuse it across retries — a key generated inside the retry loop is a different key each time and buys you nothing.

Versioning

Every response carries the dated version of the contract that served it:

X-API-Version: 2026-09-03

A 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 GMT

Both 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=60

Endpoints 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.

response
{
  "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.

404 response
{
  "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.

Building on it? Start on the free plan — no card — and call the same API the dashboard uses.

Start free