Using the API

Rate limits

The data is openly licensed and read-only, so the limit exists to stop one client starving the others, not to meter access. There are no API keys and nothing is held back behind one.

What you get

Each client has a token bucket. You may spend up to 600 requests at once, and you earn them back at 10 a second. Spending the whole bucket and then waiting a minute puts you back where you started.

A burst allowance matters more than an average here: drawing a map view is a rush of requests followed by nothing at all. A map that fetches its tiles and a join table does not come close to the bucket.

Clients are counted by address. An IPv6 client is counted by its /64 prefix rather than a single address, because one machine is usually given the whole prefix.

Reading the headers

Every response carries your budget, not just a refusal, so you can slow down before you're turned away. These are the fields of the IETF HTTPAPI draft:

HeaderWhat it says
RateLimit-Policy"default";q=600;w=60 — the bucket holds 600 requests and takes 60 seconds to refill from empty.
RateLimit"default";r=487;t=12 — you have 487 requests left, and the bucket is full again in 12 seconds.
Retry-AfterSent only with a 429. The whole seconds to wait before the refused request would be affordable.

When you're refused

A request you cannot afford is answered with 429 Too Many Requests as a problem document, with Retry-After in seconds. Nothing is queued and nothing is charged for the refusal: waiting the stated time and sending it again is the whole recovery.

  • Retry-After is authoritative. Prefer it to a backoff schedule of your own.
  • A 429 is sent with Cache-Control: no-store, so it never lands in a cache.
  • The limit is applied by the server rather than the route, so any operation can answer with it.
async function get(url) {
  for (let attempt = 0; ; attempt++) {
    const response = await fetch(url);
    if (response.status !== 429) return response;

    // Retry-After is authoritative; only guess if it's missing.
    const wait = Number(response.headers.get("retry-after") ?? 2 ** attempt);
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
}

Staying under the limit

Most clients that hit the limit are re-fetching things that haven't changed, or fetching one row at a time. Both have a cheaper shape:

  • Send back the ETag you were given. A 304 Not Modified still costs a request, but it costs no bandwidth. See Caching.
  • Take a whole table rather than a row at a time: bulk downloads serve an entire partition or lookup in one request.
  • Use conditional requests for current Atlas data. An unchanged resource returns 304, so you do not re-download it.
  • Ask for a bigger page. limit accepts up to 500, so one request can replace ten.

Running the API yourself? The bucket is set by ATLAS_RATE_LIMIT_CAPACITY and ATLAS_RATE_LIMIT_REFILL_PER_SECOND, and a capacity of 0 turns the limit off.