Rate Limiting
Understand API rate limits, headers, and how to handle rate limit errors.
Overviewβ
Rynko uses rate limiting to ensure fair usage and protect API performance. Rate limits are applied per project (referred to as teamId in the API) and use a sliding window algorithm for accurate tracking.
Important: Rate limits are enforced automatically on all API endpoints. Exceeding limits returns a 429 Too Many Requests error with retry information.
Rate Limits by Endpointβ
Rynko applies different rate limits based on the operation type:
Document Generation Endpointsβ
Rate limits vary by authentication type:
| Endpoint | API Key | OAuth | JWT | Window |
|---|---|---|---|---|
POST /api/v1/documents/generate | 100 | 50 | 100 | 60 seconds |
POST /api/v1/documents/generate/batch | 10 | 5 | 10 | 60 seconds |
Why different limits? OAuth has lower limits because third-party apps share platform resources. API Keys have higher limits for direct integrations by paying customers.
Query Endpointsβ
| Endpoint | API Key | OAuth | JWT | Window |
|---|---|---|---|---|
GET /api/v1/documents/jobs | 300 | 150 | 300 | 60 seconds |
GET /api/v1/documents/jobs/:id | 300 | 150 | 300 | 60 seconds |
GET /api/v1/templates/* | 300 | 150 | 300 | 60 seconds |
Why 300/minute? Read operations are lightweight and used frequently in dashboards/reporting.
General Rate Limitsβ
| Authentication | Rate Limit | Window |
|---|---|---|
| API Key | 100 | 60 seconds |
| OAuth | 120 | 60 seconds |
| JWT (Dashboard) | 200 | 60 seconds |
| Unauthenticated | 30 | 60 seconds |
How Rate Limiting Worksβ
Rynko uses a sliding window algorithm with Redis for accurate, distributed rate limiting:
Sliding Window Algorithmβ
Window: 60 seconds
Limit: 100 requests (for document generate with API Key)
[Request 1] [Request 2] ... [Request 100] β All allowed
β
[Request 101] β Rate limit exceeded (429 error)
β
Wait for window to slide forward
β
[Request 1 expires after 60s] β New slot available
Benefits over fixed windows:
- No burst traffic at window boundaries
- More accurate request counting
- Automatic cleanup of old requests
Rate Limit Scopeβ
Rate limits are applied per project:
Project A: 100 requests/min β
Project B: 100 requests/min β
(separate limit)
User 1 (Project A): Count towards Project A
User 2 (Project A): Count towards Project A (shared limit)
Key points:
- All project members share the same rate limit
- API keys count towards the project's limit
- Each project has independent limits
Rate Limit Headersβ
Every API response includes rate limit information in headers:
Response Headersβ
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 85
X-RateLimit-Reset: 2025-11-07T10:31:00.000Z
Content-Type: application/json
Header definitions:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed in window | 100 |
X-RateLimit-Remaining | Requests remaining in current window | 85 |
X-RateLimit-Reset | UTC timestamp when window resets | 2025-11-07T10:31:00.000Z |
When Rate Limit Exceededβ
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2025-11-07T10:31:00.000Z
Retry-After: 45
Content-Type: application/json
Additional header when exceeded:
| Header | Description | Example |
|---|---|---|
Retry-After | Seconds until you can retry | 45 |
Error Response Formatβ
When you exceed the rate limit, you'll receive a 429 error:
Rate Limit Exceeded Responseβ
{
"statusCode": 429,
"code": "ERR_QUOTA_003",
"message": "Rate limit exceeded",
"timestamp": "2025-11-07T10:30:00.000Z",
"path": "/api/v1/documents/generate",
"relatedInfo": {
"limit": 100,
"windowSeconds": 60,
"resetAt": "2025-11-07T10:31:00.000Z",
"retryAfterSeconds": 45
}
}
Response fields:
statusCode: Always429for rate limit errorscode:ERR_QUOTA_003(rate limit error code)message: Human-readable error messagerelatedInfo.limit: Your rate limit (requests per window)relatedInfo.windowSeconds: Window duration (60 seconds)relatedInfo.resetAt: When the window resets (UTC)relatedInfo.retryAfterSeconds: How long to wait before retrying
Handling Rate Limitsβ
Best Practicesβ
1. Check Headers Proactivelyβ
Monitor rate limit headers in every response:
const response = await fetch('https://api.rynko.dev/api/v1/documents/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ... })
});
// Check headers
const limit = parseInt(response.headers.get('X-RateLimit-Limit'));
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
const reset = new Date(response.headers.get('X-RateLimit-Reset'));
console.log(`Rate limit: ${remaining}/${limit} remaining`);
console.log(`Resets at: ${reset.toLocaleString()}`);
// Slow down if approaching limit
if (remaining < 5) {
console.warn('Approaching rate limit! Slowing down...');
await sleep(2000); // Wait 2 seconds before next request
}