---
title: Rate Limiting
description: Understand API rate limits and how to handle them
---

The RizPay API uses rate limiting to ensure fair usage and system stability. Understanding rate limits helps you build reliable integrations.

## How It Works

Rate limits are enforced per API key on a per-minute window. Each API key has a fixed maximum number of requests per minute. Read your exact limit from the `X-RateLimit-Limit` header on any response rather than hardcoding a value. If you need a higher limit, contact support.

## Response Headers

Every API response includes rate limit headers, so you can track your usage without guessing:

| Header                  | Description                          |
| ----------------------- | ------------------------------------ |
| `X-RateLimit-Limit`     | Maximum requests allowed per minute  |
| `X-RateLimit-Remaining` | Requests remaining in current window |
| `X-RateLimit-Reset`     | Unix timestamp when the limit resets |

Example response headers:

```
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 291
X-RateLimit-Reset: 1705312860
```

## When You Hit the Limit

If you exceed the rate limit, you'll receive a `429 Too Many Requests` response:

```json
{
  "status": { "code": 429, "message": "Too Many Requests" },
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please retry after 45 seconds.",
    "retry_after": 45
  }
}
```

The response also includes a `Retry-After` header indicating how many seconds to wait:

```
Retry-After: 45
```

## Handling Rate Limits

### Basic Retry Logic

```javascript
async function makeRequestWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
      console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
      continue;
    }

    return response;
  }

  throw new Error("Max retries exceeded");
}
```

### Proactive Rate Limit Checking

```javascript
async function makeRequest(url, options) {
  const response = await fetch(url, options);

  const remaining = parseInt(response.headers.get("X-RateLimit-Remaining"));
  const resetTime = parseInt(response.headers.get("X-RateLimit-Reset"));

  if (remaining < 5) {
    const waitTime = resetTime * 1000 - Date.now();
    console.log(
      `Low on requests. ${remaining} remaining. Resets in ${waitTime}ms`
    );
  }

  return response;
}
```

## Best Practices

1. **Monitor rate limit headers** - Track remaining requests proactively
2. **Implement exponential backoff** - Don't hammer the API when rate limited
3. **Cache responses** - Reduce unnecessary API calls for data that doesn't change often
4. **Batch operations** - Combine multiple operations where possible
5. **Use webhooks** - Instead of polling for status updates, use webhooks

## Sandbox Rate Limits

The sandbox environment applies the same per-key rate limiting as production, returning the same `X-RateLimit-*` headers. Read your current limit from `X-RateLimit-Limit` while testing.

## Next Steps

- [Pagination](/docs/core-concepts/pagination) - Navigate large result sets
- [Error Handling](/docs/getting-started/errors) - Handle all error types