Appearance
Rate limits
Limits are counted per key, per minute.
| Allowance | Per minute | What spends it |
|---|---|---|
| Reads | 600 | GET |
| Writes | 120 | POST, 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: 1793826000X-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: 23The 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:
| Header | On |
|---|---|
X-Request-Id | Every response. |
X-Tightly-Region | Every response. The cell that answered. |
X-RateLimit-Limit | Every response the key reached. |
X-RateLimit-Remaining | Every response the key reached. |
X-RateLimit-Reset | Every response the key reached. |
Retry-After | A 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 is503, 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 a503only 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-Remainingon 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.