Rate limits
The per-minute limits on each group of Social Data API routes, how they are counted, and what to do on a 429.
Data lookups
Each client IP may make up to 60 requests a minute to each of these route groups:
| Route group | Limit |
|---|---|
GET /v1/comments | 60 requests per minute |
GET /v1/stats and POST /v1/stats/batch, together | 60 requests per minute |
GET /v1/profile | 60 requests per minute |
POST /v1/stats/batch, URLs | 60 URLs per minute, every URL in the body counting as one |
A batch counts twice: once as a request in the stats group, and once per URL against the batch URL limit. Batching saves round trips, but the URLs in a batch are not free of the limit. A batch that would go over is rejected whole and nothing is read, and its URLs still count until the window ends, so a smaller batch straight after is rejected too.
Limits are counted in fixed one-minute windows. They are per client IP, not per token: they are keyed on the address the request arrives from, so every caller behind a shared egress address, such as an office network or a NAT gateway, shares them, and tokens used from the same IP share them too. The per-group limits are checked before the token, so a request with a missing or wrong token counts against them as well. The batch URL limit is checked after the token, and every URL in the body counts before the body is validated: a batch of 26 URLs uses 26 of the minute's URLs and is then rejected with 422. Plan for the numbers in the table: at times more requests a minute may get through, but that is not something to build on.
| Other limit | Value | When you exceed it |
|---|---|---|
| URLs in one batch | 1 to 25 | 422 VALIDATION_ERROR |
| Batch duration | 100 seconds | Unfinished URLs return BATCH_TIMEOUT |
Need more?
Higher limits are available on request. Book a call (opens in a new tab) with the volume you expect, in requests per minute at peak. See High-volume integration for how to ramp up.
When you hit a limit
A request over a limit is answered with 429 RATE_LIMITED and a Retry-After header, in seconds, saying when the current window ends. Wait that long, then retry; retrying sooner is answered with another 429. A rejected request is not billed. The API sends no X-RateLimit-* headers, so the first sign of the limit is the 429 itself.
async function getWithRetry(url: string, attempts = 3): Promise<Response> {
for (let i = 0; i < attempts; i++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${API_TOKEN}` } });
if (res.status !== 429) return res;
const wait = Number(res.headers.get("Retry-After") ?? 60);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
}
throw new Error("Still rate limited");
}429 SCRAPING_RATE_LIMITED is different: reads of the platform are temporarily rate limited, whatever your own usage. It also carries Retry-After; see Errors.
To stay under the limits, spread requests evenly across the minute instead of sending them in bursts, and don't request the same post again sooner than its counts can change.
Engagement verification
Engagement verification routes have limits of their own:
| Limit | Value | When you exceed it |
|---|---|---|
| Post metrics refresh, one challenge | Once every 5 minutes | 429 RATE_LIMITED |
| Post metrics refresh, any challenge | 200 per minute per client IP | 429 RATE_LIMITED |
Post check (/admin/posts/thumbnail) | 10 per post per 5 minutes, and 100 per minute per token | 429 RATE_LIMITED |
| Likers refresh | 10 per minute per client IP, across every challenge | 429 RATE_LIMITED |
Likers refresh maxCount | 1 to 5000, default 1000 | — |
limit on a paginated list route | 100 | 400 |
| Webhook response time | Any 2xx within 10 seconds | The delivery is retried; see Webhooks |
| Comment history stored per challenge | 6,000 comments | The walk back through older history stops; newer comments keep being stored. See historyComplete on Comments and metrics on a challenge |
Last updated on