Aller au contenu
CoinsSendDéveloppeurs
Documentation Payments

Rate Limits and Safe Retries

Voir le MarkdownConfigurer un agent

Ce contenu n’est pas encore disponible dans votre langue.

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.