What is limited
Live mode. Test mode gets a quarter of the sustained rate on the credential-keyed policies, so a load test cannot spend your production budget.
Reading payments, creating refunds, listing balances, running payouts, managing subscriptions and invoices, registering webhook endpoints, and the platform account endpoints have no enforced limit today. We are measuring them, and limits will be introduced there. Exact numbers will be published here, and announced in the changelog, at least 60 days before they are enforced. Build the backoff described below once and apply it everywhere so that change is a non-event for you.
The budget belongs to the store
The credential-keyed limits are counted against your store, not against the individual key. Issuing a second API key does not buy a second budget, and rotating a key does not reset the one you have. This is the same model Stripe and Modern Treasury use, and it is what lets us publish a number that means something. Two consequences worth designing around: Platforms are budgeted per sub-merchant. When you sendX-Southpay-Account, the request is counted against that sub-merchant, not against your platform credential. One busy sub-merchant cannot exhaust the budget your other sub-merchants depend on.
Test and live are separate buckets. They are different stores’ worth of budget in practice, so a load test in test mode never touches live traffic.
The dashboard has its own budget. A person signed in to the SouthPay dashboard is counted separately from your API keys, and connected OAuth apps are counted separately again. A batch job that exhausts your API budget cannot lock a colleague out of the dashboard, and a busy dashboard cannot slow your integration down.
Shoppers are counted per checkout, not per address
The checkout endpoints a customer’s browser calls are budgeted per checkout reference, with a much higher per-IP ceiling behind it purely to stop abuse. This matters because shoppers share addresses. Mobile carrier NAT and office networks put hundreds of unrelated people behind one IP, and budgeting a checkout by address would let strangers throttle each other. A429 on a checkout is the one throttle that costs a merchant money directly, so it is keyed to the thing that identifies a single shopper’s session.
The practical effect: your customer’s checkout has its own budget, and nobody else can spend it.
How the limits behave
Each limit is a token bucket. The bucket refills continuously at the sustained rate, and the burst column is how many requests you can spend at once from a full bucket. There are no window boundaries and no clock alignment. A request is admitted the moment the bucket has a token for it, so you never need to guess when a window rolls over. Sustained traffic below the rate in the table never throttles, however it is distributed.This replaced a fixed one minute window in August 2026. If you built against the old behaviour, the guidance to “wait until the top of the minute” no longer applies. Read
Retry-After instead.Reading your headroom
Every authenticated response carries your current budget, so you can pace yourself without waiting to be throttled.X-RateLimit-Limit reports the burst bucket, not the per-minute rate, so that it and X-RateLimit-Remaining measure the same thing. The sustained rate lives in RateLimit-Policy as q per w seconds. In the example above you may hold 200 tokens at once, and they refill at 1,200 per minute.X-RateLimit-Reset is delta-seconds, not a Unix timestamp. The RateLimit and RateLimit-Policy pair follows draft-ietf-httpapi-ratelimit-headers. That draft is not yet an RFC and its syntax has changed between revisions, so treat the X- headers as the stable contract and the draft pair as additive.
What a 429 looks like
Every throttled request returns the same envelope as the rest of the API, and always carriesRetry-After.
Response 429
Retry-After is in seconds and is never less than 1. Waiting exactly that long is enough; you do not need to guess.
A 429 with code: "too_many_failed_attempts" is different: it means too many requests from your address failed authentication. Fix the credential rather than retrying, then the budget clears on its own.
Backing off
A throttled request is rejected before it runs, so it changes nothing on our side. It creates no payment intent and consumes no idempotency key. Retrying is always safe.southpay/retry.ts
Idempotency-Key on the retry. See Idempotency for what that guarantees.
Staying under the limits
WatchX-RateLimit-Remaining in batch jobs. It is the cheapest way to pace a migration or a nightly run. Slow down as it approaches zero rather than sprinting into a 429.
Read onramp payment methods once per checkout and cache the result. It is the tightest limit on the API, and a 429 there interrupts a paying customer. Your server and the shopper’s browser each get their own budget on a checkout, but both count against that checkout’s overall ceiling.
Cache your OAuth access token. Tokens are reusable until they expire. Refresh as expiry approaches, not on every call.
Do not retry a 429 immediately. Read Retry-After and wait.
Polling a payment for its status is not throttled today, so it will not produce a 429. It is still the wrong pattern. Subscribe with webhooks and you learn about the state change sooner.

