# Firecrawl bundle (browser shard)

GEDX402 sells **Firecrawl v2** on x402 at `browser.gedx402.com` — scrape, map, crawl, search, batch, extract, agent, and more at `/v1/firecrawl/*`. Agents pay USDC per route; operator supplies `FIRECRAWL_API_KEY`.

**AM winner (default funnel):** `GET https://gedx402.com/v1/models?bundle=winners`

| Winner | Route | Wire |
| --- | --- | --- |
| Scrape | `POST /v1/firecrawl/scrape` | `pricing:report` |

**Discovery:** `GET https://browser.gedx402.com/v1/models?bundle=browser` — Firecrawl routes only.

| Step | Route | Method | Customer wire |
| --- | --- | --- | --- |
| **Scrape Lite (hero)** | `/v1/firecrawl/scrape` | `GET\|POST` | `pricing:report` |
| **Batch scrape** | `/v1/firecrawl/batch/scrape` | `POST` | scales per URL |
| **Site map** | `/v1/firecrawl/map` | `POST` | per Firecrawl tier |
| **Extract** | `/v1/firecrawl/extract` | `POST` | per Firecrawl tier |
| **Search** | `/v1/firecrawl/search` | `POST` | per Firecrawl tier |
| **Crawl** | `/v1/firecrawl/crawl` | `POST` | per Firecrawl tier |
| **Browser interact** | `/v1/firecrawl/interact` → `…/interact/:sessionId/execute` | `POST` | per step (session `ttl` at most 120 s) |
| **Firecrawl agent** | `/v1/firecrawl/agent` | `POST` | per agent job |

**Starter route:** `POST https://browser.gedx402.com/v1/firecrawl/scrape` — Scrape Lite hero (`starter:true` in agents.json).

## Scrape Lite

```bash
curl -X POST https://browser.gedx402.com/v1/firecrawl/scrape \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'
```

Same handler as `POST /v1/firecrawl/scrape` — also accepts `GET ?url=https://example.com`.

Response shape: `{ success, markdown, metadata }` (Firecrawl parity).

## Hosts refused before payment

Scrape (`POST` or `GET /v1/firecrawl/scrape`), batch scrape, crawl, and extract refuse these URLs before the x402 gate. The response is `400` `{ "error": "over_route_bound", "url": "<blocked url>" }`. The call is not quoted a price, and you are not charged. The deprecated aliases `POST /v1/browser/outcome/scrape` and `POST /v1/browser/outcome/batch-scrape` use the same check.

- `linkedin.com` (any subdomain, such as `www` or `uk`) and `lnkd.in`. Message: `linkedin.com URLs can bill 5-40 Firecrawl credits each through enrichment providers and are not offered on this route. You were not charged.`
- `x.com` and `twitter.com`, including `www` and `mobile`. Message: `x.com / twitter.com URLs cost 30 Firecrawl credits each and are not offered on this route. You were not charged.`

Look-alike hosts (`notlinkedin.com`, `linkedin.com.evil.test`) are allowed through this check.

`POST /v1/firecrawl/search` refuses a body that targets LinkedIn before payment. Paid and unpaid requests both get `400` `{ "error": "over_route_bound", "blocked_host": "linkedin.com" }`. The call is not quoted, and you are not charged. Targeting is any of these:

- a `site:` operator for `linkedin.com` (any subdomain, a `*.` wildcard, an optional `http` or `https` scheme) or `lnkd.in`, in any case, including inside parentheses
- a LinkedIn host in `includeDomains` (bare host, `*.host`, or a URL)
- an `http` or `https` LinkedIn URL anywhere inside `scrapeOptions`

Message: `search cannot target linkedin.com (its pages can bill 5-40 Firecrawl credits each through enrichment providers). You were not charged.`

A query that only mentions LinkedIn, `excludeDomains` that lists LinkedIn, and a negated `-site:linkedin.com` are allowed. Those still quote `402` when unpaid. When `scrapeOptions.formats` is a non-empty array and `includeDomains` is not set, the worker adds `linkedin.com` to `excludeDomains` before the search runs. `includeDomains` and `excludeDomains` are mutually exclusive, so a search that pins `includeDomains` does not get that exclusion (a LinkedIn entry in `includeDomains` is refused instead).

## @x402/fetch

