cdpfleet Docs Sign up free

Function jobs

For work longer than one call: batch scrapes a list of URLs, crawl follows a site's links, map lists a whole site's URLs, watch reports live changes on a page, warmup keeps a browser browsing like a person. A job starts with one request, runs on your threads in the background for as long as it needs, and you follow it, read its results as they arrive — as JSON lines or one zip archive — stop it and resume it. Jobs come with every paid plan.

Start a job

POST https://cdpfleet.com/v1/functions/batch, /crawl, /watch, /warmup, or /map with "job": true, with your API key, returns 202 {"job_id", "status_url", "concurrency"} at once. Jobs have no size or time limit of their own: a run that reaches your plan's session limit (24 hours) continues in a new run by itself, so a job goes on until it's done or you stop it — it uses your threads and minutes all along.

curl -s https://cdpfleet.com/v1/functions/crawl -H "x-api-key: $CDPFLEET_API_KEY" -H "content-type: application/json" \
  -d '{"url":"https://books.toscrape.com/","max_depth":2,"max_pages":200,"formats":["markdown"],"concurrency":4}'
# {"job_id":"7c1e…","status_url":"https://cdpfleet.com/v1/jobs/7c1e…","concurrency":4}

batch

FieldTypeDefaultWhat it does
urlsstring[]this or urls_urlPublic http/https URLs, scraped in any order — as many as fit in the request (1 MB, about 10,000).
urls_urlstringthis or urlsA public link to a text file with one URL per line, any length.

A batch from start to finish — start it, poll its status until it ends, then download every result as JSON lines:

# curl: the job id and status are read with grep
H="x-api-key: $CDPFLEET_API_KEY"
JOB=$(curl -s https://cdpfleet.com/v1/functions/batch -H "$H" -H "content-type: application/json" \
  -d '{"urls":["https://books.toscrape.com/catalogue/page-1.html","https://books.toscrape.com/catalogue/page-2.html"],"formats":["markdown"],"concurrency":2}' | grep -o '"job_id":"[^"]*"' | cut -d'"' -f4)
echo "job $JOB"
while sleep 5; do
  STATUS=$(curl -s https://cdpfleet.com/v1/jobs/$JOB -H "$H" | grep -o '"status":"[a-z]*"' | head -1 | cut -d'"' -f4)
  echo "$STATUS"
  case $STATUS in done|failed|stopped|interrupted) break ;; esac
done
curl -s https://cdpfleet.com/v1/jobs/$JOB/results.jsonl -H "$H" -o results.jsonl
wc -l results.jsonl

crawl

FieldTypeDefaultWhat it does
urlstringrequiredWhere the crawl starts.
max_depthnumber1How many links deep to follow from the start page; no upper limit.
max_pagesnumber100Stop after this many pages; no upper limit.
include / excludestring[]all / noneURL patterns as in map: * within a path segment, across segments, a leading / matches the path, e.g. ["/blog/"].
same_sitebooleantrueOnly follow links on the start URL's site.
delay_msnumber0Pause between pages, per thread.
robotsbooleantrueFollow the site's robots.txt.

map

FieldTypeDefaultWhat it does
urlstringrequiredThe site to map.
jobbooleanrequired: trueWithout it, map answers at once, up to 5,000 URLs.
max_urlsnumberno limitStop after this many URLs.
max_depthnumber11 = the sitemaps and the start page's links; more also opens the pages found, level by level, for their links (no content is kept).
include / exclude, same_site—as in crawlWhich URLs to keep and follow.

Each result is {"url", "source": "sitemap" | "links", "lastmod", "depth"}.

Both

FieldTypeDefaultWhat it does
formats, wait_for, actions, extract, max_wait—as in scrapeApplied to every page — see scrape.
country, proxy—random, the Community proxyAs for any function.
concurrencynumber2Pages at once: the threads the job holds while it runs, up to your plan's threads (asking for more uses all of them; the 202 says how many).
deliverobjectnone{"webhook": {"url": "https://…", "events": ["page", "done"]}, "email": true, "format": "archive"} — events takes page, done (default), event (watch) and archive; see webhooks; "format": "archive" also builds the zip archive at the end. Results are always stored.

Follow it

curl -N https://cdpfleet.com/v1/jobs/$JOB/events -H "x-api-key: $CDPFLEET_API_KEY"
# event: progress
# data: {"status":"running","progress":{"done":40,"failed":1,"queued":112}}

Results

One JSON object per page — url, status, depth (crawl), partial, error, and the formats you asked for — stored in parts of up to 500 pages as the job runs.

// Node.js 18+: every result of a finished job
let cursor = 0;
while (cursor !== null) {
  const r = await fetch(`https://cdpfleet.com/v1/jobs/${job}/results?cursor=${cursor}`, { headers: { 'x-api-key': process.env.CDPFLEET_API_KEY } });
  const { results, next_cursor } = await r.json();
  for (const page of results) console.log(page.url, page.status);
  cursor = next_cursor;
}

Archive (zip)

Instead of JSON lines, get everything as one zip: POST /v1/jobs/{id}/archive once the job has ended (or start the job with "deliver": {"format": "archive"}), then GET /v1/jobs/{id}/archive → {"status": "building" | "ready" | "failed", "bytes", "url"}. The url downloads straight from storage for an hour (ask again for a fresh one), and a job.archive webhook says when it's ready. Archives of any size work (zip64); they're kept as long as the job's results.

