Rate Limits
API requests are rate-limited per tenant based on your plan tier. Rate limits protect the platform and ensure fair usage across all tenants.
Per-tier limits
| Tier | Requests/min | Requests/hour | Monthly profiles |
|---|---|---|---|
| Basic | 30 | 1,000 | 500 |
| Professional | 100 | 5,000 | 5,000 |
| Enterprise | 500 | 20,000 | Unlimited |
Event processing endpoints (/events/*) have separate, higher limits to accommodate real-time transaction feeds:
| Tier | Events/min | Events/hour |
|---|---|---|
| Basic | 100 | 5,000 |
| Professional | 500 | 25,000 |
| Enterprise | 2,000 | 100,000 |
Rate limit headers
Every API response includes rate limit headers:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests per minute for your tier | 100 |
X-RateLimit-Remaining | Requests remaining in the current window | 97 |
X-RateLimit-Reset | Unix timestamp when the current window resets | 1712505600 |
Example response headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1712505600
Handling 429 responses
When you exceed your rate limit, the API returns a 429 Too Many Requests response:
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please retry after the reset time.",
"status": 429
}
The response includes a Retry-After header with the number of seconds to wait.
Exponential backoff
Implement exponential backoff for rate-limited requests:
- Node.js
- Python
- Go
async function fetchWithRetry(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") || "1");
const delay = retryAfter * 1000 * Math.pow(2, attempt);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
return response;
}
throw new Error("Max retries exceeded");
}
import time
import requests
def fetch_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
delay = retry_after * (2 ** attempt)
time.sleep(delay)
continue
return response
raise Exception("Max retries exceeded")
func fetchWithRetry(url string, maxRetries int) (*http.Response, error) {
for attempt := 0; attempt < maxRetries; attempt++ {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode == 429 {
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
if retryAfter == 0 {
retryAfter = 1
}
delay := time.Duration(retryAfter) * time.Second * time.Duration(1<<attempt)
time.Sleep(delay)
continue
}
return resp, nil
}
return nil, fmt.Errorf("max retries exceeded")
}
Best practices
- Monitor remaining requests — Check
X-RateLimit-Remainingto proactively slow down before hitting the limit - Batch events where possible — Use the CBA webhook integration to receive events in batches rather than sending individual event API calls
- Cache responses — Cache
GETresponses (profile details, rules, configuration) to avoid unnecessary API calls - Use webhooks — Instead of polling for signal updates, use webhooks to receive results asynchronously
- Contact us for higher limits — If you need higher rate limits, contact your account manager or reach out to support@korastratum.com