Rate limits
Last updated 28 October 2026 · API version v1
Rate limiting is a contract between your script and our database, and a contract you have to discover by breaking it is not one. Every figure on this page is the one the API enforces.
Every response tells you where you stand
Every response carries all three headers, including errors, but two: a browser’s CORS preflight, answered before any key is read, and a 500 when the rate limiter itself fails. You never have to discover your limit by hitting it.
| Header | What it carries |
|---|---|
X-RateLimit-Limit | The requests this budget allows in a minute, which is also the most it serves back to back: the key’s; or its network’s, for a request without an accepted key and for one refused before its key’s secret was checked |
X-RateLimit-Remaining | The requests left before the budget refuses one |
X-RateLimit-Reset | When the budget is whole again, as a Unix timestamp in seconds |
Including on errors. A 400, a 401 and a 429 carry the same three headers as a 200, so a client that reads them on every response never has to guess whether the last failure spent budget. The quickstart prints them for one request.
The budget for each edition
Each key has one budget, a number of requests a minute set by its organisation’s edition. Reads and writes draw on the same budget, and every key has its own: two keys in one organisation do not share one.
| Edition | Requests per minute | Concurrent scan triggers |
|---|---|---|
| Starter | 60 requests per minute | 1 |
| Standard | 120 requests per minute | 2 |
| Professional | 500 requests per minute | 7 |
| Portfolio | 300 requests per minute | 5 |
| Enterprise | Negotiated, and stated in the contract | |
A key can be given a lower limit than its edition’s, when it is created or edited, never a higher one. Until a contract’s figures are set, an Enterprise organisation’s keys have Portfolio’s. An agency’s key has its agency’s edition, for every client it reaches.
Requests without a key
A request without an accepted key spends the budget of the network it comes from: 60 a minute for each IPv4 address, or for each IPv6 /64. So a 401 carries the three headers as well, and keys cannot be guessed at the speed of the network:
bash: the headers on a request without a key
curl -s -D - -o /dev/null https://api.barrierops.com/api/v1/organizations \
| grep -iE '^(www-authenticate|x-ratelimit-)'Lines are wrapped for printing. The original is one line.
http: the challenge and the network’s figures
www-authenticate: Bearer realm="BarrierOps API"
x-ratelimit-limit: 60
x-ratelimit-remaining: 59
x-ratelimit-reset: 1791561601Lines are wrapped for printing. The original is one line.
Checking a key’s secret is the costly step of a request, so a request whose key the server has not checked in the last five minutes also spends one request of its network’s budget, before the check. While that budget is spent, such a request is refused with a 429 that carries the network’s figures rather than the key’s.
Scans a key can start at once
Starting a scan, POST /properties/{id}/scans, is refused with a 409 while the asset’s organisation already has as many unfinished scans as the key may start at once. That figure is the table’s, for the edition of the organisation that holds the key — so an agency’s key has its agency’s figure, for every client. Every unfinished scan in the asset’s organisation counts, whoever started it. The sentence in the response says how many. Start it again when a scan finishes.
What a 429 looks like
A 429 is an application/problem+json document like every other error, with a Retry-After header carrying the wait in whole seconds. To show one, this key was given a limit of one request a minute and has already made it:
bash: the headers on a request over the limit
curl -s -D - -o /dev/null https://api.barrierops.com/api/v1/properties \
-H "Authorization: Bearer $BARRIEROPS_API_KEY" | grep -iE '^(retry-after|x-ratelimit-)'Lines are wrapped for printing. The original is one line.
http: the wait and the spent budget
retry-after: 60
x-ratelimit-limit: 1
x-ratelimit-remaining: 0
x-ratelimit-reset: 1791561660Lines are wrapped for printing. The original is one line.
bash: the same request, for its body
curl https://api.barrierops.com/api/v1/properties \
-H "Authorization: Bearer $BARRIEROPS_API_KEY"Lines are wrapped for printing. The original is one line.
json: the complete response, a 429
{
"type": "https://barrierops.app/problems/rate-limited",
"title": "Too many requests",
"status": 429,
"detail": "Retry in 60 seconds.",
"instance": "00000000-0000-4000-8000-000000000000"
}Lines are wrapped for printing. The original is one line.
instance is the request id, which is different on every request; this page prints one fixed value in its place. The wait is computed from the budget rather than fixed, and it never grows without limit: a refused request does not spend anything, so sending it again cannot put you further behind than you already were.
Retrying
Retry on 429 and on 5xx with exponential backoff and jitter. Do not retry a 4xx other than 429 — the request will fail the same way.
Jitter matters more than it looks. Twenty-two client organisations on one nightly cron, all backing off on the same curve, arrive together on every retry — which turns one limited request into a queue that never drains.