Bulk jobs
POST
https://api.crawl4ai.com/scrape/jobsFor big lists — up to 10,000 URLs — or when your code cannot hold a connection open for slow pages. Submit once, get a job id, then poll for results while it drains in the background. Every URL is charged the job's flat price; the status's usage shows unit_cost and cost (credits), and URLs that never got an answer are refunded at the end (credited).
You can read a job from any region: api.crawl4ai.com finds it wherever it runs.
Request
# 1. submit -> job id JOB=$(curl -s https://api.crawl4ai.com/scrape/jobs \ -H "Authorization: Bearer sk_live_..." -H "Content-Type: application/json" \ -d '{"urls": ["https://a.com", "https://b.com"]}' | jq -r .job_id) # 2. poll until done until [ "$(curl -s https://api.crawl4ai.com/scrape/jobs/$JOB \ -H "Authorization: Bearer sk_live_..." | jq -r .status)" = "done" ]; \ do sleep 2; done # 3. fetch results (NDJSON, one line per URL) curl -s https://api.crawl4ai.com/scrape/jobs/$JOB/results \ -H "Authorization: Bearer sk_live_..."
import time, requests H = {"Authorization": "Bearer sk_live_..."} base = "https://api.crawl4ai.com" # 1. submit job = requests.post(f"{base}/scrape/jobs", headers=H, json={"urls": ["https://a.com", "https://b.com"]}).json()["job_id"] # 2. poll until done while requests.get(f"{base}/scrape/jobs/{job}", headers=H).json()["status"] != "done": time.sleep(2) # 3. page through the results after = 0 while True: lines = requests.get(f"{base}/scrape/jobs/{job}/results?after={after}", headers=H).text.splitlines() if not lines: break for line in lines: print(json.loads(line)["url"]) after += len(lines)
const H = { "Authorization": "Bearer sk_live_...", "Content-Type": "application/json" }; const base = "https://api.crawl4ai.com"; // 1. submit const { job_id } = await (await fetch(`${base}/scrape/jobs`, { method: "POST", headers: H, body: JSON.stringify({ urls: ["https://a.com", "https://b.com"] }), })).json(); // 2. poll until done let status; do { await new Promise(r => setTimeout(r, 2000)); ({ status } = await (await fetch(`${base}/scrape/jobs/${job_id}`, { headers: H })).json()); } while (status !== "done"); // 3. fetch results (NDJSON) const out = await (await fetch(`${base}/scrape/jobs/${job_id}/results`, { headers: H })).text(); console.log(out);
Endpoints
| Route | Does |
|---|---|
POST /scrape/jobs |
Submit urls (max 10,000) + any /scrape field. Returns a job_id. |
GET /scrape/jobs/{id} |
Status + counts (pending / done / error). Add ?full=1 for per-URL detail. |
GET /scrape/jobs/{id}/results |
Streamed results, paged with ?after=N (500 per page). |
POST /scrape/jobs/{id}/retry |
Re-run just the failed URLs. |
Try it with your key
Open the playgroundRun this call in the playground and see the real answer, the time and the units.
Next
Scrape many pagesPOST /scrape/batch
Scrape many URLs in one call — up to 50.
Scrape a pagePOST /scrapeFetch a page and return clean Markdown (and/or HTML).
Something unclear or wrong on this page? Tell us.