Rate limits

Per-key limits, rate limit headers, 429 handling with backoff, and how to request more headroom

Rate limits keep one integration's burst from degrading anyone else's — including yours. Limits apply per API key, so your server traffic, your CI suite, and each client app draw from separate budgets, and they exist in both test and live mode so you hit them in development, not production.

The limits

SurfaceDefault limit
Secret-key requests (sk_…)100 requests/second per key
Publishable-key requests (pk_…)25 requests/second per key
Event ingestion (POST /v1/events)500 requests/second per org, pooled across keys
Unauthenticated surfaces (link redirects, code lookup)Per-IP limits, sized for real users rather than scripts

Short bursts above the sustained rate are tolerated; sustained excess returns 429. Publishable keys get tighter limits than secret keys because they ship inside apps you do not control.

Event ingestion is special-cased. POST /v1/events is the hottest path in the product, so it has its own, much larger pool that is not consumed by (and does not consume) your other API traffic. A batch import that saturates event ingestion will not starve your dashboard or reward calls.

Rate limit headers

Every API response reports where you stand:

HeaderMeaning
RateLimit-LimitThe request budget for the current window.
RateLimit-RemainingRequests left in the window.
RateLimit-ResetSeconds until the window resets.
Retry-AfterOn 429 only — seconds to wait before retrying.

Handling 429s

A 429 comes with the standard error envelope (type: "rate_limit_error", code: "rate_limit_exceeded") and a Retry-After header. Honor Retry-After, add jitter, and cap your attempts:

rate-limited-fetch.ts
async function invitebaseFetch(
  path: string,
  init: RequestInit,
  maxAttempts = 5,
): Promise<Response> {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://api.invitebase.com${path}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${process.env.INVITEBASE_SECRET_KEY}`,
        "Content-Type": "application/json",
        ...init.headers,
      },
    });

    if (res.status !== 429) return res;
    if (attempt >= maxAttempts) return res;

    const retryAfter = Number(res.headers.get("Retry-After")) || 1;
    const jitter = Math.random() * 500;
    await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
  }
}

For POST requests, send the same Idempotency-Key on every attempt so a retry never double-creates — see Errors — retrying safely.

Beyond retries:

  • Spread bulk work. Backfills and imports should run at a steady rate below the limit rather than as one burst; watch RateLimit-Remaining and slow down as it approaches zero.
  • Queue writes on your side. If you fan events out from your own infrastructure, put a queue with concurrency control in front of the API instead of firing per-request.
  • Don't poll — subscribe. If you are polling referral or reward state, switch to webhooks; the data comes to you and your read budget stays free.

The SDKs handle 429 backoff automatically; you only need this on direct API integrations.

Requesting higher limits

Limits are configurable per organization. If you are launching a campaign, migrating historical data, or consistently running near the default limits, contact support@invitebase.com with your expected request rates and which endpoints they hit — event-ingestion headroom in particular can be raised substantially. Enterprise plans include raised limits by default.

On this page