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​