Rate limiting and throttling

API Rate limit 429 Throttling Retry

This article covers where the ReceiptRoller API limits the number of requests, what comes back when you hit a limit, and how to keep your client's traffic reasonable even where there is no limit. The store and business-account APIs you call with your app's access token have no request limit today. Limits exist only on a few individual endpoints, such as receiving a receipt or a printer sending one in.

APIs you call with your app's token

  • The /api/v1/... endpoints you call with an access token (products, sales, reservations and so on) have no request-rate limit, no per-plan limit and no daily quota.
  • So no rate-limit headers such as X-RateLimit-Limit, X-RateLimit-Remaining or X-RateLimit-Reset are returned.
  • The token endpoint POST /api/v1/auth/token has no limit on exchanging an authorization code (authorization_code) or a refresh token (refresh_token) either.

No limit does not mean any amount of parallel traffic is fine. Follow "Keeping your traffic reasonable" below.

Endpoints with their own limits

These endpoints limit the number of requests in the last minute (the last hour for email). Requests are counted per client IP address, or per the combination in the "Counted per" column.

EndpointLimitCounted per
POST /api/v1/devices/printer/receipts, POST /api/v1/devices/printer/receipts/{token}/ad-printed300 per minuteIP address
GET /api/v1/apps/{shopId}/ads30 per minuteIP address
GET /api/v1/receipt-ad60 per minuteIP address
GET /api/v1/terminals/{publicTerminalId}/last-receipt, GET /api/v1/rx/{claimToken}10 per minuteIP address and terminal (or token)
POST /api/v1/terminals/{publicTerminalId}/claim, POST /api/v1/rx/{claimToken}/claim3 per minute (also counted toward the 10 per minute above)IP address and terminal (or token)
POST /api/v1/user/terminals/{publicTerminalId}/claim, POST /api/v1/user/rx/{claimToken}/claim3 per minuteIP address and terminal (or token)
POST /api/v1/rx/{claimToken}/claim with delivery by email3 per hourDestination email address

Time-clock punches (POST /api/v1/stores/{storeId}/timecards/punch) are not rate-limited, but five wrong PINs in a row for the same person lock that person's punching for 15 minutes, and the call returns 429 with {"error": "pin_locked", "message": "...", "lockedUntil": "..."}.

The sign-in steps of the official ReceiptRoller apps (exchanging an ID token, creating an anonymous account) also have their own limits, but they do not apply to the authorization-code and refresh-token exchanges that developer apps use.

The response when you hit a limit

The status is 429 Too Many Requests. Depending on the endpoint it comes back in one of two ways.

  • For printer submissions (/api/v1/devices/printer/...), GET /api/v1/apps/{shopId}/ads and the 3-per-minute limit on each .../claim, you get a Retry-After: 60 header and the text body Too many requests. Please try again in a minute.
  • For the other limits (last-receipt, GET /api/v1/rx/{claimToken}, GET /api/v1/receipt-ad, the 10 per minute on claims, and 3 per hour for email), you get a 429 with no body and no Retry-After.
HTTP/1.1 429 Too Many Requests
Retry-After: 60

Too many requests. Please try again in a minute.

There is no JSON body or error code. Detect a 429 by its status code.

Keeping your traffic reasonable

  • Send bulk work one request at a time. Don't fire hundreds of requests at once with Promise.all or similar.
  • Use list endpoints instead of fetching items one by one. For example, the product list GET /api/v1/products returns everything in one response, so a sync fetches it once and matches on your side.
  • Cache data that rarely changes, such as stores and the product master.
  • Use webhooks instead of polling for changes (Receive new sales by webhook and post them to Slack).

Retry guidance

  • 429: if Retry-After is present, wait that many seconds and send again. If it is not, wait about a minute, since the limits count the last minute.
  • 503 and other 5xx, timeouts and connection errors: retry a few times, doubling the wait each time and adding some random jitter.
  • 400, 401, 403, 404: sending the same request again gives the same result. For 401, refresh the token and retry once.
  • Resending a create POST after a timeout can create a duplicate. There is no general Idempotency-Key header, so check whether the item was created (for example, in the list) before sending it again.
async function callWithRetry(send, maxAttempts = 5) {
  for (let attempt = 1; ; attempt++) {
    const response = await send();
    const retryable = response.status === 429 || response.status >= 500;
    if (!retryable || attempt === maxAttempts) return response;

    const retryAfter = Number(response.headers.get("Retry-After"));
    const backoff = Math.min(2 ** attempt, 60) * (0.75 + Math.random() * 0.5);
    const waitSeconds = response.status === 429 ? (retryAfter || 60) : backoff;
    await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000));
  }
}

Related articles

Published: 2026-04-27 Updated: 2026-10-26
About this topic
Developer API
See the feature
Tags
API (23) OAuth (18) Android (10) iOS (8) Webhook (5) App registration (4) Node.js (4) PIM (4) POS Integration (4) Quickstart (4)
Related articles