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.
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.
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.
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "You have exceeded the allowed request rate. Please slow down.",
"details": []
}
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.
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
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
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);
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.