Rate limits
How the buyer API rate-limits requests: four classes, four scopes, token buckets, the headers you get back and exactly what to do when refused.
This page explains how the buyer API limits request rates, how to read the limits, and how a client should back off when it is refused. The buyer API is public, so it is rate-limited properly. Limits are token buckets: a bucket holds a number of tokens and refills steadily, so a burst is allowed and a sustained flood is not.
Classes
Every request falls into one class, chosen by what it costs:
| Class | Requests | Why |
|---|---|---|
Price | POST /v1/price | Cheap and frequent: a buyer moving a quantity slider. |
Upload | POST /v1/uploads and its sub-routes | Expensive: each upload becomes a geometry analysis. |
Commit | POST /v1/quotes, /v1/orders, /v1/rfqs, and any …/accept | Creates something that lasts. |
Read | Everything else | Bootstrap, model polling, the hosted pages, and the upload relay’s PUT requests (the upload was already counted). |
Scopes and budgets
Each class is counted four times over: against the client IP, the widget or API key, the shop, and the buyer session. All four must allow a request.
Live-mode budgets, as capacity / refill per minute:
| Class | Per IP | Per key | Per shop | Per session |
|---|---|---|---|---|
Price | 240 / 120 | 1200 / 600 | 6000 / 3000 | 120 / 60 |
Upload | 30 / 10 | 200 / 60 | 1000 / 300 | 20 / 6 |
Commit | 20 / 10 | 200 / 60 | 1000 / 300 | 10 / 5 |
Read | 600 / 300 | 3000 / 1500 | 12000 / 6000 | 300 / 150 |
Read the first row as: a single IP may send 240 price calls at once, and then one every half-second indefinitely. That is far above what a person at a keyboard produces, and far below what a scraper wants.
Test mode gets a quarter of these budgets. A client that is obviously automated gets a tenth.
Response headers
| Header | Sent on | Description |
|---|---|---|
X-RateLimit-Limit | Every buyer API response | The capacity of the smallest per-IP or per-key bucket that applied, or of the bucket that refused you. |
X-RateLimit-Remaining | Every buyer API response | Tokens left in that bucket. 0 on a refusal. |
Retry-After | 429 only | Whole seconds until a token is available. |
Being refused
A refusal is a 429 response with an RFC 9457 problem document whose code is rate_limited, plus the headers
above. Wait the number of seconds in Retry-After, then try again:
async function withBackoff(request, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
const response = await request();
if (response.status !== 429) return response;
const wait = Number(response.headers.get('retry-after') ?? 5);
await new Promise((done) => setTimeout(done, (wait + 1) * 1000));
}
throw new Error('still rate limited after backing off');
}
Staying under the limits
- Debounce re-pricing. The widget waits for the buyer to stop typing before it calls
/v1/price. - Upload once, price many. A
model_idcan be priced again and again. Re-uploading the same file to change a material is the most common way to hit theUploadlimit. - Do not poll the model too often. Analysis takes seconds: polling once a second is plenty, and
GET /v1/models/{id}/eventsstreams progress if you would rather be told. - Never put a secret key in a browser. Every visitor would share its budget, and anyone could read it.
Server-to-server integrations
If you integrate from your own back end with a secret key and the published budgets do not fit (a nightly bulk re-price, a catalogue import), ask us. Limits are set per plan and can be raised deliberately.
Next steps
- Errors: the problem document a
429carries. - JavaScript SDK: a client that already honours
Retry-After. - LLMs and MCP: sharing a key’s budget between many agent users.