Skip to content

Rate limits

API quotas apply per organization: every key of an organization draws from the same budget. There are three request limits and two stream limits, set by your plan.

PlanRequests / minuteRequests / dayStream connectionsSubscriptions / connectionFirehose topicsWebhooks
CoreNo API access
Pro6050,000150NoNo
Elite300500,0005500YesYes
Enterprise1,2005,000,000202,000YesYes

Concurrent requests are limited to 10 on Pro, 25 on Elite and 100 on Enterprise. Enterprise contracts can set custom quotas; your current values are always in the headers below and in GET /v1/usage.

Every response to a key-authenticated request carries two headers, following the IETF RateLimit header fields draft:

RateLimit-Policy: "minute";q=60;w=60, "day";q=50000;w=86400
RateLimit: "minute";r=59;t=1
  • RateLimit-Policy lists your quotas: q requests per window of w seconds.
  • RateLimit describes the policy closest to running out: r requests remain, and the window is fully replenished in t seconds.

The minute and day limits refill continuously (a generic cell rate algorithm), not at fixed clock boundaries: you may use the whole quota in a burst, and capacity returns at the quota’s rate afterwards.

samples/curl/rate-limits.sh
#!/usr/bin/env bash
# The quota headers of a request: RateLimit-Policy (your quotas) and RateLimit
# (requests left in the most constrained window and seconds until it resets).
set -euo pipefail
API="${STATUS302_API_URL:-https://api.status302.com}"
curl --fail-with-body -sS -o /dev/null -D - "$API/v1/events?limit=1" \
-H "Authorization: Bearer $STATUS302_API_KEY" \
| grep -i -E '^(ratelimit|ratelimit-policy|retry-after):'

When a limit is exhausted, the API answers 429 with a Retry-After header (whole seconds) and a problem naming the limit:

HTTP/1.1 429 Too Many Requests
Retry-After: 17
RateLimit-Policy: "minute";q=60;w=60, "day";q=50000;w=86400
RateLimit: "minute";r=0;t=60
Content-Type: application/problem+json
{
"type": "https://status302.com/problems/rate-limited",
"title": "Rate limited",
"status": 429,
"detail": "requests per minute quota of your plan exhausted; retry after the Retry-After seconds",
"limit": "minute"
}

limit is minute, day or concurrent for your key’s quotas, or stream_tickets (below). (stream_connections is reserved in the schema; REST responses don’t use it.) A concurrent refusal carries Retry-After: 1 and no RateLimit headers, because it isn’t a windowed quota. Signed-in app sessions don’t have these quotas; they are throttled per user and per IP instead (limit user or ip).

Wait for Retry-After seconds before retrying, and retry a bounded number of times. Refused requests don’t count against your quota, but they are counted as throttled in your usage.

samples/typescript/rate-limits.ts
// Reading the quota headers, and handling 429 without the SDK.
import { setTimeout as sleep } from "node:timers/promises";
import { Status302 } from "@status302/sdk";
const api = process.env.STATUS302_API_URL ?? "https://api.status302.com";
const headers = { Authorization: `Bearer ${process.env.STATUS302_API_KEY}` };
// Plain fetch: retry a 429 after Retry-After seconds, a bounded number of times.
async function getJSON(url: string, attempts = 5): Promise<unknown> {
for (let attempt = 1; ; attempt++) {
const res = await fetch(url, { headers });
console.log(`RateLimit-Policy: ${res.headers.get("ratelimit-policy")}`);
console.log(`RateLimit: ${res.headers.get("ratelimit")}`);
if (res.status === 429 && attempt < attempts) {
const wait = Number(res.headers.get("retry-after") ?? "1");
console.log(`rate limited, retrying in ${wait}s`);
await sleep(wait * 1000);
continue;
}
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
}
}
await getJSON(`${api}/v1/events?limit=1`);
// The SDK retries 429s itself and parses the headers on every response.
const status302 = new Status302({ apiKey: process.env.STATUS302_API_KEY, baseUrl: api });
const res = await status302.request("get", "/v1/events", { query: { limit: 1 } });
if (res.rateLimit) {
const { policy, remaining, reset, policies } = res.rateLimit;
console.log(`${remaining} requests left in the ${policy} window, resets in ${reset}s`);
for (const p of policies) console.log(`quota ${p.name}: ${p.quota} per ${p.window}s`);
}

The SDKs retry 429 responses automatically for every method, waiting out Retry-After up to 60 seconds (configurable), and raise a RateLimitError when the wait would be longer or retries are exhausted.

  • Use the stream for live data. Polling an event’s metrics every second costs 60 requests a minute; subscribing to event.{id}.kpis costs none. See the stream guide.
  • Use exports for bulk reads. An export of the whole catalog is one request; paging it is hundreds.
  • Use webhooks for events you react to. Alert firings, on-sales and status changes arrive without polling. See webhooks.
  • Watch RateLimit. Slow down when r gets low instead of running into 429.
  • Check your usage. GET /v1/usage returns your quotas, current consumption and history by endpoint group and key; the same data is in Settings → Developers → Usage.

Each open stream connection holds one of your organization’s connection slots for as long as it is open. Opening one more closes it with code 4429; close idle connections or multiplex topics over one connection. Each connection can hold up to your plan’s subscriptions per connection; one more answers an in-band subscription_limit error and the connection stays open. Clients may send at most 20 messages per second (bursts of 50). See stream limits.