Documentation

Migrate from Firecrawl

Translate routes, formats, actions, cache units and result envelopes.

Swarmtab runs browsers on your Mac and exposes its own MCP and REST tools. It is not a Firecrawl-compatible endpoint. This guide describes Firecrawl v2 and Swarmtab nineteenth checkpoint, checked on 17 October 2026. See the official scrape API, crawl, map, and batch contracts before translating an application.

Connection and cleanup

Run examples from a Swarmtab source checkout with Node 22+. Set SWARMTAB_URL to the actual http://127.0.0.1:<port> and SWARMTAB_AGENT_TOKEN privately from the running app’s agent connect information. These examples use the checkout SDK, not a published npm package. Never use the window token in agent code.

Firecrawl Swarmtab
Hosted API bearer key Local agent bearer token
Independent account requests First POST /v1/<tool> creates an agent session
Job/account identity Preserve Swarmtab-Session-Id from the response on every later call
Finish a request Keep the session while polling; DELETE /v1/session ends it and stops unfinished work
Provider session/retention rules Defaults: 600-second disconnected grace, 21,600-second maximum life, configurable by the human; server restart invalidates REST sessions

The SDK keeps the header automatically. A raw REST caller must check both HTTP status and isError. A tool refusal can be HTTP 200. Missing a session header creates a new agent; it cannot poll the old agent’s jobs. After a lost action response, inspect the page before retrying.

import { Swarmtab } from './sdk/node/index.mjs';
const browser = new Swarmtab({
  baseUrl: process.env.SWARMTAB_URL,
  token: process.env.SWARMTAB_AGENT_TOKEN,
});
try {
  const page = await browser.site('scrape', {
    url: 'https://example.com', format: 'markdown', max_age: 0,
  });
  console.log(page.content.filter(c => c.type === 'text').map(c => c.text).join('\n'));
} finally {
  await browser.close();
}

Runnable equivalent: node examples/integrations/migrate.mjs firecrawl https://example.com.

Routes and jobs

Firecrawl v2 route Swarmtab route and JSON arguments Difference
POST /v2/scrape POST /v1/site, {action:"scrape",url,format:"markdown"} One format; no formats array
POST /v2/map /v1/site, {action:"map",url} Exact origin; strips query strings; no titles or relevance ranking
Map search Map query Case-insensitive URL substring filter, not semantic ranking
POST /v2/crawl /v1/site, {action:"crawl",url,max_pages:25,max_depth:2,sitemap:"skip"} Explicit defaults; same-site traversal may leave the starting path
POST /v2/batch/scrape /v1/site, {action:"batch",count:total,urls:firstChunk} then {action:"batch",job,urls:nextChunk} 1–100 URLs per chunk, 5,000 total, all chunks within 60 seconds; SDK batch(urls) handles submission
GET /v2/crawl/:id, batch status /v1/site, {action:"status",job:"j1"} Text status in MCP envelope; waitJob returns that envelope, not a document array
DELETE /v2/crawl/:id, batch cancel /v1/site, {action:"cancel",job:"j1"} Same owning session required; already dispatched actions cannot be undone
POST /v2/search /v1/site, {action:"search",query,count:5} Human configures Brave/Tavily key and budget; count 1–20, scrape at most 10; results use address, rank, snippet
POST /v2/extract Per-page read/site(scrape) with top-level schema No wildcard or combined cross-page extraction job

Map continuation is {action:"map",cursor:<returned cursor>} in the same session. Continue while the cursor is non-null, even if limited:false. Cursors last five minutes; a new map replaces that snapshot. Crawl maximums are 5,000 pages and depth 10; budgets count attempts, including duplicates. Jobs retain the last 20 job records. Original per-page results are retained separately from the 200-page scrape cache, bounded to 16 MiB per job and 50 MiB per agent. Retrieve them with site(status) and offset/limit, following next_offset. Saved-space batches publish owner-only batch.jsonl; private batches keep results in memory without an artifact.

Deterministic schema crawls run as background jobs up to 5,000 pages; schema batches accept 1–100 URLs in one submission without chunking. Poll status and page retained results; each row has JSON text containing the extraction envelope. Saved-space schema crawls keep resumable sealed checkpoints. Actual client-model sampling keeps its 16-page, 180-second collection limit.

Formats and extraction

