Rate Limits & Quotas
Understand AlterLab's rate limits, concurrency caps, and payload restrictions to optimize your integration.
Rate Limits
Rate limits are applied per account and scale with your account balance. All API keys on the same account share one limit, so adding keys does not raise it:
| Tier | Balance | Requests/Minute | Burst After Idle |
|---|---|---|---|
| Free | $0 - $5 | 30 | 30 |
| Basic | $5 - $50 | 60 | 60 |
| Growth | $50 - $200 | 100 | 100 |
| Scale | $200 - $500 | 300 | 300 |
| High Volume | $500+ | 500 | 500 |
| Enterprise | Custom | up to 10,000 | 10,000 |
Bursts
Retry-After.Enterprise Access
is_enterprise flag enabled on your account.POST Rate Limits
Scrapes whose target request uses POST, PUT, PATCH or DELETE (the method you ask AlterLab to send to the target site) are metered by a separate per-account bucket at half your tier's rate. GET scrapes are not charged against it, so submitting a GET scrape with POST /v1/scrape counts against the GET limit. In a batch, each item counts against the bucket for its own target method. SERP and batch search queries are metered by the POST bucket, one token per query.
| Tier | GET Target (RPM) | POST/PUT/PATCH/DELETE Target (RPM) |
|---|---|---|
| Free | 30 | 15 |
| Basic | 60 | 30 |
| Growth | 100 | 50 |
| Scale | 300 | 150 |
| High Volume | 500 | 250 |
| Enterprise | 10,000 | 5,000 |
Polling Has Its Own Allowance
Auth Rate Limits
Authentication and account management endpoints are protected by stricter per-IP limits to prevent abuse, independent of your API key tier:
| Endpoint | Limit | Window |
|---|---|---|
POST /auth/register | 3 requests | 1 hour |
POST /auth/login | 5 requests | 15 minutes |
POST /auth/forgot-password | 3 requests | 1 hour |
POST /scrape/demo | 10 requests | 1 hour |
Per-IP Enforcement
429 with a Retry-After header indicating when the window resets.Concurrency Limits
Maximum number of requests that can be processed simultaneously. Add funds to instantly unlock higher limits:
| Account Balance | Tier | Concurrent Requests | Requests/Minute |
|---|---|---|---|
| $0 - $5 | Free | 3 | 30 |
| $5 - $50 | Basic | 10 | 60 |
| $50 - $200 | Growth | 15 | 100 |
| $200 - $500 | Scale | 25 | 300 |
| $500+ | High Volume | 50 | 500 |
| Enterprise | Enterprise | up to 500 | up to 10,000 |
Instant Upgrades
Payload Limits
| Limit Type | Value | Notes |
|---|---|---|
| Request Body Size | 1 MB | Maximum size of JSON request |
| Response Size | 50 MB | Maximum scraped content size |
| URL Length | 8,192 chars | Maximum URL length |
| Headers Size | 32 KB | Total custom headers size |
| Extraction Schema | 64 KB | Maximum JSON schema size |
| Extraction Prompt | 4,000 chars | Maximum prompt length |
Timeout Limits
| Operation | Default | Maximum |
|---|---|---|
| Sync Request | 85 seconds | 85 seconds |
| Sync Polling (internal) | 85 seconds | 85 seconds |
| Async Job | 300 seconds | 600 seconds |
| Wait for Selector | 30 seconds | 60 seconds |
| PDF/OCR Processing | 120 seconds | 300 seconds |
Long-Running Jobs
sync: false to get a job ID and poll for results.Batch Limits
| Limit | Value |
|---|---|
| URLs per batch request | 1,000 |
| Concurrent batches | 5 |
| Batch result retention | 24 hours |
Scrape Dispatch Limits
Scrape, crawl, batch, scheduled, and workflow dispatch all use the same balance tier as request-rate limiting. Adding funds updates the tier immediately; no subscription is required. Dispatch concurrency matches the account concurrency entitlement, and new jobs are admitted at your tier's requests-per-minute rate.
| Account Tier | Jobs/Minute | Max In-Flight |
|---|---|---|
| Free | 30 | 3 |
| Basic | 60 | 10 |
| Growth | 100 | 15 |
| Scale | 300 | 25 |
| High Volume | 500 | 50 |
| Enterprise | 10,000 | 500 |
One canonical account policy
429 Response When Over a Limit
Every rate limit (request rate, concurrency, crawl slots, extraction, dispatch entitlement) returns an HTTP 429 with the same structured body describing exactly which limit was hit. Limit-specific fields (such as in_flight) are added inside detail:
{
"error": "rate_limit_exceeded",
"message": "You have reached your account tier's concurrent job limit.",
"detail": {
"scope": "global_concurrent",
"domain": null,
"limit": 15,
"in_flight": 15,
"requested": 1,
"accepted": 0,
"account_tier": "growth",
"retry_after_seconds": 5
},
"docs_url": "https://alterlab.io/docs/reference/limits"
}Every dispatch response also carries headroom headers so you can track your entitlement without waiting for a 429:
X-Dispatch-Entitlement-Limit: 15
X-Dispatch-Entitlement-Remaining: 4
X-Dispatch-Entitlement-Reset: 5
Retry-After: 5 # Only on 429 responsesBatch requests (POST /v1/batch) accept and reject items individually: a batch that partially exceeds entitlement still returns HTTP 202 with the accepted URLs dispatched and the rejected ones listed with a per-item reason — you are never charged for a rejected item.
Rate Limit Headers
Requests checked against a per-account request bucket (scrape, batch, crawl and other job submissions) return your remaining headroom on success. Every 429 carries the headers of the limit that was hit:
RateLimit-Limit: 60
RateLimit-Remaining: 45
RateLimit-Reset: 12
X-RateLimit-Limit: 60 # 429 responses
X-RateLimit-Window: 60 # Only on 429 responses
X-RateLimit-Scope: account # Only on 429 responses
Retry-After: 12 # Only on 429 responses
X-Concurrency-Limit: 10
X-Concurrency-Remaining: 8 # Successful scrapes
X-Concurrency-Current: 10 # Concurrency 429sRateLimit-LimitMaximum requests per minute (X-RateLimit-Limit is kept for compatibility). Sent only for request-rate limits; spend, concurrency, crawl-slot and dispatch limits report their value in the error body (detail.limit) and X-Limit-Type, X-Concurrency-* or X-Dispatch-Entitlement-* headers insteadRateLimit-RemainingRequests you can send right now before being limited (request-rate limits only)RateLimit-ResetOn a 429, seconds until a retry can succeed (equals Retry-After). On a successful response, seconds until the request quota is fully replenished. Spend-limit resets are in detail.reset_atRetry-AfterWhole seconds to wait before retrying, computed from the real reset time (429 only)X-RateLimit-ScopeWhich limit was hit, for example account, org or ip (429 only)Handling Rate Limits
1. Check Headers
Monitor RateLimit-Remaining and slow down before hitting the limit.
2. Implement Exponential Backoff
On 429 errors, wait and retry with increasing delays: 1s, 2s, 4s, 8s...
import time
def scrape_with_retry(url, max_retries=5):
for attempt in range(max_retries):
response = requests.post(...)
if response.status_code == 429:
# Prefer the server's exact wait; fall back to exponential backoff
wait = int(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
return response
raise Exception("Max retries exceeded")3. Use Batching
A batch replaces many HTTP round trips with one call. Each item still counts against your per-minute limit, so size batches to fit your tier's rate.
4. Queue Requests
Use a job queue (Redis, RabbitMQ) to throttle requests and handle retries gracefully.
Increasing Limits
Add Funds
Your limits automatically increase as your account balance grows. See the tier tables above.
Enterprise Plans
For custom limits, dedicated infrastructure, or SLA guarantees, contact our sales team.
Need Higher Limits?