Understand the interfaces

Rate limits, quotas and fair use

The limit is per tenant, not per key — which means your BI job can throttle your storefront. What 429 means, how to back off, and when a loop should have been a bulk job.

Rate limits are the thing every integration meets eventually, usually at 02:00 on the night of the first full data load. Knowing the shape of them in advance changes how the integration is designed, which is cheaper than finding out afterwards.

The numbers

Value
Sustained rate100 requests per second
Burst headroom200 requests
Scope of the limitPer tenant — shared across every key and every user
Response when exceeded429 Too Many Requests with a Retry-After header
Running counters in responsesNone — treat the 429 as the signal

Burst headroom is a bucket, not a second allowance: a short spike above 100 per second is absorbed, a sustained one is not. Momentary peaks are fine. A tight loop is not.

The part that surprises people

The limit is per tenant, not per key. Every integration you run, plus your storefront, plus everyone clicking around the Cockpit, draws on the same allowance.

That has a consequence worth stating plainly: a badly written nightly job can degrade your shop. If the BI extract loops through 200,000 products at full speed at 20:00, the buyers still working at 20:00 are competing with it — and your storefront has no way to explain that to them.

Three habits follow:

  • Schedule heavy work into a window nobody uses, and write the windows down so two integrators do not both pick 02:00.
  • Cap concurrency in each integration. Four or eight parallel requests is usually plenty; thirty-two is a way of consuming the whole tenant allowance from one job.
  • Add jitter to schedules. Six jobs that all start exactly on the hour produce a spike that none of them individually caused.
No counters, by design. The gateway does not return running X-RateLimit-* headers, so an integration cannot pace itself by watching a remaining count. The contract is simpler: go at a sensible rate, and when you receive a 429, honour Retry-After and back off. Build for the second half of that sentence.

Designing around 429

A well-behaved client treats 429 as ordinary, not exceptional.

  1. Read Retry-After and wait at least that long. It is not advisory.
  2. Then back off exponentially, with jitter. 1s, 2s, 4s, 8s, 16s — each multiplied by a random factor between roughly 0.5 and 1.5. Without jitter, every parallel worker retries in lockstep and recreates the spike that caused the 429.
  3. Cap the backoff — a minute or two is plenty — and cap the number of attempts.
  4. Give up loudly. After the cap, fail the job with an alert. A job that retries forever is a job that is silently not doing its work.
  5. Never treat 429 as a failed write. The request was rejected before anything happened, so retrying it is always safe. That is not true of a timeout — see below.
A timeout is not a rejection.429 means nothing happened. A connection timeout means you do not know whether anything happened. Those need different handling, and the difference is exactly why writes must be idempotent — see Test an integration end to end.

When a loop should have been a bulk job

Most rate-limit incidents are a design problem wearing a throttling costume. The tell is a loop.

What the integration is doingRoughlyBetter
GET /v1/products page by page, 60,000 articles nightly1,200 requestsA bulk export — one job, one file
PUT one product at a time to update 40,000 prices40,000 requestsA bulk import of a price file
GET /v1/inventories/stock/{sku} per SKU to refresh stockOne per articleA stock file, or a live read only where a buyer is looking
Polling /v1/orders every 30 seconds for new orders2,880 requests a day, almost all emptyA webhook, plus an hourly reconciliation poll
Fetching each order's detail after listing themN+1 requestsOne list call with the fields you need

The bulk plane exists exactly for this. It handles files up to a million rows in CSV, JSON, XLSX and XML, runs as a job you poll for status rather than a request you wait on, and does not consume your per-second allowance one row at a time. See Bulk exports.

The rule of thumb: if the number of requests scales with the number of articles, it is the wrong interface.

Quotas on the other interfaces

Rate limiting is not the only ceiling. Three others are worth knowing before they surprise you.

Bulk jobs. A file is a job, not a request: you submit it, it is queued, and you poll for its state. Two practical limits — a single file is bounded in row count, and formats differ in how they are processed. CSV and XML stream row by row; JSON and XLSX are read into memory first, which makes them the wrong choice for the very largest files. Split a huge extract into several files rather than one enormous one.

Webhook delivery. Deliveries are retried with growing gaps when your endpoint fails, and a destination that keeps failing is eventually disabled automatically so that a dead endpoint does not accumulate an infinite backlog. The delivery log is kept for a limited window — long enough to investigate last week's problem, not long enough to be an archive. Read the log rather than relying on it as a store of record.

Event volume from your own actions. A bulk edit across 30,000 products can fire 30,000 events, each of which becomes a delivery to every subscribed endpoint. That is a load test of your integrator's server that nobody scheduled. Tell them before you run a mass edit, and subscribe narrowly — see Events and webhooks.

A sizing conversation worth having early

Before an integration is built, get four numbers from whoever is building it:

  1. Requests per run, at your real data volume — not the sandbox's.
  2. Concurrency — how many requests in flight at once.
  3. The window it runs in.
  4. What it does at 429, in one sentence.

If the answer to the first is in the tens of thousands, the design is wrong and it is much cheaper to say so now. If the answer to the fourth is "it retries", ask how many times and how long it waits.

Next