aov-post-purchase-upsell
🔌 Store API
Platform
Rate limits

Rate limits

60 requests per minute, per store, on a sliding one-minute window.

Headers

Every reply carries the current state:

HeaderMeaning
X-RateLimit-Limit60
X-RateLimit-RemainingCalls left in the current window
Retry-AfterOn 429 only: seconds to wait

Reading X-RateLimit-Remaining as you go is cheaper than being refused and backing off.

When you go over

{ "success": false, "data": null, "error": "Rate limit exceeded" }

Status 429. Wait for Retry-After and retry — every endpoint is a GET, so a retry is always safe.

if (response.status === 429) {
  const wait = Number(response.headers.get('Retry-After') || 60);
  await new Promise(r => setTimeout(r, wait * 1000));
}

The limit is per store, not per token

The bucket you are metered against is keyed by the store the token resolves to. Rotating a token does not give you a fresh allowance, and an integration serving many merchants gets 60/minute for each of them independently.

There is a second bucket in front of it, and it matters in exactly one case: calls are throttled before the token is checked, keyed by the token presented — or by your IP when there is no token at all. So a client hammering with a token that is wrong or revoked is refused with 429 rather than 401, and fixing the credential is the way out, not waiting.

The MCP connection has its own 60-per-minute allowance. A Claude session and a nightly export job do not compete for the same budget.

What it is, and is not

This is a first-line guard against runaway callers, not a metered quota. It is enforced per server instance, so under load the effective ceiling can be higher than 60. Do not design around exceeding it — build to 60/minute and you will never meet it.

There is no monthly quota, no per-endpoint limit, and no billing attached to request volume.

Staying under it

Page instead of hammering. /data/offers returns 20 per page. Walking 200 offers is 10 calls, not 200.

Ask for the roll-up, not the parts. /data/analytics/summary is one call for shop-wide figures. Fetching each placement from /data/analytics/stats and adding them is both more calls and — because the columns do not share a denominator — the wrong answer.

Cache what does not move. /data/analytics/lifetime is all-time; polling it every minute tells you nothing new. Yesterday's closed date range will not change either.

Spread scheduled jobs. A nightly export that fires 60 calls in one second is refused halfway; the same 60 spread over a minute is not.

Related

Product
Install AppWebsiteAvada Apps
Resources
DocumentationFAQPrivacy Policy
Company
Avada GroupContact
© 2026 Avada Group. All rights reserved.