Rate limits

Every account has a plan. A plan limits requests per second and per calendar month, counted per account: every token you create draws on the same budget. The pricing page compares the plans.

Plan Price Requests per month Requests per second Extras
Free $0 100,000 5
Developer $20 / month 1,000,000 25
Business $100 / month 10,000,000 100 99.9% uptime SLA, priority email support

Prices are in US dollars, plus applicable taxes. Commercial use is allowed on every plan. Change plans on your billing page.

What counts

Months are calendar months in UTC: the quota resets at 00:00 UTC on the 1st. Successful responses (2xx), redirects (3xx) and client errors (4xx) count. These don't: 403 (verify your email first), 429 (a limit turned the request away) and 5xx (our failure).

Paid plans get 10% of headroom: requests above the quota are still served until 110% of it, so a busy month doesn't break your integration while you upgrade. The Free plan stops at its quota. We email you when you reach 80% and 100% of it.

Monthly quotas aren't enforced yet. Until they are, no request is refused for the month, no quota emails go out, and the rate-limit headers carry only the per-second burst limit. We'll email every account before quotas start.

Traffic that degrades the service for others can be throttled or suspended under the fair use terms.

Rate-limit headers

Every response carries the IETF rate-limit fields next to the older X-RateLimit-* pair:

Header Example Meaning
RateLimit-Policy "burst";q=5;w=1, "monthly";q=100000 Your limits: q requests per w seconds, and monthly per calendar month.
RateLimit "burst";r=4;t=1, "monthly";r=98766;t=345600 What remains (r) and the seconds until each resets (t).
X-RateLimit-Limit 5 Requests allowed per second.
X-RateLimit-Remaining 4 Requests left this second.

Over a limit

Both limits answer 429 Too Many Requests with a Retry-After header in seconds.

Over the per-second limit, Retry-After is a second or so, and X-RateLimit-Reset gives the Unix time the window resets:

{
    "message": "Too Many Attempts."
}

Over the monthly quota, Retry-After is at most an hour (check back then), RateLimit's monthly t counts down to the 1st, and the message says what to do:

{
    "message": "Your Free plan's 100,000 requests for September 2026 are used up. Upgrade at https://www.timezone.io/billing for more, or wait until the quota resets on October 1, 2026 at 00:00 UTC."
}

Example — retry politely after a 429

A short Retry-After (the per-second limit) is worth waiting for; add a little jitter so concurrent clients don't stampede back at the same instant. A long one means the monthly quota: waiting won't help, so stop and surface the message, which says how to get more.

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);

        if (retryAfter > 60) {
            // The monthly quota: the message names your plan and how to get more.
            const { message } = await response.json();
            throw new Error(message);
        }

        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_version tells 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=us is one request; fetching 29 zones one by one is 29.
  • Spread scheduled jobs out rather than firing them on the same second.