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:

EndpointLimitWindow
POST /auth/login10 requests60 seconds
POST /auth/signup5 requests60 seconds
POST /chat10 requests60 seconds
GET /chat/:conversationId30 requests60 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:

HeaderExampleMeaning
Retry-After5Seconds until the next request will be accepted. Use this verbatim before applying any client-side backoff.
X-RateLimit-Limit200Bucket ceiling for the calling key.
X-RateLimit-Remaining0Requests left in the current window.
X-RateLimit-Reset1717400000Unix 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 429 response, wait before retrying. Start with a short delay (e.g. 1 second) and double it on each consecutive 429 response.
  • 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.

Previous
Errors