Size a launch

The limits a launch runs into, in one place — the per-tenant request budget, batch and page caps, site timeouts, App build waits, and 429 handling — and how to design around each before you hit it.

This guide is for the developer or solution architect who is about to size a project: a storefront, some integrations and a few scripts, all calling one tenant (one merchant's store on the platform). By the end you have a request budget for that tenant, a list of the loops that must become batch calls, a compute size and timeout for each site, a backoff that keeps a 429 from becoming an outage, and a decision on warmup for each App.

The limits are documented across several pages, and they behave differently: some refuse a request, some quietly cut it down. The table below puts them side by side. The five sections after it are the design work, in the order you need it.

The short answer: treat 100 requests per second per tenant as the whole budget for every consumer together (as of 2026-09), replace per-item loops with batch calls, page lists to the last page, and give every client a limiter and a capped backoff.

The limits in one place

LimitValue (as of 2026-09)What happens past it
API requests per tenantBy default 100 per second sustained, bursts up to 200. Shared by every key and user of the tenant429 with Retry-After. See Rate limits
Page size on a listUp to 200 items per page and 50 by default on the lists checked. Limits are defined per endpoint, so check the Explorer for yours. See PaginationA larger limit is clamped to 200, not refused
Items in a batch callUp to 200 for prices/resolve and for the stock availability routeRefused with 400. Split the call into chunks of 200 or fewer
Products in POST /v1/products/labelsUp to 500 ids and, separately, up to 500 skusAnything past 500 in either list is ignored, not refused
Bulk dataFiles through /v1/io, not entity routes. Price entries also have a bulk routeSee Import and export and Price lists
Request time of a server-rendered siteA per-site setting, up to 30 secondsThe request fails when it runs out
How long the CLI waits for an App build600 seconds by default (--timeout). This is a setting of the CLI, not a platform limitThe CLI stops waiting. See Deploying
Site environment variableKey up to 255 characters, value up to 8192The variable is over the limit and cannot be saved as it is

What is not published (as of 2026-09): storage quotas, invocation quotas and pricing. The docs carry no numbers for them, so this guide does not either. If your design depends on one, ask through support.revenexx.com before you build on a guess.

1. Build a request budget per tenant

The 100 requests per second belong to the tenant, not to a key. Ten keys share the same bucket as one. Creating a separate key per job is good practice for revoking access, but it does not split the budget.

Everything that calls the tenant draws from it: the storefront's server, every integration, and every script someone runs by hand. Write the budget down before launch, one line per consumer. The numbers below are an illustration, not a recommendation:

ConsumerHow you count itRequests per second
Storefront serverPages rendered per second × API calls per page. 15 × 460
Order sync to the ERPOne call per order, paced by the worker. About 10 orders per second at the daily peak10
Catalog importDeliberately slow: the job holds itself to 5 per second5
Headroom for scripts and retriesWhatever is left after the rows above20
TotalStay below the sustained 100. The burst of 200 absorbs a spike; it is not extra capacity to plan on95

Two habits keep the budget honest:

  • Count the storefront by its fan-out, not its traffic. A product page that makes four API calls costs four times what a visitor count suggests. Cut the fan-out first: one market context call replaces five reads, and one price call covers a whole listing (next section).
  • Give bulk work its own pace. A backfill that runs flat out at 100 per second leaves the storefront to compete with it for the same bucket. Run it at a fixed share, or outside trading hours.

A 429 is a signal to slow down, not a setting you change in Cockpit. If a launch's budget does not fit in 100 per second even after batching and caching, tell support before the launch so the plan can be looked at with you, not after.

The edge has its own, separate protection for the domains you point at it: a client hammering one URL is throttled there. That is per client and is not your tenant budget. See WAF. api.revenexx.com is answered by the origin and is not behind it.

2. Swap loops for batch and bulk routes

Every loop that makes one call per item spends the budget one request at a time. Find them by asking, for each page and job: does it call the API inside a for or a map?

The loop you haveWhat replaces it
A price per product on a listing or cartOne POST /v1/prices/resolve with up to 200 items, answers in request order
Stock per itemThe stock availability route takes up to 200 items in one call. See Stock
A name for each product ID or SKUOne POST /v1/products/labels for up to 500 products
Reading a whole collectionPages of 200, not 50. Ask for limit=200 and page on
Writing thousands of products or pricesA file through /v1/io, or for price entries the bulk route, not thousands of entity calls

Three things catch people out:

  • The caps are not all the same kind. A limit above 200 on a list is clamped: ?limit=100000 returns 200 rows and a 200, not an error. So a loop that thinks it fetched everything in one call did not. Always page until the last page.
  • labels drops the overflow without saying so. Send 800 SKUs and you get names for 500, with no error. The cap is 500 per list, ids and skus separately. Chunk to 500 yourself.
  • An import still makes some calls. The file bytes go straight to object storage and never through the API, but minting the upload URL, registering the job and polling it are API calls. Poll every few seconds, not in a tight loop.

After a bulk catalog change, category rules and the completeness measurement do not update themselves. Recompute them as described on Import and export.

3. Pick compute and timeouts

Compute is a per-site setting. A site has one server specification that covers both the build and the running site, and there is no plan tier that unlocks a bigger one. Read the sizes from GET /v1/sites/specifications rather than from a table you copied. See Compute specifications.

Decide per site:

SiteSizeTimeout
Server-rendered storefrontNot the smallest size. It does real work on every requestAs short as your slowest page needs, never above the maximum
Static siteBarely matters. Nothing executes per requestNot relevant
A theme with a large block libraryRaise the specification and Node's heap (--max-old-space-size) together. A bigger specification with the default heap fails the same wayNot relevant to the build

Rules for sizing:

  • Do not raise the timeout to fix a slow page. The request time is capped, and a slow page is almost always a slow upstream call on the render path. Render the shell on the server and fetch the buyer-specific parts, such as price and stock, after paint. See Build configuration.
  • Read the request duration in the logs before you resize. See Logs.
  • Give an App build room. revenexx deploy app waits 600 seconds by default. If a build is slow, raise --timeout in your pipeline. The flag only changes how long the CLI waits. See Deploy with the CLI.
  • Keep environment variables small. A key is limited to 255 characters and a value to 8192. A certificate bundle or a large JSON document does not belong in one; store a reference and fetch the content at start.

4. Add 429 backoff, and stay under the limit in the first place

The SDKs do not retry (as of 2026-09). A 429 reaches your code as an error. The CLI does retry, but only methods that are safe to replay (GET, HEAD, PUT, DELETE, OPTIONS), never POST or PATCH. So every client you write needs two things.

A limiter, so you rarely see a 429 at all. Give each consumer its share from section 1 and enforce it in the client. This one limits how fast calls start. It does not cap how many run at once, so pair it with a concurrency limit if your worker fans out:

Limiter
// At most `perSecond` calls start in any one second.
function makeLimiter(perSecond) {
  const interval = 1000 / perSecond
  let next = Date.now()
  return async function limited(fn) {
    const now = Date.now()
    const wait = Math.max(0, next - now)
    next = Math.max(now, next) + interval
    if (wait) await new Promise(r => setTimeout(r, wait))
    return fn()
  }
}

const limited = makeLimiter(5) // the catalog import's share

// Inside an async function or an ES module:
await limited(() => callTheApi()) // your own function that makes one API call

A retry, for the 429 you still get. The response carries Retry-After, which is a fixed 1 (as of 2026-09). Honour it and add jitter so that many clients released by the same limit do not retry in the same instant. The backoff loop is on Rate limits; use it as it is.

Rules for both:

  • A 429 from the gateway means the request was not processed, because the gateway refuses it before any service sees it. So replaying it is safe, for any method. The CLI is more cautious and does not retry POST or PATCH. That is different from a timeout or a 502 on a write, which may have been applied. There is no idempotency header, so make writes repeatable with a natural key or check first. See Retries & idempotency.
  • Cap the retries and surface the error after the last one. An unbounded retry loop turns a spike into a sustained overload of your own making.
  • Do not retry in a tight loop across workers. Ten workers that each back off on their own are ten workers hitting the limit again together. Share one limiter per process, and pace the process.

5. Decide on warmup per App

An App runs on demand, so the first request after a quiet period pays a cold start. warmup keeps the App warm on the platform's schedule. It is off by default and it is set per App in the manifest:

manifest.json
"warmup": true

Decide by asking who waits:

The Appwarmup
A capability a storefront or checkout awaits: prices, stock, cartOn
An admin-only App that an operator opens a few times a dayOff
A scheduled or event-driven job that nobody waits forOff

It is not free, which is why it is off by default. Switch it on where a buyer notices the delay, and measure that it helped before you spread it across every App. If the slow part of a request is your own code, warmup will not fix it. See Operating an App.

Before you launch

  • The request budget is written down, one line per consumer, and the total is under 100 per second with headroom.
  • No page or job calls the API inside a loop for something a batch route answers.
  • Every list is paged to the last page, and labels calls are chunked to 500.
  • Each site has a size you chose, and a slow page is fixed on the render path, not with a longer timeout.
  • Every client of the API has a limiter and a capped backoff with jitter.
  • Environment variables fit the 255 and 8192 character limits, and the pipeline's --timeout allows for a slow App build.
  • warmup is on for the Apps a buyer waits for and off elsewhere, and you measured that it helped.

Where to go next

Was this page helpful?