Rate limits
Synjar enforces rate limits to ensure fair usage and system stability.
Default limits
| Endpoint type | Limit | Window |
|---|---|---|
| Search | 60 requests | 1 minute |
| Upload | 30 requests | 1 minute |
| Read operations | 100 requests | 1 minute |
| Write operations | 60 requests | 1 minute |
Rate limit headers
All responses include rate limit information:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1704110400
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests in window |
X-RateLimit-Remaining | Requests remaining |
X-RateLimit-Reset | Unix timestamp when limit resets |
Rate limit exceeded
When you exceed the limit:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 42 seconds."
}
}
Handling rate limits
Check remaining quota
Before making requests:
const response = await fetch(url, options);
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining < 10) {
console.warn('Running low on API quota');
}
Implement retry logic
async function fetchWithRateLimit(url, options) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(
response.headers.get('Retry-After') || '60'
);
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
return fetchWithRateLimit(url, options);
}
return response;
}
Queue requests
For high-volume applications:
class RequestQueue {
constructor(maxPerMinute = 50) {
this.queue = [];
this.processing = false;
this.interval = (60 / maxPerMinute) * 1000;
}
async add(fn) {
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject });
this.process();
});
}
async process() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const { fn, resolve, reject } = this.queue.shift();
try {
const result = await fn();
resolve(result);
} catch (error) {
reject(error);
}
await new Promise(r => setTimeout(r, this.interval));
this.processing = false;
this.process();
}
}
Best practices
Batch operations
Instead of many small requests:
// Instead of this
for (const doc of documents) {
await uploadDocument(doc);
}
// Do this
await uploadDocuments(documents); // Batch endpoint
Cache responses
Cache read operations when possible:
const cache = new Map();
async function getCached(key, fetchFn, ttl = 300) {
if (cache.has(key)) {
const { data, expires } = cache.get(key);
if (Date.now() < expires) return data;
}
const data = await fetchFn();
cache.set(key, { data, expires: Date.now() + ttl * 1000 });
return data;
}
Use webhooks
For real-time updates, prefer webhooks over polling:
// Instead of polling every 5 seconds
// setInterval(() => checkStatus(docId), 5000);
// Use webhook notifications
app.post('/webhook', (req, res) => {
const { event, documentId, status } = req.body;
handleStatusUpdate(documentId, status);
res.sendStatus(200);
});
Enterprise limits
Enterprise customers can request higher limits:
- Custom request quotas
- Dedicated API endpoints
- Priority processing
Contact sales@synjar.com for custom arrangements.