← WebExtract

WebExtract API docs

Base URL: https://api.webextract.dev (same app at https://webextract.dev). Machine-readable: /openapi.json · /llms.txt · /.well-known/x402.

Framework guides: Vercel AI SDK (@webextract/ai-sdk) · LangChain (langchain-webextract for Python, @webextract/langchain for JS).

Endpoints

EndpointReturnsPrepaidx402
GET /v1/extract?url=<page>Markdown + metadata, product, tables1 credit$0.005
GET /v1/markdown?url=<page>Markdown, title, word count0.4 credit$0.002
GET /v1/balanceCredits left (or free calls left today for your IP)free—

Prices and provenance

product.price_detail is the recommended price field. Each entry in prices[] has type (list, sale, current), amount, currency, and the matched variant when the page proves it. It never contains 0. When no price can be trusted, status is unresolved and unresolved.reason says why (for example no_offer_in_structured_data, variant_not_matched, zero_price). When the URL selects a variant (?variant=, ?sku=, ?size=…), the flat product.price is that variant's price. If the variant cannot be matched on a page whose variants have different prices, product.price is null instead of another variant's price. Listing and category pages return product: null with product_unresolved.reason: "listing_page". content_source is raw_html (no JavaScript is executed); product.source is the extraction method (json-ld, opengraph, microdata, heuristic).

Both data endpoints also accept POST with a JSON body {"url": "https://..."} (or a form body).

curl -H "X-Billing: free" "https://api.webextract.dev/v1/extract?url=https://example.com"
curl -X POST https://api.webextract.dev/v1/markdown -H "X-Billing: free" -H "Content-Type: application/json" -d '{"url":"https://example.com"}'

Auth and billing

NetworkCAIP-2USDC
Baseeip155:84530x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Polygoneip155:1370x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
Arbitrum Oneeip155:421610xaf88d065e77c8cC2239327C5EDb3A432268e5831
World Chaineip155:4800x79A02482A880bCE3F13e09Da970dC34db4CD24d1
Monadeip155:1430x754704Bc059F8C67012fEd69BC8A327a5aafb603
Avalanche C-Chaineip155:431140xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E
OP Mainneteip155:100x0b2C639c533813f4Aa9D7837CAf62653d097Ff85

X-Billing header: free opts into the free tier. x402 returns the 402 with payment requirements (reason x402_requested). x402-testnet uses the sandbox (below). Any other value returns 400 invalid_billing_mode.

Order of checks: 1) url validation (400, never billed) → 2) Authorization: Bearer key (credits; wins over everything below) → 3) payment header PAYMENT-SIGNATURE (wins over X-Billing: free) → 4) X-Billing: x402 → 402 x402_requested → 5) X-Billing: free or ?free=1 → free quota, or 402 free_limit_reached with X-Free-Remaining: 0 → 6) otherwise 402 x402_required.

Note on ?free=1: it is read from the API's own query string. If your target URL has its own query string, URL-encode it (curl -G --data-urlencode "url=https://a.com/?x=1" -d free=1 ...) or simply use the header.

SDK note: official x402 SDKs (@x402/evm 2.28, Python x402 2.25) do not include OP Mainnet and World Chain USDC in their default asset lists; add them to spendControls.allowedAssets, and register only the network(s) where your wallet holds USDC (or pass a requirements selector), otherwise the SDK picks the first option (Base).

HTTP 402 Payment Required

The x402 v2 requirements are in the PAYMENT-REQUIRED response header (base64 JSON). Sign one option and retry with PAYMENT-SIGNATURE. The JSON body says why payment is needed; the same value is in the X-Payment-Reason header:

reasonMeaning
x402_requiredNo key, no payment and no free opt-in. Pay with x402, send a Bearer key, or retry with X-Billing: free while your IP has free calls left.
x402_requestedYou sent X-Billing: x402 (or used the sandbox) and no payment yet.
free_limit_reachedYou opted into the free tier but your IP's free daily calls are used up (X-Free-Remaining: 0). Retrying with X-Billing: free will not help today.
invalid_paymentA payment was sent but rejected; detail has a short reason code (e.g. insufficient funds, expired, bad signature) or a decode error. Nothing was charged.
unsupported_networkThe payment used a network not offered for this endpoint.
settlement_failedThe payment was verified but on-chain settlement did not complete. Nothing was charged (charged: false, retryable: true). Retry the request with a freshly signed payment.
{"error":"payment_required","reason":"x402_required","message":"Payment required. ... or try it free: add header 'X-Billing: free' ...",
 "x402":{"price":"$0.005","network":"eip155:8453","networks":["eip155:8453","eip155:137",...],"asset":"USDC"},
 "stripe":{"buy_url":"https://webextract.dev/#pricing","packs":[...]}}

With a prepaid key and no credits left you get 402 {"error":"no_credits"}.

Errors

All errors are JSON and never charged: 400 missing or invalid url (checked before any billing) or unknown X-Billing value · 401 invalid API key · 402 payment required / out of credits · 413 page over 3 MB · 415 unsupported content type · 422 target site blocked or errored · 451 robots.txt disallows the URL.

Sandbox (free test money only)

Test an x402 client end to end without real money. Same endpoints and responses:

https://api.webextract.dev/sandbox/v1/extract?url=...
https://api.webextract.dev/sandbox/v1/markdown?url=...
# or on the normal endpoints:
curl -H "X-Billing: x402-testnet" "https://api.webextract.dev/v1/markdown?url=https://example.com"

MCP

Remote streamable-http server: https://api.webextract.dev/mcp (stateless JSON). Tools extract_url and page_markdown, both with outputSchema and structuredContent. Registry name dev.webextract/extract. Server card: /.well-known/mcp/server-card.json. MCP calls use the free tier (50/day per IP, no header needed) or a Bearer key; for pay-per-call use the REST endpoints with x402.

Crawling policy

We identify as WebExtractBot, obey robots.txt, never bypass CAPTCHAs or anti-bot protection, and refuse private network addresses.