Skip to main content

Rate Limits

To keep the platform fast and fair for every tenant, the Weave API limits how many requests each environment can send.

How limits work​

Each environment has its own allowance, shared by every credential that calls on its behalf: users, M2M clients and API keys. A single API key may use the whole allowance.

Limits are token buckets. Each one refills at a sustained rate per second, and its burst is the most requests it can hold for a short spike. An idle bucket fills back up to its burst.

Every endpoint belongs to one of six buckets. GET, HEAD and OPTIONS requests count as reads, and every other method counts as a write.

BucketWhat it coversSustainedBurst
Operational readDay-to-day reads: customers, program accounts, ledgers, coupons, badges, search100/s200
Operational writeDay-to-day writes on the same resources30/s60
Events writePOST /events100/s300
Events readEvent lists and traces30/s60
Management readConfiguration reads: programs, tiers, offers, data models, rules, templates, settings50/s100
Management writeConfiguration writes on the same resources5/s20

These are the production limits on a Base plan. Weave MCP tool calls count against the same buckets as the equivalent API calls, with read tools counting as reads and write tools counting as writes.

A few endpoints also keep a tighter limit of their own. For example, search has a per-user limit within operational read.

Limits for your plan​

Your plan scales every number in the table:

PlanMultiplier
Free, Test0.2×
Base1×
Enterprise L12×
Enterprise L23×
Enterprise L35×
Enterprise L410×
Enterprise L520×

Development and staging environments​

Development and staging environments get 25% of your plan's production limits. Management writes are the exception: they keep the production limit, so configuration imports and promotions run at full speed.

Whatever the plan and environment, no limit drops below 5 requests per second with a burst of 20.

For example, on Enterprise L2 a staging environment gets 75 operational reads per second (100 × 3 × 25%) and 15 management writes per second (5 × 3, with no discount).

If you need more than your plan allows, contact support@loomloyalty.com. Limits for an individual organization can be raised without a plan change.

429 Too Many Requests​

When a bucket is empty, the API responds with 429 Too Many Requests and these headers:

HeaderMeaning
Retry-AfterSeconds to wait before retrying. Always at least 1.
X-RateLimit-LimitThe bucket's capacity, which is its burst.
X-RateLimit-RemainingRequests left in the bucket.
X-RateLimit-ResetSeconds until the bucket is full again.

The body uses the standard error envelope. errors[0].description is RATE_LIMITED:

{
"success": false,
"errors": [
{
"code": 429,
"message": "Rate limit exceeded. Retry after the number of seconds in the Retry-After header.",
"description": "RATE_LIMITED"
}
]
}

503 Service Unavailable​

When the platform is briefly too busy to start your request, the API waits about a second and then responds with 503 Service Unavailable and a Retry-After header. errors[0].description is SERVICE_OVERLOADED. This response doesn't use up your rate limit, so retry it the same way as a 429.

Backing off​

Retry 429 and 503 responses after the Retry-After delay. When the header is missing, back off exponentially. In both cases, add random jitter so that many clients don't all retry at the same moment.

async function requestWithBackoff(url: string, options: RequestInit, maxRetries = 5) {
for (let attempt = 0; ; attempt++) {
const response = await fetch(url, options);
const retryable = response.status === 429 || response.status === 503;
if (!retryable || attempt === maxRetries) return response;

const retryAfter = Number(response.headers.get("Retry-After"));
const baseSeconds =
Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : Math.min(2 ** attempt, 30);
const jitterSeconds = Math.random() * baseSeconds * 0.5;
await new Promise((resolve) => setTimeout(resolve, (baseSeconds + jitterSeconds) * 1000));
}
}

Best practices​

  • Back off and retry on 429 and 503 instead of immediately hammering the endpoint again.
  • Watch X-RateLimit-Remaining on a 429, and slow a bulk job down before it runs dry again.
  • Cache responses that change infrequently rather than re-fetching them.
  • Page through data with the page and size parameters (see Pagination & Filtering) instead of requesting everything at once.
  • Spread out large bulk operations rather than firing them all at the same instant.