Rate Limits
The Qarion API enforces rate limits to ensure fair usage and protect the platform's stability. Understanding these limits — and designing your integration to respect them — is important for building reliable automations that don't get throttled unexpectedly.
Default Limits
Rate limits are applied per API key and vary by subscription tier:
| Tier | Requests/Minute | Requests/Hour |
|---|---|---|
| Free | 60 | 1,000 |
| Standard | 300 | 10,000 |
| Enterprise | Custom | Custom |
These limits count all requests equally regardless of endpoint — a GET request to list products consumes the same quota as a POST request to create one. The per-minute limit provides burst protection, while the per-hour limit caps sustained throughput.
Response Headers
The API includes rate limit information in the headers of every response, allowing your application to monitor its usage in real time and throttle itself proactively before hitting the limit:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 250
X-RateLimit-Reset: 1706792400
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per window |
X-RateLimit-Remaining | Requests left in window |
X-RateLimit-Reset | Unix timestamp when window resets |
By checking X-RateLimit-Remaining after each request, your application can slow down as it approaches the limit rather than waiting for a hard rejection.
Rate Limit Exceeded
When you exceed the limit, the API returns a 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait before sending another request:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"detail": "Rate limit exceeded. Retry after 30 seconds."
}
Handling Rate Limits
The most robust way to handle rate limits is to build retry logic directly into your API client. Both examples below demonstrate a simple pattern that waits for the duration specified in the Retry-After header before retrying automatically.
Python Example
import time
import requests
class RateLimitedClient:
def __init__(self, api_key, base_url="https://api.qarion.com"):
self.api_key = api_key
self.base_url = base_url
def request(self, method, path, **kwargs):
headers = {"Authorization": f"Bearer {self.api_key}"}
while True:
response = requests.request(
method,
f"{self.base_url}{path}",
headers=headers,
**kwargs
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Retrying in {retry_after}s...")
time.sleep(retry_after)
continue
return response
JavaScript Example
class QarionClient {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.qarion.com';
}
async request(method, path, options = {}) {
const url = `${this.baseUrl}${path}`;
const headers = {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
};
while (true) {
const response = await fetch(url, { method, headers, ...options });
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response;
}
}
}
Best Practices
Cache Responses
The most effective way to stay within rate limits is to avoid unnecessary requests entirely. Cache the results of GET requests locally when the underlying data doesn't change frequently — product metadata, space configurations, and user profiles are all good candidates for caching with a TTL of a few minutes.
from functools import lru_cache
@lru_cache(maxsize=100)
def get_product(product_id):
return client.get(f"/catalog/spaces/{space}/products/{product_id}")
Batch Requests
When you need to operate on multiple resources, use bulk endpoints where they are available. This dramatically reduces the number of API calls compared to making individual requests in a loop:
# Bad: Individual requests
for product_id in product_ids:
client.get(f"/products/{product_id}") # 100 requests
# Good: Batch request
client.post("/products/batch", json={"ids": product_ids}) # 1 request
Use Webhooks
If your integration polls the API periodically to check for updates — for example, monitoring for new issues or quality alert — consider using webhooks instead. Webhooks deliver events to your application as they occur, eliminating the need for polling entirely and significantly reducing your API usage.
Implement Backoff
When retrying after a rate limit or server error, use exponential backoff with jitter. This spreads out retry attempts and prevents a "thundering herd" scenario where many clients retry simultaneously:
def request_with_backoff(func, max_retries=5):
for attempt in range(max_retries):
try:
return func()
except RateLimitError:
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
raise Exception("Max retries exceeded")
Endpoint-Specific Limits
Some endpoints have stricter limits than the general rate cap, because they trigger expensive operations (such as executing a quality check against a warehouse or syncing metadata from an external source):
| Endpoint | Limit |
|---|---|
POST /search | 30/min |
POST /quality/checks/{id}/run | 10/min |
POST /spaces/{slug}/source-systems/{id}/sync | 5/min |
If your automation needs to trigger these operations frequently, consider batching them or spacing them out over time to stay within these tighter limits.
Increasing Limits
If the default rate limits are insufficient for your production workload, contact the Qarion support team to discuss higher rate limits for production use, dedicated API capacity for high-volume integrations, and custom SLAs with guaranteed throughput.