Learn · SDK reference
Errors, pagination and retries
The five conventions that hold across every call.
Checked against the product on · written for people writing code
1. One error shape
Every failure returns the same four keys. Switch on error, which is stable. Read detail for a person.
Any failing callThe envelope every failure returns, from a validation problem to an outage.
| Field | Type | Required | What it is |
|---|---|---|---|
| error | string | Yes | A stable code. Switch on this. |
| detail | string | object | Yes | What went wrong, for a person. A validation failure carries the field list. |
| type | string | Yes | The class of failure. |
| where | string | Yes | The method and path that failed. |
Returns
{
"error": "validation_error",
"detail": [{ "loc": ["body", "subject_type"], "msg": "field required" }],
"type": "RequestValidationError",
"where": "POST /v1/agent-stream/event"
}| Status | error | What to do |
|---|---|---|
| 400 | bad_request | Fix the request. Retrying the same body fails the same way. |
| 401 | unauthorized | Check the key and the header. |
| 402 | payment_required | The account needs a plan or a payment method. |
| 403 | forbidden | The key is valid and the scope or role denies this call. |
| 404 | not_found | The id does not exist, or sits outside the key’s reach. |
| 409 | conflict | Read the current state and decide. Retrying repeats the conflict. |
| 413 | payload_too_large | Split the payload. |
| 422 | validation_error | Read detail for the field list. |
| 429 | rate_limited | Back off. The response headers carry the window. |
| 500 | internal_server_error | Retry with backoff. Send us the where value. |
| 503 | service_unavailable | Retry with backoff. |
2. Cursor pagination
A list endpoint returns a page and an opaque cursor. Send the cursor back to get the next page. The cursor is a keyset over a sort key and a tiebreak, so a row added while you page never shifts a result into or out of a page you already read.
/v1/{collection}?limit=50&cursor={next_cursor}Every list endpoint takes these two. Send back whatever next_cursor the previous page returned.
Returns
{
"items": [ … ],
"count": 50,
"next_cursor": "eyJzIjoiMjAyNi0wOC0zMFQxMjowNDoxMVoi…"
}3. Idempotency
A mutating call takes an idempotency key. Sending the same key twice returns the first result, so a retry after a timeout is safe.
Idempotency-Key: <a value you generate>Send it on any mutating call. Use one value per logical operation, and reuse it on retries of that operation.
4. Rate limits
A rate-limited response returns 429 and carries the limit, the remaining count, and the reset time in headers. Back off to the reset time before retrying.
5. Versioning
- The path carries the version. A breaking change gets a new version.
- Fields are added without a version bump, so parse permissively and ignore what you do not know.
- A field is deprecated in the description before it is removed.
- The SDKs are versioned with the contract they call and are tested against it on every change.
A client that holds up
- Switches on error, and never on the text of detail.
- Retries 429, 500 and 503 with backoff, and nothing else.
- Sends an idempotency key on every mutating call.
- Pages with the cursor it was handed and stops on a null.
- Ignores fields it does not recognize.
- Treats a failed guard check as a block.