Implement Lokalise rate limiting, backoff, and request queuing patterns.
Use when handling rate limit errors, implementing retry logic,
or optimizing API request throughput for Lokalise.
Trigger with phrases like "lokalise rate limit", "lokalise throttling",
"lokalise 429", "lokalise retry", "lokalise backoff".
Lokalise enforces a strict 6 requests per second rate limit across all API endpoints (https://api.lokalise.com/api2). Exceeding this triggers a 429 Too Many Requests response. This skill covers request queuing with 170ms minimum spacing, exponential backoff for 429 recovery, bulk operation throttling, and proactive quota monitoring via response headers.
API token configured (read or read/write scope depending on operations)
Node.js 18+ for native AbortController support in timeout handling
Instructions
1. Understand the Rate Limit Headers
Every Lokalise API response includes rate limit headers:
X-RateLimit-Limit: 6 # Max requests per second
X-RateLimit-Remaining: 4 # Requests remaining in current window
X-RateLimit-Reset: 1700000000 # Unix timestamp when the window resets
Retry-After: 1 # Seconds to wait (only on 429 responses)
Always read these headers. Never hardcode assumptions about the window — Lokalise may adjust limits per plan tier.
2. Implement a Request Queue with 170ms Spacing
Space requests at minimum 170ms apart (1000ms / 6 = ~167ms, rounded up). Use p-queue for concurrency control:
import PQueue from "p-queue";
const lokaliseQueue = new PQueue({
concurrency: 1,
interval: 170,
intervalCap: 1,
});
async function queuedRequest<T>(fn: () => Promise<T>): Promise<T> {
return lokaliseQueue.add(fn, { throwOnTimeout: true });
}
// Usage with the SDK
import { LokaliseApi } from "@lokalise/node-api";
const lokalise = new LokaliseApi({ apiKey: process.env.LOKALISE_API_TOKEN });
const keys = await queuedRequest(() =>
lokalise.keys().list({
project_id: "123456789.abcdefgh",
limit: 500,
page: 1,
})
);
3. Add Exponential Backoff for 429 Recovery
When a 429 occurs, honor the Retry-After header first. If absent, use exponential backoff with jitter:
Track remaining quota and preemptively slow down before hitting the limit:
let remainingRequests = 6;
let resetTimestamp = 0;
function updateQuota(headers: Record<string, string>): void {
remainingRequests = parseInt(headers["x-ratelimit-remaining"] ?? "6", 10);
resetTimestamp = parseInt(headers["x-ratelimit-reset"] ?? "0", 10);
}
async function throttleIfNeeded(): Promise<void> {
if (remainingRequests <= 1) {
const now = Math.floor(Date.now() / 1000);
const waitSeconds = Math.max(0, resetTimestamp - now) + 0.5;
console.warn(
`Quota nearly exhausted (${remainingRequests} remaining). ` +
`Pausing ${waitSeconds}s until reset.`
);
await new Promise((resolve) =>
setTimeout(resolve, waitSeconds * 1000)
);
}
}
Output
Request queue enforcing 6 req/sec with 170ms minimum spacing between calls
Automatic retry with exponential backoff + jitter on 429 responses
Paginated fetching with throttling for bulk data retrieval
Proactive throttling when X-RateLimit-Remaining drops to 1
Error Handling
Header
Description
Action
X-RateLimit-Limit
Max requests per window (always 6)
Use as concurrency ceiling
X-RateLimit-Remaining
Requests left in current window
Pause proactively when <= 1
X-RateLimit-Reset
Unix timestamp of window reset
Sleep until this time on exhaustion
Retry-After
Seconds to wait (only on 429)
Always honor this value exactly
If you receive a 429 without Retry-After, default to 1 second then exponential backoff. Never retry more than 5 times — if consistently rate-limited, your architecture needs request consolidation, not more retries.