Appearance
Rate Limits
Updated Feb 2026Firecrawl enforces rate limits to ensure fair usage and availability of the API for all users. Rate limits are measured in requests per minute and vary by plan and endpoint. When configured correctly, your real bottleneck will be concurrent browsers, not rate limits.
Billing Model
Firecrawl uses subscription-based monthly plans. There is no pure pay-as-you-go model, but the auto-recharge feature provides flexible scaling -- once you subscribe to a plan, you can automatically purchase additional credits when you dip below a threshold, with better rates on larger packs. For testing before committing, start with the Free or Hobby tier.
Plan downgrades are scheduled to take effect at the next renewal, and unused-time credits are not issued.
See Billing & Plans for full details on credits and pricing.
Concurrent Browser Limits
Concurrent browsers represent how many web pages Firecrawl can process for you at the same time. Your plan determines how many jobs can run simultaneously -- if you exceed this limit, additional jobs wait in a queue until resources become available.
Current Plans
| Plan | Concurrent Browsers |
|---|---|
| Free | 2 |
| Hobby | 5 |
| Standard | 50 |
| Growth | 100 |
| Scale / Enterprise | 150+ |
Extract Plans (Legacy)
| Plan | Concurrent Browsers |
|---|---|
| Free | 2 |
| Starter | 50 |
| Explorer | 100 |
| Pro | 200 |
Need Higher Concurrency?
If you require higher concurrency limits, contact help@firecrawl.com to discuss custom plans.
API Rate Limits
Rate limits are measured in requests per minute and are primarily in place to prevent abuse.
Current Plans
| Plan | /scrape | /map | /crawl | /search | /agent | /crawl/status | /agent/status |
|---|---|---|---|---|---|---|---|
| Free | 10 | 10 | 1 | 5 | 10 | 1,500 | 500 |
| Hobby | 100 | 100 | 15 | 50 | 100 | 1,500 | 25,000 |
| Standard | 500 | 500 | 50 | 250 | 500 | 1,500 | 25,000 |
| Growth | 5,000 | 5,000 | 250 | 2,500 | 1,000 | 1,500 | 25,000 |
| Scale | 7,500 | 7,500 | 750 | 7,500 | 1,000 | 25,000 | 25,000 |
Extract Endpoints
The extract endpoints (/v2/extract) share rate limits with the corresponding /agent rate limits shown above.
Batch Scrape Endpoints
The batch scrape endpoints (/v2/batch/scrape) share rate limits with the corresponding /crawl rate limits shown above.
FIRE-1 Agent
Requests involving the FIRE-1 agent have separate rate limits that are counted independently:
| Endpoint | Rate Limit (requests/min) |
|---|---|
/scrape | 10 |
/extract | 10 |
Extract Plans (Legacy)
| Plan | /extract (requests/min) | /extract/status (requests/min) |
|---|---|---|
| Starter | 100 | 25,000 |
| Explorer | 500 | 25,000 |
| Pro | 1,000 | 25,000 |
Handling 429 Responses
When you exceed a rate limit, the API returns an HTTP 429 (Too Many Requests) response. Your application should handle this gracefully.
Response Format
json
{
"success": false,
"error": "Rate limit exceeded. Please retry after some time."
}Response Headers
Firecrawl includes rate limit information in response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed per minute for this endpoint |
X-RateLimit-Remaining | Number of requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the rate limit window resets |
Retry-After | Seconds to wait before retrying (on 429 responses) |
Retry Strategies
Exponential Backoff (Recommended)
The best approach for handling rate limits is exponential backoff with jitter:
javascript
async function firecrawlRequest(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
const baseDelay = retryAfter
? parseInt(retryAfter) * 1000
: Math.pow(2, attempt) * 1000;
// Add jitter to prevent thundering herd
const jitter = Math.random() * 1000;
const delay = baseDelay + jitter;
console.log(`Rate limited. Retrying in ${delay}ms (attempt ${attempt + 1})`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Python with Exponential Backoff
python
import time
import random
import requests
def firecrawl_request(url, headers, json_data, max_retries=5):
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=json_data)
if response.status_code == 429:
retry_after = response.headers.get('Retry-After')
if retry_after:
delay = int(retry_after)
else:
delay = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limited. Retrying in {delay:.1f}s (attempt {attempt + 1})")
time.sleep(delay)
continue
return response
raise Exception("Max retries exceeded")Best Practices
| Practice | Description |
|---|---|
| Use exponential backoff | Double the wait time between retries: 1s, 2s, 4s, 8s, 16s... |
| Add jitter | Add random delay to prevent multiple clients from retrying simultaneously |
Respect Retry-After | When present, use this header value instead of your own backoff calculation |
| Use webhooks | For crawl and batch scrape, use webhooks instead of polling /crawl/status to avoid hitting status endpoint limits |
| Batch requests | Use /v2/batch/scrape instead of making many individual /v2/scrape calls |
| Cache results | Use the maxAge parameter to leverage Firecrawl's built-in caching and avoid re-scraping recently fetched pages |
Rate Limit Quick Reference
For fast lookups during development:
| If you are on... | You can call /scrape... | And /crawl... |
|---|---|---|
| Free | 10 times/min | 1 time/min |
| Hobby | 100 times/min | 15 times/min |
| Standard | 500 times/min | 50 times/min |
| Growth | 5,000 times/min | 250 times/min |
| Scale | 7,500 times/min | 750 times/min |
Related Pages
- Billing & Plans -- Credit costs, plan details, and auto-recharge
- Webhooks Overview -- Avoid polling with real-time notifications
- Scrape -- Scrape endpoint documentation