Concepts
Rate limits
Limits per class, daily and hourly caps, concurrency, RateLimit headers, 429 versus 503 and backoff.
Limits keep one runaway script from hurting you or anyone else. Every request must fit its key's budget and its organization's budget for its class of operation; a few operations also count against a daily or hourly cap. The numbers are the same on every plan.
Per minute, by class
Each operation belongs to one class (the reference names it). Budgets are per rolling minute and shared by every server, so spreading requests across connections does not raise them. The burst column is how many requests a key can send back to back before the per second pace applies.
| Class | Covers | Per key | Per organization | Burst per key |
|---|---|---|---|---|
read | GET lists and reads | 600 | 1,200 | 20 |
write | single resource POST, PATCH, PUT and DELETE, and the lead upsert | 600 | 1,200 | 20 |
heavy | imports, exports, bulk previews and confirmations, enrolling by filter, A/B analytics, spintax proposals, job result downloads | 20 | 30 | 3 |
send | operations that send a message to a lead | 60 | 120 | 5 |
discovery_quote | discovery quotes | 30 | 30 | 5 |
discovery_spend | starting discovery runs, resolving handles, promoting candidates | 10 | 20 | 3 |
Most requests cost one unit, whatever their filters or batch size: a lead list with search costs 1 and a batch upsert costs 1 per request. Analytics reads cost 5. The table at the end lists every operation's class and cost.
Daily and hourly caps
| What | Cap | Refusal |
|---|---|---|
| Leads created through the API | 100,000 per organization per day | LEADS_DAILY_LIMIT_429 |
| Job payload (imports and exports) | 100 MiB per organization per day | JOB_PAYLOAD_QUOTA_413 |
| Discovery quotes | 1,000 per organization per day | DISCOVERY_QUOTE_DAILY_LIMIT_429 |
| Messages the API sends into one conversation | 10 per hour | CONVERSATION_RATE_LIMITED_429 |
These caps count a rolling window: 24 hours, or one hour for messages into one conversation. They come on top of your plan's own limits (DM caps, discovery tokens, custom fields, import size), which the API applies exactly as the app does.
Concurrency
Besides the rate, a key and an organization may have only a few requests in flight at once on each server:
| Limit | Requests |
|---|---|
| Requests in flight per key | 2 |
| Requests in flight per organization | 2 |
heavy requests in flight | 2 |
send requests in flight | 2 |
A request over these answers 503 with Retry-After. Send requests in sequence or with low parallelism; there is nothing to gain from more.
Headers
Every response describes the most constrained budget that applied:
RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 41
RateLimit-Policy: "key-write";q=600;w=60RateLimit-Limit: the budget of that bucket.RateLimit-Remaining: what is left in it.RateLimit-Reset: seconds until its window ends.RateLimit-Policy: which bucket it is, its quotaqand its windowwin seconds.Retry-After: on every 429 and 503, the seconds to wait.
429 or 503
| Status | Means | Do |
|---|---|---|
429 API_RATE_LIMITED_429 | Your key or organization used its budget. | Slow down. Wait Retry-After, then continue at a lower pace. |
503 API_OVERLOADED_503 | Our capacity: the fleet is busy or your requests are too parallel. | Wait Retry-After with jitter and retry. Lower your concurrency. |
Backing off
Respect Retry-After first. When it is absent, back off exponentially with full jitter:
async function callWithRetry(request, attempts = 5) {
for (let i = 0; ; i++) {
const res = await request();
if (res.status !== 429 && res.status !== 503) return res;
if (i === attempts - 1) return res;
const retryAfter = Number(res.headers.get("retry-after"));
const base = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** i;
await new Promise((r) => setTimeout(r, base + Math.random() * base));
}
}Reuse the same Idempotency-Key on every retry of a write (see Idempotency).
Fair share
When the whole platform is busy, each active organization's per minute budget shrinks to an equal share of the remaining capacity, so a single heavy user cannot crowd out the others. A refusal for this reason is a 429 whose RateLimit-Policy names fair-share; it lifts as soon as the load drops.
The five original endpoints
GET /v1/me, GET /v1/campaigns, POST /v1/leads, GET /v1/leads/lookup and POST /v1/campaign-leads, the endpoints the Clay integration has always used, keep the limits they have always had. A request to them is never refused for concurrency or for platform load, and none of the class budgets above applies. A row-by-row Clay table that worked before keeps working.
| Limit | Pace | Burst |
|---|---|---|
| Per key | 10 per second | 20 |
| Per workspace | 20 per second | 20 |
Over these, the answer is the same 429 these endpoints have always sent, with Retry-After.
Requests without a valid key
| Limit | Value |
|---|---|
| Requests without a valid key, per IP address | 120 per minute |
| Failed authentications per IP address before a block | 20 per 10 minutes, then blocked for 15 minutes |
These limits apply only to traffic that does not authenticate. A request with a valid key is never throttled or blocked because of its IP address, so shared egress addresses (Clay, Zapier, Make, n8n, hosted agents) keep working whatever other tenants on them do.