Rate Limits
Rate limiting is enforced on the API. Two limiters apply, both keyed by client IP address — not by credential or organization.
Global limit
Section titled “Global limit”Every endpoint is covered by a global limiter:
| Budget | 300 requests per 60 seconds per IP |
| Applies to | All endpoints, including /v1/ussd/*, /v1/customers, /v1/payment/* and /v1/webhooks/callback |
| Exempt | /healthz and /metrics |
Exceeding it returns 429 Too Many Requests:
{ "error": "Too Many Requests", "message": "Too many requests. Please slow down and try again shortly."}Token-endpoint limit
Section titled “Token-endpoint limit”POST /v1/oauth/token carries a second, much stricter limiter on top of the
global one, to blunt brute-forcing of client secrets:
| Budget | 10 requests per 15 minutes per IP |
| Applies to | POST /v1/oauth/token |
Exceeding it returns 429 Too Many Requests:
{ "error": "Too Many Requests", "message": "Too many authentication attempts. Please try again in a few minutes."}Response headers
Section titled “Response headers”Both limiters emit the RFC draft-6 rate-limit headers, as four separate fields:
| Header | Meaning | Example |
|---|---|---|
RateLimit-Limit |
Requests allowed in the current window | 300 |
RateLimit-Remaining |
Requests left in the current window | 299 |
RateLimit-Reset |
Seconds until the window resets | 60 |
RateLimit-Policy |
The policy being applied, as limit;w=window-seconds |
300;w=60 |
Retry-After |
Sent on a 429 — seconds to wait before retrying |
47 |
Exempt endpoints carry no rate-limit headers at all, so do not treat their absence as a budget of zero.
Handling a 429
Section titled “Handling a 429”Back off rather than retrying immediately:
- Read
Retry-Afterand wait at least that long. - Retry with exponential backoff and jitter.
- Because the limit is per IP, several processes behind one egress address share a single budget. Account for that when sizing concurrency.
A retried mutating request must reuse the same
X-Idempotency-Key so the operation still happens at
most once.