Rate limits
Requests are limited to 5 per second, counted per token. The limit is generous for typical use — batch or back off if you need to pull large amounts of data.
Rate-limit headers
Every response includes headers describing your current budget:
| Header | Description |
|---|---|
X-RateLimit-Limit |
The maximum number of requests allowed in the window. |
X-RateLimit-Remaining |
Requests remaining in the current window. |
When you exceed the limit, the API responds with 429 Too Many Requests and adds:
| Header | Description |
|---|---|
Retry-After |
Seconds to wait before retrying. |
X-RateLimit-Reset |
Unix timestamp when the window resets. |
{
"message": "Too Many Attempts."
}
Example — retry politely after a 429
Read Retry-After and pause before retrying; add a little jitter so concurrent clients
don't stampede back at the same instant:
async function getWithRetry(url, options, attempts = 3) {
for (let attempt = 1; attempt <= attempts; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) return response;
const retryAfter = Number(response.headers.get('Retry-After') ?? 1);
const jitter = Math.random() * 250;
await new Promise((resolve) =>
setTimeout(resolve, retryAfter * 1000 + jitter),
);
}
throw new Error('Rate limited after retries');
}
Avoid hitting the limit at all
- Cache responses.
meta.tzdb_versiontells you when the underlying data actually changed — cached reference data (zones, countries, abbreviations) stays valid until the next tzdb release. - Prefer one filtered list call over many lookups.
GET /v1/timezones?country=usis one request; fetching 29 zones one by one is 29. - Spread scheduled jobs out rather than firing them on the same second.