Pagination and retries
Walk long lists, retry safely with idempotency keys, and stay within rate limits.
Pagination
List endpoints return items, has_more and next_cursor. Pass next_cursor
as cursor to fetch the next page; limit is 1 to 100.
curl "https://utopia-payments.com/api/v1/payments?limit=50" \
-H "Authorization: Bearer $UTOPIA_API_KEY"
# → { "items": [...], "has_more": true, "next_cursor": "eyJ0Ijoi…" }
curl "https://utopia-payments.com/api/v1/payments?limit=50&cursor=eyJ0Ijoi…" \
-H "Authorization: Bearer $UTOPIA_API_KEY"Idempotency
Send an Idempotency-Key header with POST requests. If a request times out,
retry it with the same key: you get the first response back instead of a second
checkout session. Keys are remembered for 24 hours. The libraries add one
automatically.
A key lives inside one account and one mode: your live and test keys never
collide, and neither do two merchants'. Within that scope a key belongs to
exactly one request — the method, the path and the body are all remembered with
it, so reusing a key for a different endpoint or a changed body returns
422 IDEMPOTENCY_KEY_REUSED rather than quietly replaying the wrong answer.
A replayed response repeats the original status code and carries
idempotent-replayed: true, which is how you tell a replay from a first
attempt. Three other details are worth knowing:
- While the first request is still running, the same key answers
409 IDEMPOTENCY_KEY_IN_USE. Wait and retry the same request. - A
5xxis not a final answer and is not stored, so retrying with the same key runs the request again. - A stored response drops any
secretfield. ReplayingPOST /webhooksreturns the endpoint without itswhsec_…secret, so keep the secret from the first answer, or read it fromGET /webhooks/:id/secret.
Override the automatically generated key when your application already has a stable operation id:
await utopia.checkoutSessions.create(params, { idempotencyKey: 'order-1001' });$utopia->checkoutSessions->create($params, ['idempotency_key' => 'order-1001']);Rate limits
/api/v1 allows 240 requests per minute, counted per key: the allowance
follows the key wherever it is used from, and your live and test keys are
counted apart. A request that carries no key is counted against the calling
address instead, and the count is taken before the key is read, so requests
rejected as unauthorised still use the allowance up.
Every response carries x-ratelimit-limit, x-ratelimit-remaining and
x-ratelimit-reset (seconds until the window resets). Beyond the limit the API
answers 429 with retry-after in seconds and this body:
{
"code": "RATE_LIMITED",
"message": "Rate limit exceeded, retry in 1 minute"
}The libraries honour retry-after and retry for you.