> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revalonlabs.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Arbitrage API Rate Limits and Throttling

> Understand Arbitrage's rate limit tiers, response headers, and how to handle 429 errors with backoff strategies to keep your integration running smoothly.

Every request to the Arbitrage API is subject to rate limiting. Limits are applied per tier based on how you authenticate, and every response — including successful ones — includes headers that tell you exactly where you stand. Understanding these limits upfront lets you design integrations that stay well within bounds and recover gracefully when they don't.

## Rate limit tiers

Arbitrage enforces three distinct rate limit tiers. The tier that applies to your request depends on how you authenticate.

| Tier                  | Authentication                               | Requests per minute | Burst |
| --------------------- | -------------------------------------------- | ------------------- | ----- |
| Product read          | Unauthenticated (product session)            | 120                 | 40    |
| Authenticated session | Session JWT (`predex_siwe` cookie or bearer) | 60                  | 20    |
| API Key               | `X-API-Key: pdx_...` header                  | 600                 | 50    |

The **burst** value is the maximum number of requests you can send in a short burst before the per-minute window takes effect. API Key access provides the highest throughput — 600 requests per minute with a burst of 50 — making it the right choice for automated polling and data pipelines.

<Note>
  The Authenticated session tier (60 req/min) is lower than the Product read tier (120 req/min) because it is scoped to the Developer Console and operational routes. Use an API key for external integrations that need higher throughput.
</Note>

## Rate limit response headers

Every API response includes the following headers regardless of status code. Read these headers to track your remaining capacity and plan retries precisely.

| Header                  | Type                     | Description                                               |
| ----------------------- | ------------------------ | --------------------------------------------------------- |
| `X-RateLimit-Limit`     | integer                  | Maximum requests allowed in the current window            |
| `X-RateLimit-Remaining` | integer                  | Requests remaining in the current window                  |
| `X-RateLimit-Reset`     | integer (Unix timestamp) | When the current window resets and your limit replenishes |

### Reading headers with curl

The `-i` flag prints response headers alongside the body. Use it to inspect your current rate limit state:

```bash curl theme={null}
curl -i -H "X-API-Key: pdx_your_key_here" \
  "https://api.predexy.com/api/v1/external/arbitrage/opportunities"
```

You'll see headers like:

```text theme={null}
HTTP/2 200
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 1714147260
```

`X-RateLimit-Reset` is a Unix timestamp. Subtract the current time from it to calculate how many seconds remain before your window resets.

## When you exceed the limit

When your request rate exceeds the allowed limit, the API returns a `429 Too Many Requests` status with the following response body:

```json theme={null}
{
  "status": "RATE_LIMITED",
  "message": "Rate limit exceeded. Try again in 42 seconds."
}
```

The `message` field includes a human-readable countdown to help with debugging. The `X-RateLimit-Reset` header on the 429 response tells you the exact Unix timestamp when you can resume.

<Warning>
  Do not retry immediately after a 429. Sending additional requests before the window resets does not reset the clock and will continue to return 429s.
</Warning>

## Handling 429s in Python with exponential backoff

The safest approach is to check `X-RateLimit-Remaining` proactively and pause before you hit zero. When you do receive a 429, read `X-RateLimit-Reset` from the response headers and wait until that timestamp before retrying. The example below combines both strategies with exponential backoff as a fallback:

```python python theme={null}
import time
import requests

API_KEY = "pdx_your_key_here"
BASE_URL = "https://api.predexy.com"

def fetch_opportunities(max_retries: int = 5) -> dict:
    url = f"{BASE_URL}/api/v1/external/arbitrage/opportunities"
    headers = {"X-API-Key": API_KEY}

    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        # Proactive: pause if remaining capacity is low
        remaining = int(response.headers.get("X-RateLimit-Remaining", 1))
        if remaining < 10 and response.status_code == 200:
            reset_at = int(response.headers.get("X-RateLimit-Reset", 0))
            wait = max(0, reset_at - int(time.time()))
            if wait > 0:
                time.sleep(wait)

        if response.status_code == 200:
            return response.json()

        if response.status_code == 429:
            reset_at = int(response.headers.get("X-RateLimit-Reset", 0))
            wait = max(1, reset_at - int(time.time()))
            print(f"Rate limited. Waiting {wait}s before retry {attempt + 1}/{max_retries}.")
            time.sleep(wait)
            continue

        # For non-429 errors, use exponential backoff
        backoff = 2 ** attempt
        print(f"Request failed ({response.status_code}). Retrying in {backoff}s.")
        time.sleep(backoff)

    raise RuntimeError("Max retries exceeded.")
```

## Custom rate limits on API keys

When you create an API key in the Developer Console, you can set a custom `rate_limit` field. This overrides the default 600 req/min ceiling for that specific key and can be set anywhere from 1 to 10,000 requests per minute:

```json theme={null}
{
  "name": "High-Volume Scanner",
  "rate_limit": 1200
}
```

This is useful when you have multiple keys serving different workloads and want to allocate capacity explicitly. The custom limit is reflected in the `X-RateLimit-Limit` header on every response for that key.

<Tip>
  Use the `classification=actionable` filter when polling the arbitrage endpoint. Filtering to only actionable opportunities reduces response payload size and the request frequency you need to stay current — a straightforward way to get more value from each request within your limit.
</Tip>
