Rate Limiting
Overview
Naiza enforces rate limits and quotas to ensure fair usage and system stability. All external API endpoints are subject to these limits.
Quota System
Default Quota
Quota Headers
Responses include quota information in headers:
X-Quota-Limit: 10000 X-Quota-Remaining: 8750 X-Quota-Reset: 1640995200
| Header | Description |
|---|---|
| X-Quota-Limit | Total quota limit for the current window |
| X-Quota-Remaining | Remaining requests in the current window |
| X-Quota-Reset | Unix timestamp when the quota resets |
Checking Quota
const response = await fetch(url, options);
const remaining = response.headers.get('X-Quota-Remaining');
const limit = response.headers.get('X-Quota-Limit');
console.log(`Quota: ${remaining}/${limit} remaining`);Rate Limiting
Per-Endpoint Limits
Different endpoints may have different rate limits:
Current default throttle in controllers: 300 requests / 60 seconds per key for most public endpoints.
Rate Limit Headers
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1640995200 Retry-After: 60
Handling 429 Responses
When you exceed rate limits or quotas, you'll receive a 429 Too Many Requests response:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Quota exceeded",
"details": {
"limit": 10000,
"window": "1 hour",
"retryAfter": 3600
}
}
}Use whichever is available first: the Retry-After header, or error.details.retryAfter in the response body.
Retry Logic
Implement exponential backoff when receiving 429 responses:
async function makeRequestWithBackoff(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) {
return response;
}
if (response.status === 429) {
const error = await response.json();
const retryAfter = error.error.details.retryAfter ||
parseInt(response.headers.get('Retry-After')) ||
Math.pow(2, attempt);
console.log(`Rate limited. Retrying after ${retryAfter} seconds`);
await sleep(retryAfter * 1000);
continue;
}
// Don't retry other errors
throw new Error(`Request failed: ${response.status}`);
}
throw new Error('Max retries exceeded');
}Best Practices
1. Monitor Quota Usage
function checkQuotaBeforeRequest() {
// Store quota info from last response
const quotaRemaining = getStoredQuotaRemaining();
if (quotaRemaining < 100) {
console.warn('Low quota remaining. Consider throttling requests.');
}
}2. Implement Request Throttling
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
async waitIfNeeded() {
const now = Date.now();
// Remove old requests outside the window
this.requests = this.requests.filter(
time => now - time < this.windowMs
);
if (this.requests.length >= this.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = this.windowMs - (now - oldestRequest);
await sleep(waitTime);
return this.waitIfNeeded();
}
this.requests.push(now);
}
}
const limiter = new RateLimiter(100, 60000); // 100 requests per minute
async function makeThrottledRequest(url, options) {
await limiter.waitIfNeeded();
return fetch(url, options);
}3. Batch Operations
When possible, batch multiple operations to reduce API calls:
// Instead of multiple calls
for (const item of items) {
await createListItem(item);
}// Batch if the API supports it await createListItems(items);
4. Cache Responses
Cache responses when appropriate to reduce API calls:
const cache = new Map();
async function getCachedDecision(decisionId) {
const cacheKey = `decision:${decisionId}`;
if (cache.has(cacheKey)) {
const cached = cache.get(cacheKey);
if (Date.now() - cached.timestamp < 60000) { // 1 minute cache
return cached.data;
}
}
const response = await fetch(`/api/v1/decisions/${decisionId}`, options);
const data = await response.json();
cache.set(cacheKey, {
data,
timestamp: Date.now(),
});
return data;
}5. Handle Quota Exhaustion Gracefully
async function handleQuotaExhaustion() {
// Option 1: Queue requests for later
await queueRequest(request);
// Option 2: Notify user
notifyUser('API quota exhausted. Requests will be processed when quota resets.');
// Option 3: Fallback behavior
return getCachedDecision(decisionId);
}Quota Plans
Quota limits vary by subscription plan. Contact support to upgrade your plan for higher limits.
Default Plan
Enterprise Plans
- Custom quotas available
- Dedicated rate limits
- Priority support
Monitoring
Dashboard
Monitor your quota usage in the Naiza dashboard:
Settings → Usage → Quota
Programmatic Access
Check usage via the dashboard API (JWT + tenant context — not the external x-api-key /api/v1 surface):
GET /v1/usage/summary Authorization: Bearer <jwt-token>
Response:
{
"periodStart": "2026-04-01T00:00:00.000Z",
"periodEnd": "2026-05-01T00:00:00.000Z",
"apiRequestsCount": 125000,
"eventsCount": 98000,
"distinctCustomersCount": 12000,
"asOf": "2026-04-16T10:30:00.000Z"
}Troubleshooting
"Quota exceeded" errors
- Check current usage: Review quota headers in responses
- Wait for reset: Quota resets at the start of each window
- Optimize requests: Reduce unnecessary API calls
- Upgrade plan: Contact support for higher limits
High quota usage
- Review request patterns: Identify unnecessary calls
- Implement caching: Cache responses when appropriate
- Batch operations: Combine multiple operations
- Monitor headers: Track quota usage via response headers