Skip to content
CoinsSendDevelopers
Payments documentation

Rate Limits and Safe Retries

View MarkdownAgent setup

The public contract does not promise universal X-RateLimit-* headers or one fixed global request count. Clients must handle HTTP 429 on any operation.

POST /v1/wallet-address has this merchant limit:

  • threshold: 100 successful wallet creations;
  • window: 60 seconds;
  • key: current merchant;
  • failed creation attempts are not counted;
  • rejection: HTTP 429 with status and message.

Example:

{
"status": "error",
"message": "Too many requests. Please try again later."
}

This response does not currently guarantee X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After, or retry_after.

Additional limits may apply. Their response bodies and headers are not part of a universal contract. Clients should tolerate HTTP 429 whether or not Retry-After is present.

OperationAutomatic retry after 429 or transport failure?Reason
GET /v1/get-coin-rateYes, with bounded backoffRead-only public discovery
GET /v1/coins-and-feeYes, with bounded backoff and cachingRead-only public discovery
GET /v1/merchants/balancesYes, with a fresh signature/timestamp when usedRead-only merchant state
GET /v1/merchants/feesYes, with a fresh signature/timestamp when usedRead-only merchant state
GET /v1/invoices/{invoiceCode}YesRead-only checkout state
GET /v1/wallet-addresses/{walletAddress}/qrYesRead-only binary response
POST /v1/invoicesNo blind retry after an unknown outcomeorder_id uniqueness returns 409; it does not replay the original response
POST /v1/withdrawalsNoNo request idempotency key; timestamp freshness is not idempotency
POST /v1/wallet-addressNo for static walletsA successful retry can create another address
POST /v1/invoices/{invoiceCode}/provider-card-ordersDo not generalizeOnly an already-active order is reused; there is no general Idempotency-Key contract

An HTTP response proves whether a request was rejected. A client-side timeout does not: the server may already have accepted the write.

For read operations, use bounded exponential backoff with jitter:

async function retrySafeRead(makeRequest, maxAttempts = 4) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const response = await makeRequest()
if (response.status !== 429 || attempt === maxAttempts) {
return response
}
const retryAfterHeader = response.headers.get('Retry-After')
const retryAfter = retryAfterHeader === null ? Number.NaN : Number(retryAfterHeader)
const baseDelayMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: Math.min(500 * 2 ** (attempt - 1), 8000)
const jitterMs = Math.floor(Math.random() * 250)
await new Promise(resolve => setTimeout(resolve, baseDelayMs + jitterMs))
}
}

For timestamp-signed reads, recompute Timestamp and Sign for each attempt. Never sleep and reuse a timestamp that can age beyond the 300-second signature window.

  1. Persist the client order/reference and request intent before sending.
  2. If a complete success response arrives, persist the returned CoinsSend ID.
  3. If the result is unknown, do not issue a blind duplicate.
  4. Reconcile using a known returned/public identifier where one exists, or investigate through merchant operations/support.
  5. For withdrawals, wait for the signed withdrawal webhook when an ID was obtained; otherwise escalate the unknown outcome instead of resubmitting.
  • Record endpoint, HTTP status, request correlation data, and Retry-After when present; never log the API key or full Sign header.
  • Separate application 429 responses from edge 429 responses.
  • Alert on sustained rate limiting and unknown outcomes for write operations.
  • Cache the catalog according to its advertised 900-second cache lifetime.

For request-signature freshness and canonicalization, see Authentication. For exact operation metadata, see OpenAPI.