Key concepts
Rate limiting
Nexpay enforces rate limits to protect the API from abuse and ensure consistent performance for all users. When you exceed a rate limit, the API responds with HTTP 429 Too Many Requests.
Default limits
The API applies a global rate limit of 200 requests per second per client. This limit applies to all endpoints unless a stricter per-endpoint limit is in place.
Per-endpoint limits
Some endpoints have stricter limits to prevent abuse:
| Endpoint | Limit | Window |
|---|---|---|
POST /auth/login | 10 requests | 60 seconds |
POST /auth/signup | 5 requests | 60 seconds |
POST /chat | 10 requests | 60 seconds |
GET /chat/:conversationId | 30 requests | 60 seconds |
Handling rate limit errors
When you exceed the rate limit, the API returns a 429 status code along with the following response headers — your retry policy should always honour Retry-After first:
| Header | Example | Meaning |
|---|---|---|
Retry-After | 5 | Seconds until the next request will be accepted. Use this verbatim before applying any client-side backoff. |
X-RateLimit-Limit | 200 | Bucket ceiling for the calling key. |
X-RateLimit-Remaining | 0 | Requests left in the current window. |
X-RateLimit-Reset | 1717400000 | Unix epoch seconds at which the window resets. |
{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests"
}
Buckets are scoped per API key. Sharing one key across many pods aggregates against the same bucket — shard by key when you fan out. 503 responses for maintenance also include Retry-After.
Best practices
- Implement exponential backoff — When you receive a
429response, wait before retrying. Start with a short delay (e.g. 1 second) and double it on each consecutive429response. - Cache responses — Avoid unnecessary repeated requests by caching responses locally, especially for data that doesn't change often (e.g. lookup data, payee lists).
- Spread requests over time — If you need to make many requests (e.g. bulk imports), spread them evenly rather than sending them all at once.
- Use query parameters efficiently — Retrieve multiple records per request using pagination instead of fetching them one by one.
Retry example
const fetchWithRetry = async (url, options, maxRetries = 3) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429 || (response.status >= 500 && response.status !== 501)) {
// 1. Honor the server's Retry-After if present.
const retryAfter = Number(response.headers.get('Retry-After'));
// 2. Otherwise exponential backoff with ±30% jitter to avoid herd-fail.
const base = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: Math.min(30_000, 2 ** attempt * 1000);
const jitter = base * (0.7 + Math.random() * 0.6);
await new Promise((r) => setTimeout(r, jitter));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
};
The retry policy above covers 429 and transient 5xx (502/503/504), caps the delay at 30 seconds, and adds jitter so a fleet of pods doesn't thunder-herd the next window.