Skip to content

Rate limiting

The ceilings a request passes, and how to keep batch work away from checkout.

A 429 is not a failure of the API. It is the API telling you that something in your code is issuing requests faster than it needs to, and the only reason it ever reaches a customer is that the noisy job and the checkout call share the same quota.

The quota table lives in Rate Limits. This page is about not needing it.

Three ceilings, checked in order

Every authenticated request passes three independent counters, each on a 60 second window. The first one that is full answers, and the body names which.

typeCountsScope
teamEvery request your team makesYour plan's ceiling
api_keyEvery request made with one keyThe same ceiling as the team
endpointRequests touching payments, transfers, refunds or customersTighter, and scaled by plan

Two consequences are worth internalising. Every key draws on the same team budget, so splitting traffic across several keys adds no headroom. And the endpoint ceilings are the ones a burst hits first: a loop over payments will trip endpoint long before it troubles team. On Enterprise, none of the three is counted.

Requests that arrive without a recognised key are counted per IP at 30 per minute, with type set to ip. That is what you see when a key is wrong rather than merely busy.

Read the ceiling, do not hardcode it

Every response carries your current team-level position. Those three headers are the truth, and they move when your plan does.

Headers on every response
X-RateLimit-Limit: 360
X-RateLimit-Remaining: 357
X-RateLimit-Reset: 1748083260

If a job of yours is anywhere near the limit, have it read X-RateLimit-Remaining and slow itself down before it gets pushed. Reacting to the header costs nothing; reacting to the 429 costs a round trip and a delay you did not choose.

Two shapes of 429

Most 429s come from the counters above and carry a type. A few routes have a second, dedicated throttle on top, and those answer without one. The payout writes are the ones to know: POST /transfers, POST /transfers/batch, POST /accounts/{id}/payouts, POST /beneficiaries, POST /beneficiaries/{id}/receivables and POST /beneficiaries/{id}/receivables/{receivable}/settle share a flat ceiling of 20 per minute per team and environment, independently of your plan, because a payout executes with no human in the loop. A loop that creates a beneficiary and then pays it counts twice.

The two bodies
{
  "code": 429,
  "status": "Too Many Requests",
  "message": "Too many requests",
  "type": "endpoint",
  "retry_after": 24,
  "retry_after_human": "00:00:24"
}

{
  "code": 429,
  "status": "Too Many Requests",
  "message": "Too Many requests. Merchant limit : 360",
  "retry_after": 24
}

Read type when it is there, and treat its absence as a route-specific throttle rather than as a malformed response. Both shapes carry retry_after in seconds. Only the first also sets the Retry-After header.

Wait the time you were given

The delay is in the response. Guessing produces either a wasted wait or a second 429.

Honour Retry-After, then back off
import { WajubRateLimitError } from '@wajub/node';

export async function withRateLimitRetry(fn, maxAttempts = 5) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (!(err instanceof WajubRateLimitError) || attempt >= maxAttempts) throw err;

      const seconds = err.retryAfter ?? 2 ** attempt;
      await new Promise((r) => setTimeout(r, seconds * 1000 + Math.random() * 500));
    }
  }
}

The SDK reads Retry-After for you and puts it on err.retryAfter. The random tail matters when several workers were throttled together: without it they all come back at the same instant and refill the bucket they were waiting on.

Keep batch work away from checkout

Reconciliation sweeps, settlement exports, and any loop over transfers or beneficiaries are where 429s are made. They are also the easiest to fix, because nobody is waiting on them.

HabitInstead
Promise.all over every idA paced loop under the endpoint's ceiling, see Performance
A sweep every minuteA sweep every 15 minutes, over a wider window
Running the export at noonRunning it when your checkout is quiet
One key for everythingA separate restricted key for batch work, scoped to the reads it needs

A separate key does not protect checkout on its own: every key draws on the same team budget and the same endpoint ceilings. It lets you revoke the job without touching checkout. Only pacing keeps a runaway export from spending the requests your checkout needs.

When you genuinely need more

If the ceiling is limiting real traffic rather than a loop, your plan's requests per minute is what moves. Contact support with the endpoint, the pattern, and the volume you expect. Raising a limit around a job that polls every payment every five seconds only delays the conversation.

What did you think of this content?