Rate Limits
The platform applies rate limiting in two layers:
| Layer | Where | What it protects against |
|---|---|---|
| Edge (firewall) | Before a request reaches the API | Volumetric abuse, brute force, distributed floods |
| Application | Per endpoint, after authentication | Thresholds specific to sensitive operations |
The edge layer is the baseline and is tuned per installation. The application layer is limited according to what the operation does — all of them are listed below.
The 429 response
When a limit is exceeded you get the standard error envelope, and the response carries a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 47{
"status": "failure",
"code": "RATE_LIMITED",
"statusCode": 429,
"requestId": "req_7f3d9c1a4b2e"
}Retry-After is in seconds, and you should honour it. Do not invent your own backoff — the server knows when the window resets and you do not. Retrying sooner only feeds the counter.
Depending on the surface you may see one of three codes: RATE_LIMITED · RATE_LIMIT_EXCEEDED · ACTIVATION_RATE_LIMITED. All carry 429; the distinction is useful when you log them.
Per-endpoint limits
Identity SDK
The window is 60 seconds:
| Endpoint | Limit |
|---|---|
analyze | 30 requests/min |
| Single event | 120 requests/min |
Batch events (events/batch) | 20 requests/min |
| Compatibility check | 60 requests/min |
The batch endpoint's limit is lower because each request carries many records. At volume, use the batch endpoint rather than sending single events — 20 batch requests carry far more events than 120 single ones.
Wallet portal
The authentication surface is limited on both IP and phone number, to resist brute force:
| Operation | Per IP | Per phone |
|---|---|---|
| Registration | 3 / hour | 3 / 5 min |
| Login | 10 / min · 40 / hour | 5 / min |
| OTP verification | 10 / min | — |
| OTP resend | 5 / hour | — |
Two axes is deliberate. Limiting on IP alone does not stop an attacker who rotates IPs; limiting on phone alone does not stop someone trying many numbers. Together they close both patterns.
POS terminal activation
The window is 5 minutes:
| Axis | Limit |
|---|---|
| Per IP | 10 attempts |
| Per serial number | 5 attempts |
The serial-number axis bounds attempts to guess the identity of a valid terminal.
Bank accounts — sync now
The window is 60 seconds:
| Endpoint | Limit |
|---|---|
POST /api/v1/bank-connect/accounts/{accountId}/sync | 6 requests/min per API key |
The 429 response carries RATE_LIMITED, the Retry-After header and details.retryAfterSeconds. Read endpoints do not count towards this limit. → Bank Accounts › Limits
Retrying correctly
async function callWithBackoff(fn: () => Promise<Response>) {
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fn();
if (res.status !== 429) return res;
// Honour what the server says; fall back to exponential backoff
const retryAfter = Number(res.headers.get('retry-after'));
const waitSec = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter
: 2 ** attempt;
await new Promise(r => setTimeout(r, waitSec * 1000));
}
throw new Error('rate limit: retries exhausted');
}Do not retry a 429 in an unbounded loop. Put a ceiling on it and then surface the error. Unbounded retries turn a temporary slowdown into a lasting outage — on your side and on ours.
Staying under the limits
- Use the batch endpoints. When feeding events,
events/batchis far more efficient than single calls. - Cache your token. Minting one per call creates needless requests. → Authentication
- Raise
limitwhen paginating. A page of 100 means four times fewer requests than a page of 25. → Pagination - Take webhooks rather than polling. Receive a webhook instead of asking in a loop whether something changed. → Webhooks
If the volume you expect is above the limits above, tell us in advance rather than discovering it by hitting them. Contact: [email protected]
Related
- Error Handling — the envelope and retry rules
- Best Practices — what to watch in production