Skip to main content

Rate limits

Synjar enforces rate limits to ensure fair usage and system stability.

Default limits

Endpoint typeLimitWindow
Search60 requests1 minute
Upload30 requests1 minute
Read operations100 requests1 minute
Write operations60 requests1 minute

Rate limit headers

All responses include rate limit information:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1704110400
HeaderDescription
X-RateLimit-LimitMaximum requests in window
X-RateLimit-RemainingRequests remaining
X-RateLimit-ResetUnix 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.

See also