index.jsonl                          one line per page: url, status, depth, error, and the files below
pages/<host>/<path>/index.html       html
pages/<host>/<path>/index.md         markdown
pages/<host>/<path>/text.txt         text
pages/<host>/<path>/screenshot.png   screenshot, as an image
pages/<host>/<path>/links.json · requests.json · cookies.json · console.json · extract.json
errors.jsonl                         pages that failed
job.json                             the job, as GET /v1/jobs/{id} shows it

Paths keep the URL's segments with unsafe characters replaced; a query string or a very long path adds a short hash, so two URLs never share a folder. A map job's archive has urls.txt (one per line) and index.jsonl.

Watch: live changes on a page

POST https://cdpfleet.com/v1/functions/watch opens the page in a browser of yours and stays on it, reporting what changes while it's open: the XHR and fetch calls it makes (with their responses), WebSocket messages, and changes to a part of the page. Use it for prices, scores, order books, chat — anything that updates without a reload.

FieldTypeDefaultWhat it does
urlstringrequiredThe page to watch.
durationnumberuntil stoppedSeconds to watch.
filterobjecteverything{"url": ["*api*"], "selector": "#prices", "types": ["xhr", "fetch", "ws", "dom"]} — URL patterns for network events, the element whose changes count (debounced 0.5 s).
reload_everynumberneverReload the page every so many seconds.
browser, launch, device—ChromeAs in your own browser; "launch": {"live_view": true} lets you watch the tab (GET /v1/jobs/{id}/live). WebSocket messages need a Chromium-based browser.
deliverobjectnoneWebhooks get job.event with a batch of events, at most every 2 s.

Events arrive at once on GET /v1/jobs/{id}/events as event: event with {"events": [...]}, and are stored with the job's results: {"type": "xhr" | "fetch", "url", "method", "status", "response_body"}, {"type": "ws", "url", "direction", "data"}, {"type": "dom", "selector", "text", "html"}, each with at; bodies are cut at 64 KB.

curl -s https://cdpfleet.com/v1/functions/watch -H "x-api-key: $CDPFLEET_API_KEY" -H "content-type: application/json"   -d '{"url":"https://quotes.toscrape.com/scroll","duration":300,"filter":{"url":["*api/quotes*"],"types":["xhr"]}}'
curl -N https://cdpfleet.com/v1/jobs/$JOB/events -H "x-api-key: $CDPFLEET_API_KEY"

Warmup: a browser that browses like a person

POST https://cdpfleet.com/v1/functions/warmup keeps a browser of yours browsing a site the way a visitor would — reading, scrolling, moving the mouse, following links, now and then searching — on one sticky IP, for as long as you like. Its cookies and storage build up in a persistent profile, and you can scrape pages in the same tab whenever you need, with that history behind you.

FieldTypeDefaultWhat it does
urlstringrequiredWhere to start.
sitesstring[]noneMore start pages to wander between.
durationnumberuntil stoppedSeconds to run.
profilestringnoneYour persistent profile to keep it in (created if missing, saved when the warmup ends — stopped or at its duration); reuse it later in any session or function.
proxystringCommunity proxyBest a sticky residential token (cdpfleet-resi-…-session-name) or your own; the same exit is used throughout.
behaviorobjectall on{"scroll": true, "mouse": true, "typing": true, "dwell_s": [5, 40], "pages_per_site": [3, 12]}.
cookie_everynumber30Minutes between cookie deliveries (webhook job.cookies, results, and GET /v1/jobs/{id}/cookies).
browser, launch, device, deliver—CamoufoxAs for watch, except that the default browser is Camoufox.

Watch and warmup run in your own browser session: they hold its threads (2 for a headful browser) and are billed as a session, and continue past the 24-hour session limit by themselves.

Stop and resume

DELETE /v1/jobs/{id} stops a job after the pages in flight (stopping, then stopped); its results stay. POST /v1/jobs/{id}/resume continues a stopped or interrupted job where it left off — a crawl keeps its queue and the pages it has seen, so nothing is fetched twice. A job is interrupted when its run can't continue: your minutes or threads running out, or a restart on our side.

Billing

A running job holds concurrency threads and uses browser-minutes per second for each, like that many sessions; billed_seconds is the total in thread-seconds. Starting a job needs that many free threads (429 threads_exceeded otherwise). The Community proxy and your own proxies cost nothing extra; residential traffic is billed per GB.

Webhooks and email

With "deliver": {"webhook": {"url": "https://…"}} we POST JSON to your URL. events picks what you get (default ["done"]):

Each request carries x-cdpfleet-event, x-cdpfleet-delivery (a unique id; retries reuse it) and x-cdpfleet-signature: t=<unix time>,v1=<hex>. Answer with any 2xx; otherwise we retry after 10 s, 1 min, 5 min, 30 min and 2 h. The URL must be public https.

Your signing secret (whsec_…) is the same for all your jobs: GET /v1/me/webhook-secret, and POST /v1/me/webhook-secret/rotate to replace it. Check every request:

// Node.js: verify x-cdpfleet-signature on the raw request body
import crypto from 'node:crypto';
function verify(rawBody, header, secret, toleranceS = 300) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceS) return false;
  const want = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}

The same check in Python, Java, C# and Go, de-duplicating retries and rotating the secret: Webhooks.

"email": true sends a short note to your account's address when the job ends.

Errors

StatuserrorWhen
400invalid_option / invalid_urlA field out of range, or a URL that isn't public http(s); the message names it.
403jobs_not_availableJobs come with paid plans.
404no such jobNot one of your jobs.
409not_resumableOnly stopped and interrupted jobs resume.
410results_expired / job_expiredMore than 14 days after the end.
429threads_exceeded, daily_quota_exhaustedNot enough free threads or minutes to start; wait Retry-After.