Firecrawl format Swarmtab Boundary
markdown format:"markdown" read defaults to controls; site(scrape) defaults to Markdown
html format:"clean_html" Cleaned rendered HTML
rawHtml read({format:"html"}) after open Live DOM, not original response bytes
rawBase64 Unsupported No original-body Base64 mode
links, images Same single format names Output shapes differ
screenshot / option object Single format:"screenshot" or screenshot({full:true}) MCP image/artifact, not a hosted download URL
{type:"json",schema} Top-level schema, format:"json" Deterministic evidence extraction without a model by default
JSON prompt without schema Unsupported in ordinary REST Use caller-owned model with bounded page evidence
summary, question/answer Sampling-capable MCP client or caller model REST has no sampling channel
product, branding Same single format names Different shapes and narrower coverage; product identity mixing was reported at this base
changeTracking site(changes) / approved monitors No tagged scrape-history or arbitrary schema-diff format compatibility

For combined output, first open(url), then read({formats:"markdown,links,screenshot"}). The plural value is a comma-separated string, not an array. Allowed combined read modes are markdown,links,json,screenshot; json uses CSS fields. HTML is not combinable. structuredContent.data holds the combined payload. A screenshot reference such as swarmtab://agent/self/downloads/... is an agent-scoped resource, not an HTTP URL.

For a schema read, the extraction envelope is nested:

{"content":[],"structuredContent":{"untrusted":true,"data":{"path":"deterministic","complete":true,"data":{"title":"Fixture book","price":19.95},"missing":[],"sources":{}}}}

This is an illustrative shape; inspect the actual content and source metadata. Read the extracted value at result.structuredContent.data.data. Tool success can still mean complete:false. Check missing, sources, evidence truncation and paging. The example result reader in examples/integrations/migration-results.mjs requires completeness and a caller-supplied throwing validator. Existing SDK extractSchema(schema,{model}) / extract_schema(schema,model=...) callbacks run in the caller; callback output is explicitly validated:false and must be independently validated.

Actions and duration units

Firecrawl action Swarmtab scrape actions entry Difference
{type:"click",selector:"#more"} {click:"#more"} Add submit:true explicitly if it submits a form
write to focused field {type:{selector:"#name",text:"Visitor"}} Explicit unique selector; replaces the field value
{type:"press",key:"Enter"} {press:"Enter",submit:true} Submission permission is explicit
{type:"wait",milliseconds:500} {wait:500} 0–10,000 milliseconds per scrape wait
Wait for text {wait:{text:"Ready"}} Text wait, with ten-second bound
Selector-visible wait Unsupported Count assertions are not visibility waits
Directional scroll {scroll:600} / {scroll:-600} Signed pixels; no element/direction object translation
executeJavascript, intermediate screenshot/scrape/PDF Separate read(format:"script"), screenshot, files(pdf) calls No equivalent action inside one scrape sequence

Scrape sequences accept at most 20 actions, versus Firecrawl’s documented 50. For example:

await browser.site('scrape', {
  url, format: 'markdown',
  actions: [{ type: { selector: '#name', text: 'Visitor' } }, { click: '#more' }, { wait: 100 }],
});
Duration Units / translation
Firecrawl maxAge Milliseconds; convert to Swarmtab max_age seconds, rounding down to avoid older-than-requested cache
Swarmtab max_age omitted or 0 Fresh read; no two-day Firecrawl cache default
Swarmtab scrape action {wait:500} Milliseconds
Standalone wait({seconds:10,text:"Ready"}) Seconds
Node request timeout and waitJob({timeout,interval}) Milliseconds
Python request timeout and wait_job(timeout=...,interval=...) Seconds
Monitor interval Seconds; minimum 900; calendar strings use UTC, e.g. weekdays@09:00

Unsupported options to remove or redesign

There is no generic passthrough. Swarmtab rejects unknown fields. Do not copy onlyMainContent, includeTags, excludeTags, Firecrawl waitFor, timeout, browser headers, cookies, proxy, location, mobile, blockAds, removeBase64Images, skipTlsVerification, storeInCache, zeroDataRetention, parsers, screenshot option objects, extraction prompt, PII redaction, or prompt-injection classification into a Swarmtab scrape call. Some concerns have separate human-owned settings; they are not per-request aliases.

Crawl external-link scope, per-job concurrency, map query-string preservation, search news/images/categories/domain/date-range controls, goal judging, email/Slack alerts and cron/timezone schedules have no direct equivalent. Webhooks use terminal counts and X-Swarmtab-Signature, not Firecrawl’s events/page documents/signature. Do not reuse an existing receiver unchanged. No hosted fleet or automatic CAPTCHA solving is supplied. Models, search and proxy providers remain caller/human-owned services with separate costs.

All documentation