Dashly Docs

Rate limits and quota

How fast and how much you can call the Dashly API, the headers that tell you where you stand, and how to back off.

Two kinds of limits apply to every call:

  • Rate limits bound how fast you call. They are measured in seconds to minutes, and waiting a few seconds lifts them.
  • The monthly quota bounds how much work your agency asks for in a calendar month. Waiting until it resets lifts it.

Both answer 429 rate_limited with a Retry-After header (in seconds) and details.reason naming the limit you met.

Rate limits

LimitCeilingCounted perdetails.reason
Requests120 per minutekeyburst_limit
Writes60 per minutekeywrite_burst_limit
Rows read20,000 per rolling hourkeyvolume_budget
Reads from one address600 per minutecalling IP addressip_limit
Writes from one address240 per minutecalling IP addressip_limit
Unknown or revoked keys from one address20 per 10 minutescalling IP addressauth_failures
  • Requests count every call made with the key, reads and writes together, in fixed one-minute windows.
  • Writes (POST, PATCH, DELETE, and actions that change data) also count against their own ceiling.
  • Rows read is the number of records your reads returned over the last hour. A read is admitted while the key's hour has room, and is charged afterwards with the rows it actually returned. A full sync of a real agency fits; dumping every collection in one go does not.
  • Per address limits apply before the key is even checked, so several integrations behind the same server share them. Unknown keys counts calls with a missing, unknown, revoked or expired key from one address; past it, every call from that address is refused for a while, even with a valid key.

A refused request still counts against the window, so retrying immediately in a loop keeps you locked out.

Rate limit headers

Every successful response tells you where you stand in the per-key request window:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key (120).
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetSeconds until the window resets.

Key suspension

A key that keeps reading after volume_budget refused it, ignoring Retry-After (20 refusals within 15 minutes), is treated as a possible leak and suspended. Every call then gets 403 forbidden with details.reason: "key_suspended", and the agency's admins are notified.

An admin can reactivate the key from Connectors → API & webhooks. Reactivating does not reset the hourly budget: an integration still looping is throttled again. See Authentication.

Monthly quota

Each agency has a monthly allowance of tokens, shared by all its keys. It comes with the agency's plan and resets on the first day of each month at 00:00 UTC. The agency can also set a monthly cap per member: a key's usage counts against the cap of its owner.

What a call costs

A token is a unit of work, not a request:

CallCost in tokens
Read (GET)1 + 1 per record returned
Write (POST, PATCH, DELETE)6
Action that changes data11
Read-only actionPriced as a read

A single record (GET /v1/clients/{id}, GET /v1/me) counts as one record, so it costs 2. A list of 200 records costs 201: fetching 200 records in one page costs the same per record as fetching them in pages of 25, plus one token per page.

A call that fails after passing the limits (a 422, for example) is charged the minimum for its kind: 1 for a read, 6 for a write, 11 for an action. Calls refused by a limit or by the quota cost nothing.

Quota headers

Successful responses carry all five headers; a call refused by the quota carries them too:

HeaderMeaning
X-Quota-CostTokens this call cost. 0 on a 429. Also sent on a failed call that was charged.
X-Quota-UsedTokens used by the agency this month.
X-Quota-LimitThe monthly allowance, or unlimited.
X-Quota-RemainingTokens left, or unlimited.
X-Quota-ResetThe date the quota resets, YYYY-MM-DD (UTC).

GET /v1/me returns the same figures in its body. See Requests and responses.

Reads are admitted on their minimum cost, because their real cost is only known once the records are counted. The last calls of a month can therefore overshoot the allowance slightly.

When the quota is spent

{
  "error": {
    "code": "rate_limited",
    "message": "The API token quota is reached for this cycle.",
    "details": {
      "reason": "agency_quota_exhausted",
      "used": 50012,
      "limit": 50000,
      "resetsAt": "2026-11-01"
    },
    "request_id": "req_…"
  }
}

details.reason is agency_quota_exhausted when the agency's allowance is spent, or user_quota_exhausted when the key's owner has reached the personal cap set for them. Retry-After gives the seconds until the reset.

All 429 reasons

details.reasonWhat happenedWait
burst_limitMore than 120 requests in a minute with this keySeconds
write_burst_limitMore than 60 writes in a minute with this keySeconds
volume_budgetThe key read 20,000 records in the last hourUp to an hour
ip_limitToo many calls from your addressSeconds
auth_failuresToo many unknown or revoked keys from your addressUp to 10 minutes
agency_quota_exhaustedThe agency's monthly tokens are spentUntil the reset
user_quota_exhaustedThe key owner's monthly cap is reachedUntil the reset

The per-key and per-address refusals also carry details.limit and details.retryAfter.

How to back off

  1. Honour Retry-After. On any 429, wait at least that many seconds before retrying the same call. Never retry in a tight loop: refused calls still count, and ignoring volume_budget gets the key suspended.
  2. Watch the headers. Slow down when X-RateLimit-Remaining gets low, and schedule large syncs according to X-Quota-Remaining and X-Quota-Reset.
  3. Read in big pages. Use per_page=200 and filters rather than many small pages; it is cheaper in requests and in tokens.
  4. Spread large syncs over time. Keep a full read of the agency under 20,000 records per hour per key.
  5. Make writes safe to retry. Send an Idempotency-Key on POST so a retry after a timeout never creates a duplicate. See Idempotency.
  6. Retry server errors with exponential backoff and jitter. A 500 can be retried; a 4xx other than 429 will fail again until you change the request.
async function call(url: string, init: RequestInit, attempt = 0): Promise<Response> {
  const res = await fetch(url, init);
  if (res.status === 429 && attempt < 5) {
    const wait = Number(res.headers.get("Retry-After") ?? "1");
    await new Promise((r) => setTimeout(r, wait * 1000));
    return call(url, init, attempt + 1);
  }
  return res;
}

An agency_quota_exhausted or user_quota_exhausted refusal can ask you to wait days. Stop the job and resume after the reset rather than sleeping inside a request.

On this page