Rate limits

Vectros applies three independent limits, each tuned for a different purpose:

  1. 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.
  2. 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.
  3. A batch-write collision budget — an abuse control on POST /v1/records/batch only. 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 (GET requests) do not — listing and fetching are not throttled by this limit. POST /v1/scripts/execute takes 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

PlanRequests per minute
Free60
Starter300
Pro600
Scale1,200
Enterprise1,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 externalId you already own is an idempotent match, not a collision: the existing record is returned to you with status updated, 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 results from your previous batch: every item with status conflict and error code already_exists is 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": "…"
}
HeaderMeaning
Retry-AfterSeconds to wait before retrying. After this many seconds the window resets and the request will succeed (assuming you are within budget again).
X-RateLimit-LimitYour plan's maximum requests per minute.
X-RateLimit-RemainingRequests 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 429 status, not the body. The status code is the contract; the errorCode and 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-After and X-RateLimit-* headers and a requestId you can quote to support.
  • Edge limit (per-IP, flood protection): a 429 from the firewall with the same errorCode: "RATE_LIMITED" discriminator, but no requestId (the edge has no per-request correlation id). If you contact support about an edge 429, quote the x-amz-cf-id response 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.