Utopia PaymentsDocs

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 5xx is not a final answer and is not stored, so retrying with the same key runs the request again.
  • A stored response drops any secret field. Replaying POST /webhooks returns the endpoint without its whsec_… secret, so keep the secret from the first answer, or read it from GET /webhooks/:id/secret.

Override the automatically generated key when your application already has a stable operation id:

Node.js
await utopia.checkoutSessions.create(params, { idempotencyKey: 'order-1001' });
PHP
$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.

On this page