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

# AgriBackup API Rate Limits, Headers, and Throttling

> Understand how AgriBackup enforces rate limits, read response headers to track your quota, and implement backoff strategies to stay within bounds.

The AgriBackup API enforces rate limits on every endpoint to protect platform stability for all enterprise customers. Every response you receive includes headers that tell you exactly where you stand in the current window — use them to pace your requests proactively rather than waiting for a rejection.

## Standard Limits

Live keys (`sk_live_`) are limited to **100 requests per second**. Sandbox keys (`sk_test_`) share the same limit so your load-testing environment reflects production behaviour accurately.

<Note>
  Rate limits are applied per API key, not per IP address or organisation. If you manage multiple integrations, issue a separate key for each to isolate their quotas from one another.
</Note>

## Rate-Limit Response Headers

Every API response — including successful ones — carries three headers you can inspect to track your remaining quota:

| Header                  | Type      | Description                                                      |
| ----------------------- | --------- | ---------------------------------------------------------------- |
| `X-RateLimit-Limit`     | `integer` | The maximum number of requests permitted per second for your key |
| `X-RateLimit-Remaining` | `integer` | The number of requests you have left in the current window       |
| `X-RateLimit-Reset`     | `integer` | UTC epoch timestamp (seconds) at which the window resets         |

Parse these headers on every response so your client can throttle itself before hitting the hard limit.

## HTTP 429 — Too Many Requests

When you exceed the rate limit, the API returns `429 Too Many Requests`. The response body follows the standard `ApiError` schema and includes a `Retry-After` header indicating how many seconds to wait before retrying.

```json theme={null}
{
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "You have exceeded the allowed request rate. Please slow down.",
  "details": []
}
```

<Warning>
  Do not immediately retry a `429` response. The server will continue to reject your requests until the window resets. Respect the `Retry-After` header value before issuing your next request.
</Warning>

## Handling Rate Limits in Your Integration

### Recommendations

* **Read `X-RateLimit-Remaining` proactively.** If the value drops below 10, introduce an artificial delay before the next request rather than waiting for a `429`.
* **Use batch endpoints where available.** Submitting multiple polygons or batches in a single call consumes one request unit instead of many. Prefer `POST /polygons` with an array payload over looping individual calls.
* **Apply exponential backoff with jitter.** If you do receive a `429`, wait for the `Retry-After` interval, then retry with an exponentially increasing delay to avoid a thundering-herd pattern.
* **Queue writes during bursts.** For high-volume ingestion pipelines, use an internal queue and drain it at a controlled rate (e.g. 80 requests/second) to give yourself headroom.

### Python — Exponential Backoff

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

API_KEY = "sk_live_YOUR_API_KEY"
BASE_URL = "https://live.agribackup.com/api/v1"

def request_with_backoff(method: str, path: str, **kwargs) -> requests.Response:
    """Make an API request, retrying with exponential backoff on 429."""
    url = f"{BASE_URL}{path}"
    headers = {"X-API-Key": API_KEY, **kwargs.pop("headers", {})}
    max_retries = 5
    base_delay = 1.0  # seconds

    for attempt in range(max_retries):
        response = requests.request(method, url, headers=headers, **kwargs)

        if response.status_code != 429:
            return response

        retry_after = int(response.headers.get("Retry-After", base_delay * (2 ** attempt)))
        jitter = random.uniform(0, 0.5)
        sleep_time = retry_after + jitter

        print(f"Rate limited. Retrying in {sleep_time:.2f}s (attempt {attempt + 1}/{max_retries})")
        time.sleep(sleep_time)

    raise Exception("Max retries exceeded after repeated 429 responses.")


# Example usage
response = request_with_backoff("GET", "/enterprise/eudr/polygons")
print(response.json())
```

### Node.js — Exponential Backoff

```javascript theme={null}
import axios from "axios";

const API_KEY = "sk_live_YOUR_API_KEY";
const BASE_URL = "https://live.agribackup.com/api/v1";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function requestWithBackoff(method, path, options = {}) {
  const url = `${BASE_URL}${path}`;
  const headers = { "X-API-Key": API_KEY, ...options.headers };
  const maxRetries = 5;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await axios({ method, url, headers, ...options });
      return response.data;
    } catch (err) {
      if (err.response?.status !== 429) throw err;

      const retryAfter = parseInt(err.response.headers["retry-after"] ?? "1", 10);
      const jitter = Math.random() * 500; // up to 500 ms of jitter
      const delay = retryAfter * 1000 * Math.pow(2, attempt) + jitter;

      console.warn(`Rate limited. Retrying in ${(delay / 1000).toFixed(2)}s (attempt ${attempt + 1}/${maxRetries})`);
      await sleep(delay);
    }
  }

  throw new Error("Max retries exceeded after repeated 429 responses.");
}

// Example usage
const data = await requestWithBackoff("GET", "/enterprise/eudr/polygons");
console.log(data);
```

<Tip>
  If you are running bulk data migrations or end-of-month compliance archiving, schedule those jobs during off-peak hours and target no more than 70 requests/second to give yourself a comfortable buffer below the 100 rps ceiling.
</Tip>