```typescript
import { wrapFetchWithPayment } from "@x402/fetch";
import { createEvmSigner } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const fetchWithPayment = wrapFetchWithPayment(fetch, createEvmSigner(account));

const res = await fetchWithPayment("https://browser.gedx402.com/v1/firecrawl/scrape", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ url: "https://example.com" }),
});
const data = await res.json();
```

## Browser automation (Interact)

**No Browserbase.** Multi-step login, forms, and checkout use [Firecrawl Interact](https://docs.firecrawl.dev/features/interact) on the same x402 shard — steerable sessions with `ttl` capped at 120 seconds, not per-URL scrape only.

1. `POST /v1/firecrawl/interact` — open session (natural-language goal or URL). Session `ttl` is at most 120 seconds. Leave `ttl` out and the worker sets it to 120. `ttl` must be a positive integer; a larger value is `400` `{ "error": "over_route_bound", "message": "ttl is at most 120 seconds on this route", "max_ttl": 120 }` after payment is verified, and that response is not settled. `activityTtl`, when set, must be a positive integer no greater than `ttl`. An unpaid create is still `402` until it is paid; the cap is applied in the handler. The response includes `cdpUrl`, `liveViewUrl`, and `interactiveLiveViewUrl` for the session this call just created. Those are browser credentials (`cdpUrl` carries `?token=`).
2. `POST /v1/firecrawl/interact/{sessionId}/execute` — next action(s); pay per x402 call. `timeout`, when set, must be a positive integer of at most 120 seconds. A larger `timeout` is `400` `over_route_bound` (`timeout is at most 120 seconds on this route`, `max_timeout` 120) and is not settled. The body does not include CDP or live-view URLs.
3. `DELETE /v1/firecrawl/interact/{sessionId}` — close, free when you already have the session id.

There is no session list. The worker does not store which payer created which Firecrawl session, so it cannot show a caller only their own sessions, and it does not proxy Firecrawl's account-wide list. `GET /v1/firecrawl/interact` is 404.

`POST /v1/firecrawl/scrape/{jobId}/interact` is paused. Paid or unpaid, it returns `410` before payment and does not call Firecrawl:

```json
{
  "error": "paused",
  "message": "Scrape-bound interact is paused: its browser session stays open until stopped, which the price does not cover. Use POST /v1/firecrawl/interact (session ttl capped at 120 s) and POST /v1/firecrawl/interact/:sessionId/execute. You were not charged.",
  "use_instead": "/v1/firecrawl/interact"
}
```

That route is not listed in discovery and is not quoted a price. Open a session with `POST /v1/firecrawl/interact` and step it with `POST /v1/firecrawl/interact/{sessionId}/execute`. `DELETE /v1/firecrawl/scrape/{jobId}/interact` stays free, so a session that is already open can still be stopped.

For one-shot “find data on the web” without hand-steering steps, use `POST /v1/firecrawl/agent` instead.

```bash
# Create session (x402)
curl -X POST https://browser.gedx402.com/v1/firecrawl/interact \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/login","prompt":"Sign in and open account settings"}'

# Execute next step (x402) — use sessionId from response
curl -X POST "https://browser.gedx402.com/v1/firecrawl/interact/SESSION_ID/execute" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Click export and download CSV"}'
```

Operator dev (Cursor): `firecrawl_interact` MCP tool or `firecrawl interact "…"` CLI — same upstream API.

## Full Firecrawl API

[Firecrawl v2](https://docs.firecrawl.dev/api-reference/v2-introduction) endpoints are proxied under `/v1/firecrawl/*` with x402 on mutating methods. A status GET for a job or monitor id you already have is free, and that JSON never includes `cdpUrl`, a CDP or live-view token, or any other credential-bearing URL.

These unpaid GETs are not served (404). Each one proxied an account-wide listing on the operator's Firecrawl key:

- `GET /v1/firecrawl/interact` — every browser session, including `cdpUrl` and live-view URLs
- `GET /v1/firecrawl/crawl/active` — every active crawl
- `GET /v1/firecrawl/monitor` — every monitor, including webhook headers

Discover: `GET https://browser.gedx402.com/v1/models?bundle=browser` or hub `?shard=browser`.

## Async jobs

Long crawls: use Firecrawl `crawl` or `batch/scrape` on browser.gedx402.com. Async CF Browser Run jobs live on jobs.gedx402.com (omitted from default discovery).
