Data

Crawl a website without crawling the whole website

Map first, scope second, crawl third — plus the resume trick that means a truncated crawl does not cost you the pages you already paid for.

Mapping and crawling are priced differently on purpose: a map is a flat 2 credits whatever the site's size, and a crawl is 3 credits per page READ. That asymmetry is the whole technique — use the cheap call to decide what the expensive one is allowed to touch.

Step one: map the site

POST /api/v1/map takes any URL on the site and returns the URL inventory from sitemap.xml, robots.txt and a shallow link scan, with per-source counts and a truncation flag. It reads no page content, so it is flat-priced. A `search` substring filter narrows the result before it comes back.

Discover the URLs — flat fee, no page readsbash
curl -X POST https://twin-browser.com/api/v1/map \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "url": "https://docs.example.com",
        "limit": 2000,
        "search": "/guides/" }'

# { "urls": [ … ], "source_counts": { … }, "truncated": false,
#   "credits_charged": 2 }

Step two: scope by path, not by depth

maxDepth is a blunt instrument — it caps how far you walk but not where. Path globs and regular expressions are precise. `includePaths` and `excludePaths` take globs; `allowPatterns` and `denyPatterns` are regular expressions tested against the full URL with deny winning over allow; and `followSelector` confines link discovery to one CSS region, which is the difference between crawling a catalogue and crawling the site chrome.

  • includePaths: ["/guides/**"] — the glob allowlist.
  • denyPatterns: ["\\?page=\\d{3,}"] — a regex denylist beats an allow match.
  • followSelector: ".product-grid" — only discover links inside that region.
  • sameDomainOnly defaults true; respectNofollow defaults true.

Step three: submit the crawl

POST /api/v1/crawl returns 202 with a jobId. `maxPages` caps both the crawl and the credit reserve, so the worst case is bounded before anything starts — set it deliberately, because the default is 50. Per-page structured extraction is available inline with `fields` or `extractSchema`, which adds metered model cost on top of the per-page price.

A bounded, extracting crawl with a completion webhookbash
curl -X POST https://twin-browser.com/api/v1/crawl \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "startUrl": "https://docs.example.com/guides/",
    "includePaths": ["/guides/**"],
    "maxPages": 200,
    "maxDepth": 3,
    "concurrency": 3,
    "fields": ["title", "summary"],
    "callbackUrl": "https://your.app/hooks/crawl",
    "callbackSecret": "whsec_…"
  }'
# → 202 { "jobId": "job_…" }

curl "https://twin-browser.com/api/v1/jobs/job_…" \
  -H "Authorization: Bearer $TWIN_API_KEY"

Being a good citizen (and staying unblocked)

robots.txt Disallow, Crawl-delay and Request-rate are honoured by default. Autothrottle learns a per-domain delay from response latency and doubles it — or honours Retry-After — when the site answers 429 or 403, easing back as it recovers. Concurrency is capped at 5. These defaults are not just etiquette: a crawl that backs off finishes, and a crawl that hammers gets its egress blocked with nothing refunded for the pages already read.

Resuming a truncated crawl

This is the part that saves real money. When a crawl hits its page ceiling the response carries `nextUrls` and `seenUrls`. Submit a new crawl passing those back as `startUrls` and `seenUrls` and it continues exactly where the last one stopped — without re-reading, or re-paying for, the pages you already have.

Continue where it stoppedbash
curl -X POST https://twin-browser.com/api/v1/crawl \
  -H "Authorization: Bearer $TWIN_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "startUrl": "https://docs.example.com/guides/",
    "startUrls": [ /* the "nextUrls" from the previous response */ ],
    "seenUrls":  [ /* the "seenUrls"  from the previous response */ ],
    "maxPages": 200
  }'

Crawling behind a login

A crawl accepts the same account and session controls as a run: pass `account` to use a stored login for the host and `persistSession` so the session is reused across the crawl rather than re-established per page. If the site blocks automated sign-in outright, connect the account once with a handoff link first.

Common questions

Why map before crawling?
Because a map is flat-priced and a crawl is per page. 2 credits buys the whole URL inventory; scoping the crawl from it rather than guessing is usually the difference between reading 200 pages and reading 2,000.
Can a crawl extract structured data as it goes?
Yes — pass `fields` or `extractSchema` with an optional `prompt`, and each page is read into that shape while it is crawled. That adds metered model cost on top of the per-page price.
What happens to pages that answer 403 or 429?
They are re-queued after a backoff up to `maxBlockedRetries` times (default 1, maximum 3) and then recorded as blocked. Autothrottle also slows the whole crawl down in response, rather than continuing at the rate that provoked it.
Is there a synchronous crawl?
No. A crawl is always a job — it returns 202 with a jobId. Use map when you need a synchronous answer about a site.

Delegate the work. Keep the decision.

Hand off a real task, set the guardrails, and let repeated work compile into a skill that replays deterministically at near-zero cost.