Skip to main content
Rinne applies rate limits to keep the platform fast and stable for everyone. Limits are enforced consistently, so you should design your integration to stay within them from the start.

How the limits work

There are two rate limits, both measured in requests per second (req/s):
Request typeLimit
POST requests for transaction creation and 3DS session creation and authentication1000 req/s
Every other request50 req/s
Each limit is a single shared pool, not a per-endpoint allowance. The limiter counts the total number of requests you send across all endpoints in a bucket, then compares that sum against the limit. Sending requests to many different endpoints does not give you a higher combined throughput.
The two pools are independent of each other. Transaction-creating or 3DS-creating requests draw from the 1000 req/s pool, while all other requests draw from the 50 req/s pool.

Same across all environments

Rate limits behave identically in every environment, including sandbox. This means you can validate your integration against the real limits before going to production.
Use sandbox to load-test your request patterns. Because the limits match production exactly, the behavior you observe in sandbox is the behavior you will get in production.

When you exceed a limit

Exceeding a limit does not block your account or interrupt service. Requests that stay within the limit always succeed and the API keeps working normally. Only the excess requests above the limit are rejected. Rejected requests return a 429 Too Many Requests status code with no response body. The rate limit details are returned in the response headers:
HTTP/1.1 429 Too Many Requests
x-ratelimited: true
x-ratelimit-limit: 50, 50;w=1
x-ratelimit-remaining: 0
x-ratelimit-reset: 1
HeaderDescription
x-ratelimitedSet to true when the request was rejected for exceeding the limit.
x-ratelimit-limitThe limit for the applicable pool. 50, 50;w=1 means 50 requests per 1-second window.
x-ratelimit-remainingRequests remaining in the current window. 0 when the limit has been reached.
x-ratelimit-resetSeconds until the window resets and requests are accepted again.
Unlike other API errors, a rate limit rejection has no JSON body. Detect it from the 429 status code and the x-ratelimit-* headers rather than parsing a response body.
There is no penalty or cooldown beyond the rejected request itself. As soon as your request rate drops back within the limit, requests succeed again.

Best practices

Smooth out bursts on the client side. Spreading requests evenly across each second keeps you under the limit far more reliably than sending large bursts.
  • Throttle outbound requests so your sustained rate stays within the applicable pool.
  • Retry rejected requests with a short backoff. Because there is no penalty, a rejected request can be retried as soon as your rate is back within the limit.
  • Use idempotency for transaction creation. Include a request_id when creating transactions so safe retries never produce duplicates. See Error handling for details.
// Assumes your HTTP client throws an error exposing the response status
// (e.g. error.status === 429). A 429 has no body, so detect it from the status.
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      // Only retry rate-limit rejections, and only while retries remain.
      // Anything else (or the last attempt) is rethrown to the caller.
      if (error.status === 429 && i < maxRetries - 1) {
        const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
        await new Promise((resolve) => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

// Usage
const transaction = await retryWithBackoff(() => createTransaction(data));

Next steps

Error handling

Handle 429 and other error responses

API Reference

Explore all endpoints and schemas