Developer docs menu

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:

ClassRequestsWhy
PricePOST /v1/priceCheap and frequent: a buyer moving a quantity slider.
UploadPOST /v1/uploads and its sub-routesExpensive: each upload becomes a geometry analysis.
CommitPOST /v1/quotes, /v1/orders, /v1/rfqs, and any …/acceptCreates something that lasts.
ReadEverything elseBootstrap, 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:

ClassPer IPPer keyPer shopPer session
Price240 / 1201200 / 6006000 / 3000120 / 60
Upload30 / 10200 / 601000 / 30020 / 6
Commit20 / 10200 / 601000 / 30010 / 5
Read600 / 3003000 / 150012000 / 6000300 / 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

HeaderSent onDescription
X-RateLimit-LimitEvery buyer API responseThe capacity of the smallest per-IP or per-key bucket that applied, or of the bucket that refused you.
X-RateLimit-RemainingEvery buyer API responseTokens left in that bucket. 0 on a refusal.
Retry-After429 onlyWhole 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_id can be priced again and again. Re-uploading the same file to change a material is the most common way to hit the Upload limit.
  • Do not poll the model too often. Analysis takes seconds: polling once a second is plenty, and GET /v1/models/{id}/events streams 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 429 carries.
  • JavaScript SDK: a client that already honours Retry-After.
  • LLMs and MCP: sharing a key’s budget between many agent users.