{
  "name": "webfetch",
  "motto": "Give your agents access to the web.",
  "baseUrl": "https://api.webfetch.run",
  "docs": "https://webfetch.run/docs",
  "endpoints": {
    "scrape": {
      "method": "POST",
      "path": "/v1/scrape",
      "auth": "api_key",
      "plans": [
        "starter",
        "scale"
      ]
    },
    "playground": {
      "method": "POST",
      "path": "/v1/scrape/demo",
      "auth": "clerk_session",
      "plans": [
        "free",
        "starter",
        "scale"
      ]
    },
    "me": {
      "method": "GET",
      "path": "/v1/me",
      "auth": "clerk_session"
    },
    "keys": {
      "method": "GET|POST",
      "path": "/v1/keys",
      "auth": "clerk_session"
    },
    "health": {
      "method": "GET",
      "path": "/health",
      "auth": "none"
    }
  },
  "auth": {
    "scheme": "bearer",
    "header": "Authorization",
    "prefix": "sk_live_"
  },
  "billing": {
    "freePlan": {
      "hasApiKeys": false,
      "playground": true,
      "credits": 1000,
      "note": "Playground only. Not the API."
    },
    "apiAccessRequiresPaidPlan": true,
    "cheapestPaidPlanUsd": 29,
    "creditsExpire": false,
    "failuresCostCredits": false
  },
  "requestFields": [
    {
      "name": "url",
      "type": "string",
      "required": true,
      "note": "Public http(s) URL. Private ranges are refused."
    },
    {
      "name": "formats",
      "type": "string[]",
      "dflt": "[\"markdown\"]",
      "note": "Any format below. Case-insensitive; an unknown name is a 400 that lists the valid ones."
    },
    {
      "name": "egress",
      "type": "\"direct\" | \"datacenter\" | \"residential\"",
      "dflt": "\"direct\"",
      "note": "Where the request leaves from. Sets the price tier."
    },
    {
      "name": "onlyMainContent",
      "type": "boolean",
      "dflt": "true",
      "note": "Narrows html and markdown. rawHtml is never narrowed."
    },
    {
      "name": "waitForSelector",
      "type": "string",
      "note": "Wait for this CSS selector before capturing."
    },
    {
      "name": "persist",
      "type": "boolean",
      "dflt": "false",
      "note": "Store the capture; response gains a URL that renders it."
    },
    {
      "name": "rotations",
      "type": "number",
      "dflt": "3 (0-5)",
      "note": "Exits to try when a wall is hit."
    },
    {
      "name": "parallelProbes",
      "type": "number",
      "dflt": "3 (1-5)",
      "note": "Exits to try concurrently."
    },
    {
      "name": "solve",
      "type": "boolean",
      "dflt": "true",
      "note": "Whether a CAPTCHA may be attempted."
    },
    {
      "name": "solveTimeoutMs",
      "type": "number",
      "dflt": "20000",
      "note": "Cap on one solve (5000-120000)."
    },
    {
      "name": "timeout",
      "type": "number",
      "dflt": "120000 (5000-300000)",
      "note": "Overall budget. On expiry: 504, charged 0."
    },
    {
      "name": "session",
      "type": "object",
      "note": "Replay { cookies, egressPort } from a previous metadata.session."
    },
    {
      "name": "attributes",
      "type": "array of {selector, attribute}",
      "note": "For the `attributes` format. Max 20."
    },
    {
      "name": "summaryPrompt",
      "type": "string",
      "note": "Instruction for `summary`."
    },
    {
      "name": "jsonOptions",
      "type": "{ schema }",
      "note": "JSON Schema for `json`."
    }
  ],
  "formats": [
    {
      "name": "markdown",
      "type": "string",
      "note": "The page as markdown. Default when no format is requested."
    },
    {
      "name": "html",
      "type": "string",
      "note": "Cleaned HTML. Narrowed by onlyMainContent (default true)."
    },
    {
      "name": "rawHtml",
      "type": "string",
      "note": "The document exactly as received. Never narrowed."
    },
    {
      "name": "rawBase64",
      "type": "string",
      "note": "The document bytes as base64. UTF-8 safe."
    },
    {
      "name": "text",
      "type": "string",
      "note": "Visible text only, no markup. Inline data: images are stripped."
    },
    {
      "name": "screenshot",
      "type": "string",
      "note": "Base64 PNG of the rendered page."
    },
    {
      "name": "links",
      "type": "string[]",
      "note": "Absolute URLs found on the page."
    },
    {
      "name": "images",
      "type": "array of {src, alt?, width?, height?}",
      "note": "Absolute image URLs with alt text."
    },
    {
      "name": "metadata",
      "type": "object",
      "note": "Head tags: title, description, og:*, canonical, language."
    },
    {
      "name": "branding",
      "type": "object",
      "note": "Colours, fonts and logo. Deterministic, no model."
    },
    {
      "name": "attributes",
      "type": "object",
      "note": "Named attributes per selector sent in `attributes`."
    },
    {
      "name": "summary",
      "type": "string",
      "note": "A short abstract. Needs a model key on the deployment.",
      "needsModel": true
    },
    {
      "name": "json",
      "type": "object",
      "note": "Structured extraction against `jsonOptions.schema`.",
      "needsModel": true
    }
  ],
  "prices": {
    "http/direct": 1,
    "http/datacenter": 2,
    "http/residential": 10,
    "browser/direct": 3,
    "browser/datacenter": 5,
    "browser/residential": 10,
    "solve": 8
  },
  "errors": [
    {
      "code": "invalid_request",
      "status": 400,
      "note": "Body did not validate: bad url, unknown format, malformed selector."
    },
    {
      "code": "target_not_allowed",
      "status": 400,
      "note": "URL is private, internal, or not public http(s)."
    },
    {
      "code": "egress_unavailable",
      "status": 400,
      "note": "That egress tier is not configured here. Refunded."
    },
    {
      "code": "missing_api_key",
      "status": 401,
      "note": "No credential presented."
    },
    {
      "code": "invalid_api_key",
      "status": 401,
      "note": "Unknown or revoked key."
    },
    {
      "code": "unauthorized",
      "status": 401,
      "note": "A session is required (console routes)."
    },
    {
      "code": "insufficient_credits",
      "status": 402,
      "note": "Balance cannot cover the maximum this request could cost. Nothing ran."
    },
    {
      "code": "account_suspended",
      "status": 403,
      "note": "Account is not active."
    },
    {
      "code": "plan_requires_api",
      "status": 403,
      "note": "Free plans get the playground, not machine keys."
    },
    {
      "code": "fleet_unavailable",
      "status": 503,
      "note": "Our renderer was unreachable. Our fault, refunded."
    },
    {
      "code": "timeout",
      "status": 504,
      "note": "Exceeded `timeout`. Charged 0."
    },
    {
      "code": "blocked",
      "status": 200,
      "note": "success:false. A wall was served instead of content. Charged 0."
    }
  ],
  "failureCostsCredits": false,
  "verdicts": [
    "ok",
    "captcha",
    "blocked",
    "error"
  ],
  "defaults": {
    "rotations": 3,
    "parallelProbes": 3,
    "solveTimeoutMs": 20000,
    "timeoutMs": 120000,
    "maxTimeoutMs": 300000
  }
}
