Skip to content

Rate limits ​

Limits are counted per key, per minute.

AllowancePer minuteWhat spends it
Reads600GET
Writes120POST, PATCH, PUT, DELETE

Ten reads a second is more than a catalogue sync or an hourly order pull needs. Two writes a second is the shape of a purchase-order feed rather than of a bulk load, and a bulk load belongs in an import rather than in a loop over a public operation.

They are two ceilings rather than one blended number because a read and a write cost different amounts, and a single number would let a caller spend a read budget on writes.

Per key, not per address ​

An ERP behind one NAT and a retailer's whole office look identical to a per-address limit. The unit here is the thing you minted, can see in a list, and can replace. A runaway integration throttles itself and nobody else, including your own users in the Tightly app, whose traffic is not counted here at all.

The headers ​

On every response the key reached:

X-RateLimit-Limit:     600
X-RateLimit-Remaining: 574
X-RateLimit-Reset:     1793826000

X-RateLimit-Reset is unix seconds. X-RateLimit-Limit is the allowance this request spent from, so a POST reports 120 and a GET reports 600 on the same key in the same minute.

A 401 key_invalid carries none of the three, and never will: the window is counted against a key's id, and a key that cannot be resolved has no id to count against. X-Request-Id is on that refusal like every other response.

On a 429, additionally:

Retry-After: 23

The headers a browser may read ​

A header that reaches a browser and a header JavaScript in that browser may read are two different things: everything else is hidden by the same-origin rules whatever the response carries. These are the ones this API names in Access-Control-Expose-Headers, so response.headers.get() answers:

HeaderOn
X-Request-IdEvery response.
X-Tightly-RegionEvery response. The cell that answered.
X-RateLimit-LimitEvery response the key reached.
X-RateLimit-RemainingEvery response the key reached.
X-RateLimit-ResetEvery response the key reached.
Retry-AfterA 429, a 503, and the 409 a request still in flight earns.

A server-side client reads every header regardless and needs none of this. It matters where the caller is a page: the Try it console on this site reads X-RateLimit-Remaining off your own response, and so can yours.

Handling a 429 ​

js
async function call(url, init, attempt = 0) {
  const response = await fetch(url, init)
  if (response.status !== 429 || attempt >= 4) return response

  // Retry-After is authoritative. Only fall back to a guess if it is missing.
  const wait = Number(response.headers.get('Retry-After') ?? 2 ** attempt)
  await new Promise(resolve => setTimeout(resolve, wait * 1000))
  return call(url, init, attempt + 1)
}

Two things a well-behaved client does:

  • Sleep on Retry-After. A 429 retried immediately is a 429 again. The other status that carries the header is 503, which is not about your allowance at all: it means the database is briefly out of reach. The same sleep is the right answer to both, but on a 503 only a read is safe to send again unchanged, unless the sentence says nothing was written, which is the one case where a write is too.
  • Watch X-RateLimit-Remaining on the calls that succeed and slow down before it reaches zero. A refused request still spends from the allowance, so a client that ignores the headers pays for its refusals.

Page sizes and other caps ​

Rate limits are not the only ceiling. See Pagination for limit, offset and the export cap.

Where an operation offers a bulk form, use it: POST /purchase-orders/bulk creates many orders in one call and spends one write, rather than a loop that spends one per order.

If the limits are wrong for you ​

They are published so they can be argued with. An integration whose honest shape needs more than 600 reads a minute is a conversation rather than a workaround: talk to us rather than sharding across several keys, which counts the same traffic in more buckets and loses you the one thing a key gives you, which is the ability to see and stop it.

Tightly API, version 2026-11.