Rate limits
Vectros applies three independent limits, each tuned for a different purpose:
- A per-account business limit at the application layer — a fixed number of requests per minute, selected from your plan. This is the limit you pace against day to day, and the one most of this page is about.
- A per-IP ceiling at the network edge — a high, plan-independent flood-protection limit. A legitimate server-to-server backend never reaches it; it exists to cut off a single-source flood before it reaches application code.
- A batch-write collision budget — an abuse control on
POST /v1/records/batchonly. It is not a request-rate limit, and ordinary batch use never approaches it. See The batch-write collision budget below, which matters because the advice in this page's Handling 429s section — slow down and retry — will not clear it.
The first two are enforced at different layers and return slightly different responses (see Two layers, two responses below).
The per-account limit
- Window. A fixed one-minute window. Your request count resets at the top of each minute (it is not a rolling window).
- Scope. The limit is per account, shared across every key. All of your API keys and scoped tokens draw from the same per-minute budget — issuing more keys does not raise it.
- What counts. Writes, searches, and inference count against the limit. Reads
(
GETrequests) do not — listing and fetching are not throttled by this limit.POST /v1/scripts/executetakes the write-path limits once, before your script runs, whatever the script itself goes on to do — so a read-only script still spends write-path budget, and there is no free-read allowance on that call.
Limits by plan
| Plan | Requests per minute |
|---|---|
| Free | 60 |
| Starter | 300 |
| Pro | 600 |
| Scale | 1,200 |
| Enterprise | 1,200 (negotiable) |
An unrecognized or unset plan is treated as Free (60/min). Enterprise agreements can raise the ceiling beyond the Scale default by arrangement.
The batch-write collision budget
POST /v1/records/batch carries one additional limit, and it behaves differently enough from the
other two that retrying the same request will not clear it.
What it counts. Not requests, and not items — only items that collided with a value already
held by a different record: an externalId that belongs to a record outside what your credential
can read, or a value on a field your schema declares unique: true. It is counted per account, in
the same one-minute window as the per-account limit.
Ordinary batch use spends none of it. That is the design, not a courtesy:
- A clean import — records that do not yet exist — spends zero, however large the batch.
- Re-importing your own records spends zero too, even though every item "collides". Re-sending
a record with an
externalIdyou already own is an idempotent match, not a collision: the existing record is returned to you with statusupdated, which is a success. Blueprint re-applies and repeated syncs are therefore free.
So in practice you only spend this budget by submitting, in bulk, externalIds or unique values
that belong to records you cannot see — which is what the control is for.
If you hit it. The response is 429 with a Retry-After, like the other limits, but the
remedy is different:
- Do not simply back off and retry. The budget is spent on outcomes, not on request rate, so retrying the same batch after a pause spends it again and fails the same way.
- Fix the input instead. Inspect the per-item
resultsfrom your previous batch: every item with statusconflictand error codealready_existsis one that consumed budget. Remove or correct those items and resubmit. - If you are legitimately reconciling against records you cannot read, split the work across windows, or ask support about the right access model for it — needing to probe at volume usually means the credential is scoped more narrowly than the job requires.
Telling it apart from a request-rate 429. Both return 429 with Retry-After, so the status
alone does not distinguish them; the response message does. If you branch on status alone (which is
this page's general advice, and still correct for the other two limits), a collision-budget 429
will look like a burst limit and your backoff will not resolve it — so on POST /v1/records/batch
specifically, read the message before deciding that waiting is the answer.
When you exceed the limit
A request over the limit returns HTTP 429 with a JSON body and a set of headers that tell
you exactly how to recover:
{
"message": "Rate limit exceeded. Please try again later.",
"errorCode": "RATE_LIMITED",
"requestId": "…"
}
| Header | Meaning |
|---|---|
Retry-After | Seconds to wait before retrying. After this many seconds the window resets and the request will succeed (assuming you are within budget again). |
X-RateLimit-Limit | Your plan's maximum requests per minute. |
X-RateLimit-Remaining | Requests remaining in the current window — always 0 on a 429. |
Retry-After is the value to honor: it is the number of seconds until the current minute window
resets, so a client that sleeps for Retry-After seconds lands in a fresh window with a full
budget.
Handling 429s
- Honor
Retry-After. Sleep for the advertised number of seconds, then retry. This is the single most effective thing a client can do. - Back off exponentially if you retry without reading
Retry-After, and add jitter so a fleet of workers doesn't retry in lockstep. - Pace bulk work. A one-shot backfill (for example, ingesting a large corpus) is exactly the workload that hits this limit. Spread writes across minutes, or cap concurrency so your steady rate stays under your plan's per-minute budget.
- Branch on the
429status, not the body. The status code is the contract; theerrorCodeand headers are there to help you recover.
The limiter fails open: if the counter store is briefly unavailable, requests are allowed rather than rejected, so a limiter outage never blocks your traffic.
Two layers, two responses
Because the per-account limit and the per-IP edge ceiling live at different layers, a 429 can
come from either:
- Application limit (per-account, per-minute): the body above, with
Retry-AfterandX-RateLimit-*headers and arequestIdyou can quote to support. - Edge limit (per-IP, flood protection): a
429from the firewall with the sameerrorCode: "RATE_LIMITED"discriminator, but norequestId(the edge has no per-request correlation id). If you contact support about an edge429, quote thex-amz-cf-idresponse header instead.
In both cases the status is 429 and the body carries errorCode: "RATE_LIMITED", so a
single check on the status (and, if you want, the errorCode) handles either layer.
Notes & limits
- The per-minute window is fixed, not rolling: bursting right before a minute boundary and again right after briefly allows up to two windows' worth of requests in a short span. Pace to your average rate, not the instantaneous one.
- Reads are not counted by the per-account limit today. Plan your read-heavy workloads against the edge per-IP ceiling, not this limit.
- The limit is selected from your plan at request time; upgrading your plan raises it immediately on the next request.