Skip to content

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.

ERRORAny failing call

The envelope every failure returns, from a validation problem to an outage.

FieldTypeRequiredWhat it is
errorstringYesA stable code. Switch on this.
detailstring | objectYesWhat went wrong, for a person. A validation failure carries the field list.
typestringYesThe class of failure.
wherestringYesThe 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"
}
The stable codes, by status.
StatuserrorWhat to do
400bad_requestFix the request. Retrying the same body fails the same way.
401unauthorizedCheck the key and the header.
402payment_requiredThe account needs a plan or a payment method.
403forbiddenThe key is valid and the scope or role denies this call.
404not_foundThe id does not exist, or sits outside the key’s reach.
409conflictRead the current state and decide. Retrying repeats the conflict.
413payload_too_largeSplit the payload.
422validation_errorRead detail for the field list.
429rate_limitedBack off. The response headers carry the window.
500internal_server_errorRetry with backoff. Send us the where value.
503service_unavailableRetry 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.

GET/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…"
}
Note. A null next_cursor means the last page. Treat the cursor as opaque: its contents are an implementation detail and change without notice.

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.

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

Safe by default. The acknowledgment call is idempotent on the pair of output id and your own reference, with no header needed. Sending the same pair twice closes the record once.

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.