Skip to Content
APIRate limits

Rate limits

Requests are limited per API key, not per workspace or per user. Two keys in the same workspace hold independent budgets, so a backfill running under one key cannot throttle a dashboard running under another. This is the main reason to create one key per integration.

LimitValue
Requests per minute, per key (reads)300
Requests per minute, per key (writes)60
WindowSliding, 60 seconds

The window slides rather than resetting on the minute, so there is no benefit to timing requests to a clock boundary.

Reading your budget

Every response carries your current standing, so you never have to guess or wait for a rejection to find out.

RateLimit-Limit: 300 RateLimit-Remaining: 287 RateLimit-Reset: 41
HeaderMeaning
RateLimit-LimitRequests allowed in the window
RateLimit-RemainingRequests left right now
RateLimit-ResetSeconds until the window frees up

A well-behaved client reads RateLimit-Remaining and slows down as it approaches zero, rather than sprinting into a 429.

When you exceed it

{ "error": { "code": "rate_limited", "message": "Too many requests. Retry after 12 seconds.", "request_id": "req_01J8Z3KB77" } }

The response is 429 and includes a Retry-After header in seconds. Wait at least that long. Retrying sooner consumes budget without succeeding and extends the period you are throttled.

Retrying correctly

Use exponential backoff with jitter. Without jitter, every worker that hit the limit at the same moment retries at the same moment and you throttle yourselves again.

async function request(url, key, attempt = 0) { const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } }) if (res.status === 429 && attempt < 5) { const retryAfter = Number(res.headers.get('Retry-After') ?? 1) const jitter = Math.random() * 0.3 * retryAfter await new Promise(r => setTimeout(r, (retryAfter + jitter) * 1000)) return request(url, key, attempt + 1) } return res }

Retry 429 and 5xx. Never retry 4xx other than 429: the request will fail identically every time, and the only thing a retry loop achieves is burning your budget. See error handling.

Staying inside the limit

Request larger pages. limit=100 fetches the same data in a fifth of the requests that limit=20 needs. See pagination.

Filter server-side. Narrowing by date range, topic, or engine in the query is free. Fetching everything and filtering in your own code costs requests you did not need to spend.

Do not poll faster than the data changes. Asky data refreshes on a pipeline cadence measured in hours. Polling every minute returns the same numbers roughly sixty times over. See caching and freshness.

Sync incrementally. Pull the window you need rather than re-fetching all history on every run.

Serialise your workers. Parallel workers sharing a key share its budget. If you fan out, cap concurrency in your own code.

Concurrency

There is no separate concurrency limit today: the per-minute budget is the only ceiling.

Keeping parallelism in the low single digits per key is still the better strategy. Fanning out wider spends the same budget faster and then stalls, so it is rarely quicker overall, and a limit may be added if it turns out to be needed.

If the limit does not fit

The limit suits scheduled syncs, dashboards, and normal automation comfortably. If you have a genuine need for more, such as a large historical backfill or a high-frequency internal dashboard, contact us and describe the pattern. A bulk export is often a better answer than a higher request ceiling.

These numbers can change

Limits may be adjusted as we learn what real integrations need. Read the RateLimit-* headers rather than hardcoding the numbers above, and they will stay correct. If the ceiling does not fit what you are building, contact us: raising it for a workspace is a configuration change, not a release.

Last updated on