Rate limits and quota
The API is free for the first year: there are no plan tiers, invoices or payments. Still, every keyed request is counted and limited against abuse. Limits are per product.
Products and limits
| Product | Covers | Minute | Day |
|---|---|---|---|
tiles |
Keyed tile requests, region pack | 1,200 | 200,000 |
query |
Every other /v1 call (reads, uploads, consent, sharing) |
60 | 10,000 |
render |
Flight videos (next release) | 5 | 100 |
These are the values of a standard key; the minute and day windows are fixed UTC windows (reset at the start of the minute and at UTC midnight). Each key also has a monthly quota across all products (UTC month), given to you with the key. Write to the administrator if you need higher limits.
The 429 response
When a limit is reached the API answers 429 with the usual error body (Errors):
{"code": "rate_limited", "detail": "rate limit of 60 per minute reached"}
code |
What ran out |
|---|---|
rate_limited |
The minute or day window |
quota_exceeded |
The monthly quota |
| Header | Meaning |
|---|---|
Retry-After |
Seconds until the window ends (at least 1) |
X-RateLimit-Limit |
Limit of the exhausted window |
X-RateLimit-Window |
minute, day or month |
Refused requests are not counted, nor are 401 and 403. Invalid requests (422) are counted.
What a client should do
- On 429 wait
Retry-Afterseconds; do not repeat the request at once. - Keep read responses with their
ETag: ask the same query withIf-None-Matchand you get 304 (no body, cheap) when nothing changed. - If you see 429 all the time, review your request pattern or ask the administrator for higher limits.
// A simple client that honours Retry-After (Node 24 or browser fetch).
export async function updraftGet(url, apiKey, { attempts = 3 } = {}) {
for (let attempt = 1; ; attempt += 1) {
const response = await fetch(url, { headers: { 'X-Api-Key': apiKey } });
if (response.status !== 429 && response.status < 500) return response;
if (attempt >= attempts) return response;
const wait = Number(response.headers.get('Retry-After') ?? 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
}
Limits on tiles
Keyed tile requests fall under the tiles limits above and are counted for you. Tile requests without a key are guarded by a coarse limit per client address (a plain 429 without a body) and are not counted. Details: Tiles and styles.