# webfetch — full agent reference > Give your agents access to the web. One request returns clean content from any URL: JavaScript > rendered, bot challenges handled, every failure reported honestly and charged 0 credits. Base URL https://api.webfetch.run Docs https://webfetch.run/docs Pricing https://webfetch.run/pricing JSON https://webfetch.run/agent.json ## Endpoints POST /v1/scrape Scrape one URL. API key. Paid plans. POST /v1/scrape/demo Same work, Clerk session. This is the FREE plan (the playground). GET /v1/me Account, balance, plan, capabilities. Session. GET /v1/keys List key prefixes. Session. POST /v1/keys Mint a key. Session. 403 plan_requires_api on free. DELETE /v1/keys/:id Revoke a key. Session. GET /health Liveness. No auth. ## Request fields url: string (required) Public http(s) URL. Private ranges are refused. formats: string[] default: ["markdown"] Any format below. Case-insensitive; an unknown name is a 400 that lists the valid ones. egress: "direct" | "datacenter" | "residential" default: "direct" Where the request leaves from. Sets the price tier. onlyMainContent: boolean default: true Narrows html and markdown. rawHtml is never narrowed. waitForSelector: string Wait for this CSS selector before capturing. persist: boolean default: false Store the capture; response gains a URL that renders it. rotations: number default: 3 (0-5) Exits to try when a wall is hit. parallelProbes: number default: 3 (1-5) Exits to try concurrently. solve: boolean default: true Whether a CAPTCHA may be attempted. solveTimeoutMs: number default: 20000 Cap on one solve (5000-120000). timeout: number default: 120000 (5000-300000) Overall budget. On expiry: 504, charged 0. session: object Replay { cookies, egressPort } from a previous metadata.session. attributes: array of {selector, attribute} For the `attributes` format. Max 20. summaryPrompt: string Instruction for `summary`. jsonOptions: { schema } JSON Schema for `json`. ## Formats markdown -> string The page as markdown. Default when no format is requested. html -> string Cleaned HTML. Narrowed by onlyMainContent (default true). rawHtml -> string The document exactly as received. Never narrowed. rawBase64 -> string The document bytes as base64. UTF-8 safe. text -> string Visible text only, no markup. Inline data: images are stripped. screenshot -> string Base64 PNG of the rendered page. links -> string[] Absolute URLs found on the page. images -> array of {src, alt?, width?, height?} Absolute image URLs with alt text. metadata -> object Head tags: title, description, og:*, canonical, language. branding -> object Colours, fonts and logo. Deterministic, no model. attributes -> object Named attributes per selector sent in `attributes`. summary -> string A short abstract. Needs a model key on the deployment. (model-backed: may be unavailable) json -> object Structured extraction against `jsonOptions.schema`. (model-backed: may be unavailable) ## Response { "success": true, "data": { , "metadata": { ... } }, "credits": { "charged": , "remaining": } } metadata: url, finalUrl, title, status, verdict, tier, attempts, bytes, and after a successful fetch: session { cookies, egressPort }. ## Verdicts ok content came back. captcha a challenge was found; if it could not be cleared it is reported, never billed. blocked a wall was served (this includes an IP refusal). success:false, charged 0. error the fetch failed. success:false, charged 0. ## Prices, in credits http/direct: 1 http/datacenter: 2 http/residential: 10 browser/direct: 3 browser/datacenter: 5 browser/residential: 10 solve: 8 Only the tier that actually served the page is charged. A failure costs 0. ## Errors 400 invalid_request — Body did not validate: bad url, unknown format, malformed selector. 400 target_not_allowed — URL is private, internal, or not public http(s). 400 egress_unavailable — That egress tier is not configured here. Refunded. 401 missing_api_key — No credential presented. 401 invalid_api_key — Unknown or revoked key. 401 unauthorized — A session is required (console routes). 402 insufficient_credits — Balance cannot cover the maximum this request could cost. Nothing ran. 403 account_suspended — Account is not active. 403 plan_requires_api — Free plans get the playground, not machine keys. 503 fleet_unavailable — Our renderer was unreachable. Our fault, refunded. 504 timeout — Exceeded `timeout`. Charged 0. 200 blocked — success:false. A wall was served instead of content. Charged 0. ## Guidance 1. Ask only for the format you need. Every format costs the same, so over-asking costs bandwidth, not credits. 2. Check `success`, never the HTTP status alone. 3. A `blocked` result is an IP problem. Retry with residential egress and more rotations before changing anything else. 4. Persist metadata.session. It is the single biggest latency win available to a caller. 5. 402 means top up. 403 plan_requires_api means the account is on the free plan.