{
  "openapi": "3.1.0",
  "info": {
    "title": "CrawlForge REST API",
    "version": "1.0.0",
    "description": "30 metered web tools behind one API key, sent as the X-API-Key header. Every call is priced in credits: x-credits on an operation is its base price, and a tool with a per-unit or conditional price explains it in x-credits-note and x-extra. Credits are deducted only after a call succeeds. Every error response, whatever its status, charges nothing. A successful response reports credits_used and credits_remaining. Each tool also answers GET with the self-description this document was generated from. Full reference, including each tool's response shape: https://www.crawlforge.dev/docs/api-reference",
    "termsOfService": "https://www.crawlforge.dev/terms",
    "contact": {
      "name": "CrawlForge",
      "email": "simon@crawlforge.dev"
    }
  },
  "servers": [
    {
      "url": "https://www.crawlforge.dev/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "tools",
      "description": "The web tools. POST runs a tool and is metered; GET describes it and is free."
    }
  ],
  "paths": {
    "/tools/agent": {
      "post": {
        "operationId": "agent",
        "summary": "Autonomous research/extraction from a natural-language prompt — no URLs required. Plans search queries, fetches and filters relevant pages, and returns a prose or structured answer under hard safety caps. Agent runs can exceed the REST API's ~50s window (the MCP tool allows 120s) — keep maxSteps/maxUrls small, or use the CrawlForge MCP server for long runs.",
        "tags": [
          "tools"
        ],
        "x-credits": 8,
        "x-extra": {
          "availability": {
            "hosted_api": true,
            "mcp_server": true,
            "note": "Runs on the CrawlForge execution backend. Agent runs can exceed the REST API's ~50s window (the MCP tool allows up to 120s wall-clock) — keep maxSteps/maxUrls small, or use the CrawlForge MCP server for long runs. model \"pro\" is not available via the REST API."
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/agent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentRequest"
              },
              "example": {
                "prompt": "Find the current pricing tiers for the top 3 MCP web-scraping providers",
                "maxSteps": 3,
                "maxUrls": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 8 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_agent",
        "summary": "Describe agent",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/analyze_content": {
      "post": {
        "operationId": "analyze_content",
        "summary": "Analyze web content for language, keywords, sentiment, and readability",
        "tags": [
          "tools"
        ],
        "x-credits": 3,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/analyze-content",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnalyzeContentRequest"
              },
              "example": {
                "url": "https://example.com/article",
                "analyze_sentiment": true,
                "extract_keywords": true,
                "detect_language": true,
                "analyze_readability": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 3 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_analyze_content",
        "summary": "Describe analyze_content",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/batch_scrape": {
      "post": {
        "operationId": "batch_scrape",
        "summary": "Fetch up to 50 URLs concurrently in one synchronous call, returning title, text, and optional CSS-extracted fields per URL",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-credits-note": "5 per URL attempted (skipped URLs are not charged)",
        "x-extra": {
          "execution_model": {
            "mode": "synchronous",
            "time_budget": "20s of fetching per batch; URLs not started in time return status \"skipped\" and are not charged",
            "robots": "URLs disallowed by the target's robots.txt, URLs on a host CrawlForge has permanently blocked or that asked us to back off, and URLs whose host declares a Crawl-delay too long for the batch budget all return status \"skipped\" (with the reason in error) and are not charged; the reasons are summarized in notes",
            "crawl_delay": "A robots.txt Crawl-delay is honoured per host: URLs on that host are fetched one at a time, spaced by the delay, while other hosts keep the full concurrency",
            "per_url_timeout": "8s",
            "javascript": "Pages are fetched without JavaScript execution; screenshots and JS rendering require a browser — use the CrawlForge MCP server (npm: crawlforge-mcp-server)",
            "results_retention": "The full result set is stored for 24h and retrievable via get_batch_results using the returned batch_id"
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/batch-scrape",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchScrapeRequest"
              },
              "example": {
                "urls": [
                  {
                    "url": "https://example1.com/products",
                    "id": "site1"
                  },
                  {
                    "url": "https://example2.com/products",
                    "id": "site2"
                  }
                ],
                "batch_config": {
                  "concurrency": 5
                },
                "extraction_template": {
                  "fields": [
                    {
                      "name": "title",
                      "selector": "h1"
                    },
                    {
                      "name": "price",
                      "selector": ".price"
                    },
                    {
                      "name": "canonical",
                      "selector": "link[rel=\"canonical\"]",
                      "attribute": "href"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_batch_scrape",
        "summary": "Describe batch_scrape",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/crawl_deep": {
      "post": {
        "operationId": "crawl_deep",
        "summary": "Crawl websites deeply using breadth-first search to discover and extract content",
        "tags": [
          "tools"
        ],
        "x-credits": 4,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/crawl-deep",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrawlDeepRequest"
              },
              "example": {
                "start_url": "https://example.com",
                "max_pages": 20,
                "max_depth": 3,
                "same_domain_only": true,
                "crawl_delay": 1000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 4 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_crawl_deep",
        "summary": "Describe crawl_deep",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/deep_research": {
      "post": {
        "operationId": "deep_research",
        "summary": "Multi-query web research: runs Google searches, fetches the top sources, and returns the most relevant passages verbatim with citations. Synthesis is extractive (no LLM on the hosted API) — for LLM-synthesized research use the CrawlForge MCP server.",
        "tags": [
          "tools"
        ],
        "x-credits": 10,
        "x-extra": {
          "response_shape": {
            "methodology": "Queries run, backend used, source counts, synthesis: extractive, llm_used: false",
            "key_findings": "Up to 10 query-relevant passages, each with text, source_url, source_title, relevance_score. Author/affiliation and DOI front-matter is excluded, passages that barely mention the topic are dropped rather than used as padding (so this list can be short), and no single source domain fills more than 40% of it while another source has a passage to offer",
            "sources": "Every source considered: url, title, snippet, fetched (false = search snippet only), domain",
            "summary": "Extractive summary — the highest-scoring sentences from the findings, verbatim"
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/deep-research",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeepResearchRequest"
              },
              "example": {
                "research_query": "impact of remote work on software team productivity",
                "research_scope": {
                  "depth_level": "moderate",
                  "time_range": "year"
                },
                "max_sources": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 10 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_deep_research",
        "summary": "Describe deep_research",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_content": {
      "post": {
        "operationId": "extract_content",
        "summary": "Extract main article content from web pages using readability detection",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-content",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractContentRequest"
              },
              "example": {
                "url": "https://example.com/article",
                "include_images": true,
                "include_links": false,
                "clean_html": true,
                "extract_main_content": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_content",
        "summary": "Describe extract_content",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_embedded_state": {
      "post": {
        "operationId": "extract_embedded_state",
        "summary": "Return the JSON state a page already ships in its own HTML: __NEXT_DATA__, React Server Component flight chunks (self.__next_f), __NUXT__, __APOLLO_STATE__, __INITIAL_STATE__, __PRELOADED_STATE__ and <script type=\"application/json\"> blocks. One fetch, exact values, and no LLM in the extraction path — the numbers come from the site's own serialized state, so they cannot be fabricated. Sources are reported in \"found\" with their sizes; use \"path\" to return one subtree instead of a multi-megabyte blob. Results are never truncated.",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-embedded-state",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractEmbeddedStateRequest"
              },
              "example": {
                "url": "https://www.ticketmaster.com/discover/concerts",
                "path": "next_data.props.pageProps"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_embedded_state",
        "summary": "Describe extract_embedded_state",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_links": {
      "post": {
        "operationId": "extract_links",
        "summary": "Extract and analyze all links from HTML content",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-links",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractLinksRequest"
              },
              "example": {
                "url": "https://example.com",
                "include_external": true,
                "include_internal": true,
                "filter_domains": [
                  "example.com",
                  "subdomain.example.com"
                ],
                "deduplicate": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_links",
        "summary": "Describe extract_links",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_metadata": {
      "post": {
        "operationId": "extract_metadata",
        "summary": "Extract comprehensive metadata from HTML including SEO, social, and technical information",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-metadata",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractMetadataRequest"
              },
              "example": {
                "url": "https://example.com",
                "include_social": true,
                "include_seo": true,
                "include_technical": true,
                "include_structured_data": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_metadata",
        "summary": "Describe extract_metadata",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_structured": {
      "post": {
        "operationId": "extract_structured",
        "summary": "Extract structured data matching a JSON Schema from CSS selector hints, JSON-LD structured data, and OpenGraph/meta tags. LLM-guided extraction is available via the CrawlForge MCP server, not this endpoint.",
        "tags": [
          "tools"
        ],
        "x-credits": 3,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-structured",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractStructuredRequest"
              },
              "example": {
                "url": "https://example.com/product",
                "schema": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "price": {
                      "type": "number"
                    },
                    "description": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "title",
                    "price"
                  ]
                },
                "selectorHints": {
                  "title": "h1.product-title",
                  "price": ".price"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 3 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_structured",
        "summary": "Describe extract_structured",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_text": {
      "post": {
        "operationId": "extract_text",
        "summary": "Extract clean text content from HTML with various formatting options",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-text",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractTextRequest"
              },
              "example": {
                "url": "https://example.com",
                "selector": "article",
                "clean": true,
                "preserve_links": true,
                "max_length": 5000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_text",
        "summary": "Describe extract_text",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/extract_with_llm": {
      "post": {
        "operationId": "extract_with_llm",
        "summary": "Extract structured data from a URL or text using a natural-language prompt. On a locally-run MCP server this defaults to local Ollama; provider \"openai\" or \"anthropic\" use cloud models.",
        "tags": [
          "tools"
        ],
        "x-credits": 3,
        "x-extra": {
          "availability": {
            "hosted_api": true,
            "mcp_server": true,
            "note": "Runs on the CrawlForge execution backend. Which providers actually work depends on that backend's configuration: the default \"auto\"/\"ollama\" path uses the Ollama endpoint configured there (e.g. Ollama Cloud via OLLAMA_BASE_URL + OLLAMA_API_KEY), and provider \"openai\"/\"anthropic\" run only with API keys configured on the backend. For extraction against your own local Ollama, run the CrawlForge MCP server yourself."
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/extract-with-llm",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractWithLlmRequest"
              },
              "example": {
                "url": "https://example.com/article",
                "prompt": "Extract the article title, author, and key takeaways",
                "schema": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "author": {
                      "type": "string"
                    },
                    "takeaways": {
                      "type": "array"
                    }
                  },
                  "required": [
                    "title"
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 3 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_extract_with_llm",
        "summary": "Describe extract_with_llm",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/fetch_url": {
      "post": {
        "operationId": "fetch_url",
        "summary": "Fetch content from a URL with optional headers and configuration",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-extra": {
          "notes": [
            "Responses include response_time_ms — how long the target took to respond and deliver its body.",
            "Bodies are decoded using the response's own charset (Content-Type, then a <meta charset> sniff) rather than assumed UTF-8.",
            "A body larger than 25MB is refused with RESPONSE_TOO_LARGE (413) and is not charged."
          ]
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/fetch-url",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FetchUrlRequest"
              },
              "example": {
                "url": "https://example.com",
                "headers": {
                  "Accept": "text/html"
                },
                "timeout": 15000,
                "follow_redirects": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_fetch_url",
        "summary": "Describe fetch_url",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/generate_llms_txt": {
      "post": {
        "operationId": "generate_llms_txt",
        "summary": "Generate llms.txt (and optionally llms-full.txt) from a real analysis of the site: fetches the target page, robots.txt, and sitemap.xml, then reads up to 8 key same-domain pages (about/docs/pricing/blog/api/contact/faq and similar) for their titles, descriptions, and main text.",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/generate-llms-txt",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateLlmsTxtRequest"
              },
              "example": {
                "url": "https://example.com",
                "format": "both",
                "outputOptions": {
                  "organizationName": "Example Inc.",
                  "contactEmail": "ai@example.com"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_generate_llms_txt",
        "summary": "Describe generate_llms_txt",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/get_batch_results": {
      "post": {
        "operationId": "get_batch_results",
        "summary": "Retrieve paginated results for a batch_scrape job by batchId. Results are stored for 24h after the batch completes and are only readable by the account that submitted the batch. batch_scrape runs synchronously, so a stored result set is always complete and the response reports status \"completed\".",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/get-batch-results",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetBatchResultsRequest"
              },
              "example": {
                "batchId": "3f6f2b8e-8c1a-4a52-9f65-2d0f4a9be1c7",
                "page": 1,
                "limit": 25
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_get_batch_results",
        "summary": "Describe get_batch_results",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/list_ollama_models": {
      "post": {
        "operationId": "list_ollama_models",
        "summary": "Lists the Ollama models installed on the machine running the MCP server. Use this to discover which model values you can pass to extract_with_llm.",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-extra": {
          "availability": {
            "hosted_api": true,
            "mcp_server": true,
            "note": "Runs on the CrawlForge execution backend and lists models on that backend's Ollama instance, which may not exist — in that case the call fails without charging. The tool is mainly useful on a locally-run MCP server, where it lists your own models."
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/list-ollama-models",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListOllamaModelsRequest"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_list_ollama_models",
        "summary": "Describe list_ollama_models",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/localization": {
      "post": {
        "operationId": "localization",
        "summary": "Multi-language and geo-location management for international content",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/localization",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocalizationRequest"
              },
              "example": {
                "url": "https://example.com",
                "target_language": "en",
                "target_country": "US",
                "detect_language": true,
                "extract_hreflang": true,
                "check_geo_targeting": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_localization",
        "summary": "Describe localization",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/map_site": {
      "post": {
        "operationId": "map_site",
        "summary": "Enumerate a site's pages: reads {origin}/sitemap.xml when available (up to 500 URLs, following up to 3 child sitemaps), otherwise falls back to a bounded breadth-first crawl (up to 30 pages, ~18s budget)",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-extra": {
          "response_notes": {
            "source": "'sitemap' when the URL list came from sitemap.xml, 'crawl' when discovered by crawling",
            "max_depth_reached": "Actual deepest page crawled; null in sitemap mode (no crawl performed)",
            "sitemap": "Map of crawled page URL to the links found on it; null in sitemap mode"
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/map-site",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MapSiteRequest"
              },
              "example": {
                "url": "https://example.com",
                "max_depth": 2,
                "include_external": false,
                "timeout": 15000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_map_site",
        "summary": "Describe map_site",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/process_document": {
      "post": {
        "operationId": "process_document",
        "summary": "Fetch a document by URL and extract its real text, metadata, and tables. Supported on this endpoint: PDF (text, metadata, tables via pdf-parse), CSV (text + parsed rows), TXT, and HTML (text + metadata). DOCX/XLSX return 501 and are supported by the CrawlForge MCP server.",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/process-document",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProcessDocumentRequest"
              },
              "example": {
                "url": "https://example.com/document.pdf",
                "document_type": "auto",
                "extract_text": true,
                "extract_metadata": true,
                "extract_tables": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_process_document",
        "summary": "Describe process_document",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/read_result": {
      "post": {
        "operationId": "read_result",
        "summary": "Read part of a stored result. A tool whose result exceeded max_inline_chars returned a preview and a result_handle; this reads the stored copy by character range (slice), by case-insensitive literal search with context and offsets (search), by line (lines), or by dotted JSON path (json_path). Results are kept for 1 hour and are readable only by the account that produced them. Nothing is fetched.",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/read-result",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReadResultRequest"
              },
              "example": {
                "handle": "res_3f6f2b8e-8c1a-4a52-9f65-2d0f4a9be1c7",
                "operation": "search",
                "query": "Pricing",
                "max_matches": 5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_read_result",
        "summary": "Describe read_result",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/reddit_search": {
      "post": {
        "operationId": "reddit_search",
        "summary": "Search Reddit posts/comments or read a full comment thread — reads the Arctic Shift community archive (reddit.com blocks direct scraping). A scoped search (subreddit or author) queries the archive directly. A Reddit-wide keyword search finds posts with a site-restricted web search and then reads those posts from the archive — or, in comments mode, searches each of the first five posts' comments for the keywords — because Arctic Shift cannot keyword-search across all of Reddit. A scoped comment search Arctic Shift times out on is retried over the last 7d and 3d and reports window_applied. PullPush stopped serving automated clients in August 2026 and is no longer tried automatically.",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/reddit-search",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RedditSearchRequest"
              },
              "example": {
                "query": "best mechanical keyboard",
                "subreddit": "MechanicalKeyboards",
                "limit": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_reddit_search",
        "summary": "Describe reddit_search",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/scrape": {
      "post": {
        "operationId": "scrape",
        "summary": "Unified single-fetch, multi-format extraction. One fetch serves every requested format: markdown, html, rawHtml, text, links, metadata. A bot-defence challenge page returns success: false with blocked.vendor whatever its HTTP status; an empty shell or an error placeholder served as HTTP 200 is a failure too. Neither is charged. With escalate: true that same call renders the page once in a stealth browser and returns the formats from it, instead of returning the block.",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-extra": {
          "credits_addon": {
            "query_format": 1,
            "escalation": 5,
            "description": "query_format is added once per call when a highlights or question format is present; mode 'model' is rejected here and charged nothing. escalation is added only when escalate: true AND the stealth render actually ran — a call that asked to escalate and was served the page by the plain fetch pays the base price, so the projection base + escalation is a ceiling, never a floor."
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/scrape",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeRequest"
              },
              "example": {
                "url": "https://example.com",
                "formats": [
                  "markdown",
                  "links",
                  "metadata"
                ],
                "onlyMainContent": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_scrape",
        "summary": "Describe scrape",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/scrape_structured": {
      "post": {
        "operationId": "scrape_structured",
        "summary": "Extract structured data from HTML using CSS selectors",
        "tags": [
          "tools"
        ],
        "x-credits": 2,
        "x-extra": {
          "examples": {
            "single_item": {
              "url": "https://example.com/product",
              "selectors": {
                "title": "h1.product-title",
                "price": ".price",
                "description": ".product-description"
              },
              "include_attributes": [
                "href",
                "src"
              ]
            },
            "multiple_items": {
              "url": "https://example.com/products",
              "selectors": {
                "title": ".product-item h3",
                "price": ".product-item .price",
                "link": ".product-item a"
              },
              "multiple": true,
              "max_items": 50,
              "include_attributes": [
                "href"
              ]
            }
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/scrape-structured",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeStructuredRequest"
              },
              "example": {
                "url": "https://example.com/product",
                "selectors": {
                  "title": "h1.product-title",
                  "price": ".price",
                  "description": ".product-description"
                },
                "include_attributes": [
                  "href",
                  "src"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 2 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_scrape_structured",
        "summary": "Describe scrape_structured",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/scrape_template": {
      "post": {
        "operationId": "scrape_template",
        "summary": "Pre-built scrapers for popular sites and public APIs — no schema or selectors required. Three modes: name a template and pass a URL; pass template \"auto\" with a URL to have the matching template picked (the response reports which one it chose); or pass template \"list\" to enumerate them. Templates come in two kinds, reported as \"mode\" by the list. An entity template returns one record from one page. A list connector returns N records from one call, as data.items with a data.count — that covers the job-board group (Greenhouse, Lever, Ashby, Workable, Recruitee, Teamtailor), which reads a company's whole board from its ATS API, the government group (NHTSA vPIC VIN decoding, NPPES NPI provider registry), which reads free keyless federal registries, and shopify-collection, which lists a whole storefront collection. A list connector is driven either by a URL or by \"params\" — e.g. { \"template\": \"greenhouse-jobs\", \"params\": { \"company\": \"stripe\" } }. Sites that block plain HTTP fetches (e.g. Amazon) may return sparse data here; the CrawlForge MCP server version uses stealth browsing for those. reddit-thread reads the post from the Arctic Shift archive, because reddit.com blocks direct scraping; the reddit_search tool reads the comment tree. linkedin-profile and tweet are retired — those sites' robots.txt disallow every keyless path — and naming one, or passing one of their URLs to \"auto\", returns 400 TEMPLATE_UNAVAILABLE with the reason and no charge. Several templates read a machine-readable endpoint rather than the rendered page — shopify-product reads the store's own /products/<handle>.json, npm-package reads the npm registry API, and every list connector reads its platform's API — and report the URL they actually read as \"fetched_url\". When a Shopify store refuses its JSON endpoint (401, 403, 404 or 410), shopify-product reads the product page's own schema.org JSON-LD instead: the record carries \"source\": \"json-ld\" and a warning says so, with per-variant stock, compare-at prices and option names null because JSON-LD does not carry them.",
        "tags": [
          "tools"
        ],
        "x-credits": 1,
        "x-extra": {
          "supported_templates": [
            "shopify-product",
            "shopify-collection",
            "amazon-product",
            "github-repo",
            "youtube-video",
            "reddit-thread",
            "hacker-news-front-page",
            "producthunt-launch",
            "stackoverflow-question",
            "npm-package",
            "greenhouse-jobs",
            "lever-postings",
            "ashby-jobs",
            "workable-jobs",
            "recruitee-offers",
            "teamtailor-jobs",
            "nhtsa-vin",
            "npi-provider"
          ]
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/scrape-template",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeTemplateRequest"
              },
              "example": {
                "template": "github-repo",
                "url": "https://github.com/mysleekdesigns/crawlforge-mcp"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 1 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_scrape_template",
        "summary": "Describe scrape_template",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/scrape_with_actions": {
      "post": {
        "operationId": "scrape_with_actions",
        "summary": "Interact with a page before scraping — click, type, press keys, hover, choose a select option, scroll, navigate on, run JavaScript, or wait for dynamic content. Use for SPAs, login-gated content, or multi-step flows. Set browserOptions.stealth to run the chain in a stealth browser context.",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-extra": {
          "availability": {
            "hosted_api": true,
            "mcp_server": true,
            "note": "Runs on the CrawlForge execution backend. The REST call waits up to ~25s — keep action sequences and browserOptions.timeout within that window; longer runs suit the CrawlForge MCP server."
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/scrape-with-actions",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScrapeWithActionsRequest"
              },
              "example": {
                "url": "https://app.example.com/dashboard",
                "actions": [
                  {
                    "type": "click",
                    "selector": "#login"
                  },
                  {
                    "type": "type",
                    "selector": "#email",
                    "text": "user@example.com"
                  },
                  {
                    "type": "wait",
                    "selector": ".dashboard",
                    "condition": "visible"
                  }
                ],
                "formats": [
                  "markdown"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_scrape_with_actions",
        "summary": "Describe scrape_with_actions",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/search_web": {
      "post": {
        "operationId": "search_web",
        "summary": "Search the web using Google Custom Search API. One query per call, or up to 10 in a single call with queries.",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-extra": {
          "credits_by_query_count": {
            "note": "Charged per query that returned results, because a batch runs one upstream search per query. The figures below are the projection — the published price times every query sent — which is the ceiling; a query that fails is not charged, so the actual cost can be anything from 0 up to it. A single query costs the published price.",
            "one": 5,
            "ten": 50
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/search-web",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchWebRequest"
              },
              "example": {
                "query": "web scraping",
                "limit": 10,
                "lang": "en"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_search_web",
        "summary": "Describe search_web",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/serp_rank": {
      "post": {
        "operationId": "serp_rank",
        "summary": "Check a domain's organic position in Google search results for a keyword (powered by DataForSEO)",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/serp-rank",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SerpRankRequest"
              },
              "example": {
                "keyword": "managed wordpress hosting",
                "target": "example.com",
                "location_name": "United States"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_serp_rank",
        "summary": "Describe serp_rank",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/stealth_mode": {
      "post": {
        "operationId": "stealth_mode",
        "summary": "Stealth browser scraping for sites that block normal scrapers (Cloudflare, DataDome, bot detection). Use operation \"scrape\" for a one-call render of a single URL; create_context → create_page → cleanup remains for multi-step work that reuses one context.",
        "tags": [
          "tools"
        ],
        "x-credits": 5,
        "x-extra": {
          "credits_cost_by_operation": {
            "note": "Browser work costs the published 5 credits; bookkeeping operations cost 1. An absent or unrecognised operation is charged the flat 5.",
            "scrape": 5,
            "create_context": 5,
            "create_page": 5,
            "configure": 1,
            "enable": 1,
            "disable": 1,
            "get_stats": 1,
            "cleanup": 1
          },
          "availability": {
            "hosted_api": true,
            "mcp_server": true,
            "note": "Runs on the CrawlForge execution backend. Browser contexts live on the backend: a contextId stays valid between calls only until the backend recycles it."
          }
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/stealth-mode",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StealthModeRequest"
              },
              "example": {
                "operation": "scrape",
                "url": "https://protected-site.com/product/42",
                "formats": [
                  "markdown",
                  "links"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 5 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_stealth_mode",
        "summary": "Describe stealth_mode",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/summarize_content": {
      "post": {
        "operationId": "summarize_content",
        "summary": "Generate intelligent summaries of web content using extractive summarization",
        "tags": [
          "tools"
        ],
        "x-credits": 4,
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/summarize-content",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SummarizeContentRequest"
              },
              "example": {
                "url": "https://example.com/article",
                "max_sentences": 5,
                "summary_type": "extractive",
                "include_metadata": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 4 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_summarize_content",
        "summary": "Describe summarize_content",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    },
    "/tools/track_changes": {
      "post": {
        "operationId": "track_changes",
        "summary": "Detect content changes on a page by comparing it against a stored baseline (create_baseline, then compare), or create a hosted monitor that checks it on a schedule (monitor)",
        "tags": [
          "tools"
        ],
        "x-credits": 3,
        "x-extra": {
          "notes": [
            "Baselines are stored per API account for 90 days; compare against a missing baseline returns 404 BASELINE_NOT_FOUND.",
            "Change detection is text-based (line diff of visible text, whitespace-normalized). JavaScript-rendered content is not executed — use the CrawlForge MCP server for browser-rendered tracking.",
            "compare also returns structural_similarity (0-1): how far the tracked region still matches the baseline in tag vocabulary and nesting depth. A high change_percent with a high structural_similarity is the same layout with new copy; a low structural_similarity means the markup itself was rebuilt, which is usually what breaks a scraper.",
            "structural_similarity is null when the stored baseline was captured before this field existed. Re-run create_baseline, or pass update_baseline once, to start scoring it.",
            "A bot-defence challenge wall or an unusable document is returned as 502 BLOCKED or UNUSABLE_DOCUMENT with no charge, and is never reported as a change. Hosted monitors record such a page as blocked.",
            "monitor returns the created monitor (including its webhook_secret, shown only here) and estimated_credits_per_month; the response bills 0 credits. Scheduled checks appear in the request log as track_changes calls.",
            "Documented option objects (trackingOptions, storageOptions, monitoringOptions) are accepted but not applied by the hosted REST API."
          ]
        },
        "x-docs-url": "https://www.crawlforge.dev/docs/api-reference/tools/track-changes",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrackChangesRequest"
              },
              "example": {
                "url": "https://competitor.com/pricing",
                "operation": "compare",
                "selector": ".pricing-table",
                "update_baseline": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool's result; 3 credits charged (see x-credits-note and x-extra for add-ons).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolSuccess"
                }
              }
            }
          },
          "400": {
            "description": "VALIDATION_ERROR: the body does not match the request schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "MISSING_API_KEY or INVALID_API_KEY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "INSUFFICIENT_CREDITS: the balance does not cover the tool's base price. X-Auto-Recharge-Triggered says whether an automatic recharge was started; retry once it lands.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMIT_EXCEEDED: your plan's rate limit. Retry-After: 60.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "TOOL_ERROR, INTERNAL_ERROR or CREDIT_DEDUCTION_FAILED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. Which of 403, 404, 413, 422, 501, 502, 503 and 504 a tool can return depends on what it does (fetching a page, calling the execution backend, reading stored results); the code vocabulary is listed on the ErrorResponse schema.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "describe_track_changes",
        "summary": "Describe track_changes",
        "description": "The tool's self-description: its price, its request schema and an example body. No API key is needed.",
        "tags": [
          "tools"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "The ToolInfo this document was generated from.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolInfo"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "An API key from the dashboard. Every POST requires one."
      }
    },
    "schemas": {
      "ToolSuccess": {
        "type": "object",
        "description": "The envelope every tool returns on success. Credits are deducted only after the tool succeeded, so credits_used is the price of this call and an error response never charges.",
        "required": [
          "success",
          "data",
          "credits_used",
          "credits_remaining",
          "processing_time"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "description": "The tool's result. Its shape is tool-specific and documented on the tool's docs page (x-docs-url on the operation)."
          },
          "credits_used": {
            "type": "integer",
            "description": "Credits charged for this call: the operation's x-credits, plus any add-on x-credits-note or x-extra describes."
          },
          "credits_remaining": {
            "type": "integer",
            "description": "The account's credit balance after this call."
          },
          "processing_time": {
            "type": "integer",
            "description": "Server-side processing time in milliseconds."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Non-fatal notes: a robots.txt override that was recorded, a format that could not be produced, a redaction that changed the payload."
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Every error carries error.code and error.message; no error response is charged. Codes by status:\n400 VALIDATION_ERROR (details holds the validation issues; also a URL that resolves to a private or local address, or a body that is not a JSON object). scrape_template adds UNKNOWN_TEMPLATE, NO_TEMPLATE_MATCH, TEMPLATE_UNAVAILABLE, TEMPLATE_MISMATCH, MISSING_URL, INVALID_TEMPLATE_PARAMS and CREDENTIAL_MISSING; scrape adds UNSUPPORTED_FORMAT; read_result adds PATH_NOT_FOUND.\n401 MISSING_API_KEY, INVALID_API_KEY.\n402 INSUFFICIENT_CREDITS (details: required, tool, autoRechargeTriggered, upgradeUrl; the X-Auto-Recharge-Triggered header says whether an automatic recharge was started).\n403 ROBOTS_DISALLOWED (robots.txt disallows the path for CrawlForge; respect_robots: false overrides it and the override is recorded), HOST_BLOCKED (a host on the operator blocklist).\n404 RESULT_NOT_FOUND, BATCH_NOT_FOUND, BASELINE_NOT_FOUND (an unknown or expired handle, batch id or baseline).\n413 RESPONSE_TOO_LARGE, DOCUMENT_TOO_LARGE.\n422 EXTRACTION_FAILED, SELECTOR_NOT_FOUND, NO_PAGES_MAPPED, DOCUMENT_PARSE_FAILED.\n429 RATE_LIMIT_EXCEEDED (your plan's rate limit; Retry-After: 60), HOST_BACKOFF (the target site asked us to wait via Retry-After; not overridable).\n500 TOOL_ERROR, INTERNAL_ERROR, CREDIT_DEDUCTION_FAILED.\n501 OPERATION_NOT_AVAILABLE, UNSUPPORTED_DOCUMENT_TYPE (supported by the CrawlForge MCP server with the same key).\n502 FETCH_FAILED, TARGET_HTTP_ERROR, DOCUMENT_FETCH_ERROR (the target could not be reached or answered with an error), BLOCKED (a bot-defence challenge page; scrape names the vendor in `blocked`), UNUSABLE_DOCUMENT (an empty shell or error placeholder served as a page), MCP_UPSTREAM_ERROR, MCP_UPSTREAM_UNAVAILABLE, MCP_PROXY_MISCONFIGURED, RESEARCH_SEARCH_FAILED, RESEARCH_SEARCH_UNAVAILABLE, TOOL_ERROR (the execution backend reported a tool failure).\n503 TOOL_NOT_AVAILABLE (needs a browser, an LLM or local infrastructure the hosted API does not run), STORAGE_UNAVAILABLE, MCP_PROXY_MISCONFIGURED, RESEARCH_NOT_CONFIGURED, RESEARCH_RATE_LIMITED.\n504 FETCH_TIMEOUT, MCP_UPSTREAM_TIMEOUT, RESEARCH_SEARCH_TIMEOUT.",
        "required": [
          "error"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false,
            "description": "Present, and false, on a tool failure; absent on validation, authentication, credit and rate-limit errors."
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "A stable, upper-case code from the vocabulary above."
              },
              "message": {
                "type": "string",
                "description": "What went wrong, for a person."
              },
              "details": {
                "description": "Extra structure for some codes: the validation issues for VALIDATION_ERROR, the credit shortfall for INSUFFICIENT_CREDITS, the plan and limit for RATE_LIMIT_EXCEEDED."
              }
            }
          },
          "blocked": {
            "type": "object",
            "description": "scrape only, with code BLOCKED: the bot-defence vendor recognised on the page and the evidence for it.",
            "required": [
              "vendor",
              "evidence"
            ],
            "properties": {
              "vendor": {
                "type": "string"
              },
              "evidence": {
                "type": "string"
              }
            }
          },
          "escalated": {
            "type": "boolean",
            "const": true,
            "description": "scrape only: the call asked to escalate, the stealth render ran, and it did not get past the wall either. Nothing was charged."
          }
        }
      },
      "ToolInfo": {
        "type": "object",
        "description": "A tool's self-description, as its GET returns it and as this document was generated from it.",
        "required": [
          "tool",
          "description",
          "credits_cost",
          "parameters",
          "example"
        ],
        "properties": {
          "tool": {
            "type": "string",
            "description": "The tool name, as in the path."
          },
          "description": {
            "type": "string"
          },
          "credits_cost": {
            "type": "number",
            "description": "The base price in credits. A per-unit or conditional price is explained in credits_note."
          },
          "credits_note": {
            "type": "string"
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true,
            "description": "JSON Schema (draft 2020-12) for the POST request body."
          },
          "example": {
            "type": "object",
            "additionalProperties": true,
            "description": "A request body that runs."
          },
          "extra": {
            "type": "object",
            "additionalProperties": true,
            "description": "Tool-specific notes, such as a price table by operation or an execution model."
          }
        }
      },
      "AgentRequest": {
        "type": "object",
        "properties": {
          "prompt": {
            "type": "string",
            "description": "Natural-language task or question (1-2000 chars)"
          },
          "urls": {
            "type": "array",
            "description": "Optional seed URLs to include (max 20)"
          },
          "schema": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional JSON schema for structured output"
          },
          "model": {
            "type": "string",
            "enum": [
              "default",
              "pro"
            ],
            "default": "default",
            "description": "\"default\" = built-in planning loop. \"pro\" requires interactive confirmation and is rejected by the REST API — use the CrawlForge MCP server for pro runs."
          },
          "maxSteps": {
            "type": "number",
            "default": 5,
            "description": "Max fetch iterations (hard cap: 10)"
          },
          "maxUrls": {
            "type": "number",
            "default": 10,
            "description": "Max URLs to fetch (hard cap: 20)"
          }
        },
        "required": [
          "prompt"
        ]
      },
      "AnalyzeContentRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to fetch and analyze content from"
          },
          "content": {
            "type": "string",
            "description": "Direct content to analyze (if URL not provided)"
          },
          "analyze_sentiment": {
            "default": false,
            "type": "boolean",
            "description": "Perform sentiment analysis"
          },
          "extract_keywords": {
            "default": true,
            "type": "boolean",
            "description": "Extract top keywords"
          },
          "detect_language": {
            "default": true,
            "type": "boolean",
            "description": "Detect content language. Returns an ISO 639-1 code, or \"und\" when the text is too short or the language is not recognised."
          },
          "analyze_readability": {
            "default": false,
            "type": "boolean",
            "description": "Calculate readability scores"
          },
          "timeout": {
            "default": 10000,
            "type": "number",
            "minimum": 1000,
            "maximum": 30000,
            "description": "Request timeout in milliseconds (1000-30000)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          }
        },
        "anyOf": [
          {
            "required": [
              "url"
            ]
          },
          {
            "required": [
              "content"
            ]
          }
        ]
      },
      "BatchScrapeRequest": {
        "type": "object",
        "properties": {
          "urls": {
            "minItems": 1,
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Target URL"
                },
                "id": {
                  "type": "string",
                  "description": "Optional identifier (defaults to the array index)"
                },
                "priority": {
                  "type": "string",
                  "enum": [
                    "high",
                    "medium",
                    "low"
                  ]
                },
                "custom_config": {
                  "type": "object",
                  "properties": {
                    "headers": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {
                        "type": "string"
                      }
                    },
                    "timeout": {
                      "type": "number",
                      "minimum": 1000,
                      "maximum": 60000
                    },
                    "selectors": {
                      "type": "object",
                      "propertyNames": {
                        "type": "string"
                      },
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "required": [
                "url"
              ]
            },
            "description": "Array of URLs to scrape (max 50 per synchronous batch on the hosted REST API)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch each origin's robots.txt and skip URLs it disallows for CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. A disallowed URL returns status \"skipped\" and is not charged; the rest of the batch runs."
          },
          "batch_config": {
            "type": "object",
            "properties": {
              "concurrency": {
                "default": 5,
                "type": "number",
                "minimum": 1,
                "maximum": 10,
                "description": "Max concurrent requests (capped at 8)"
              },
              "delay_between_batches": {
                "type": "number",
                "minimum": 0,
                "maximum": 10000
              },
              "retry_failed": {
                "type": "boolean"
              },
              "max_retries": {
                "type": "number",
                "minimum": 0,
                "maximum": 5
              },
              "fail_fast": {
                "type": "boolean"
              },
              "preserve_order": {
                "type": "boolean"
              }
            },
            "description": "Batch processing configuration"
          },
          "extraction_template": {
            "type": "object",
            "properties": {
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "selector": {
                      "type": "string"
                    },
                    "xpath": {
                      "type": "string"
                    },
                    "attribute": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "name"
                  ]
                },
                "description": "Fields to extract from each page: { name, selector (CSS), attribute? }. Without attribute the element text is returned; xpath is not supported."
              },
              "capture_screenshots": {
                "type": "boolean"
              },
              "extract_links": {
                "type": "boolean"
              },
              "extract_images": {
                "type": "boolean"
              }
            },
            "description": "Optional per-page field extraction"
          },
          "output_config": {
            "type": "object",
            "properties": {
              "format": {
                "type": "string",
                "enum": [
                  "json",
                  "csv",
                  "xml"
                ]
              },
              "include_metadata": {
                "type": "boolean"
              },
              "include_errors": {
                "type": "boolean"
              },
              "flatten_results": {
                "type": "boolean"
              }
            }
          },
          "options": {
            "type": "object",
            "properties": {
              "user_agent": {
                "type": "string"
              },
              "follow_redirects": {
                "type": "boolean"
              },
              "javascript_enabled": {
                "type": "boolean"
              },
              "respect_robots_txt": {
                "type": "boolean"
              },
              "rate_limit_per_domain": {
                "type": "number",
                "minimum": 100,
                "maximum": 5000
              }
            }
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "required": [
          "urls"
        ]
      },
      "CrawlDeepRequest": {
        "type": "object",
        "properties": {
          "start_url": {
            "type": "string",
            "format": "uri",
            "description": "The starting URL for the crawl"
          },
          "max_pages": {
            "default": 10,
            "type": "number",
            "minimum": 1,
            "maximum": 100,
            "description": "Maximum number of pages to crawl (1-100)"
          },
          "max_depth": {
            "default": 3,
            "type": "number",
            "minimum": 1,
            "maximum": 5,
            "description": "Maximum depth to crawl (1-5)"
          },
          "same_domain_only": {
            "default": true,
            "type": "boolean",
            "description": "Only crawl pages on the same domain"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch each origin's robots.txt and skip URLs it disallows for CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED when start_url itself is disallowed, with no credits charged; URLs disallowed mid-crawl are skipped and counted in warnings. Setting this to false is recorded against your API key."
          },
          "respect_robots_txt": {
            "default": true,
            "type": "boolean",
            "description": "Alias of respect_robots, kept for callers that already send it. Either parameter set to false disables the gate."
          },
          "crawl_delay": {
            "default": 1000,
            "type": "number",
            "minimum": 0,
            "maximum": 5000,
            "description": "Delay between requests to the same host, in milliseconds (0-5000). Resolved against the site's own robots.txt Crawl-delay as the longer of the two, so the site wins when it asks for more and the two never stack. A delay that will not fit in the crawl timeout skips the page rather than overrunning the budget."
          },
          "timeout": {
            "default": 30000,
            "type": "number",
            "minimum": 1000,
            "maximum": 60000,
            "description": "Total crawl timeout in milliseconds (1000-60000)"
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "required": [
          "start_url"
        ]
      },
      "DeepResearchRequest": {
        "type": "object",
        "properties": {
          "research_query": {
            "type": "string",
            "minLength": 10,
            "description": "The research question or topic (minimum 10 characters)"
          },
          "research_scope": {
            "type": "object",
            "properties": {
              "depth_level": {
                "default": "moderate",
                "type": "string",
                "enum": [
                  "surface",
                  "moderate",
                  "deep",
                  "comprehensive"
                ],
                "description": "How many sources to gather: surface (3), moderate (5), deep (8), comprehensive (10)"
              },
              "time_range": {
                "default": "any",
                "type": "string",
                "enum": [
                  "any",
                  "day",
                  "week",
                  "month",
                  "year"
                ],
                "description": "Restrict results by recency: any, day, week, month, year"
              },
              "language": {
                "default": "en",
                "type": "string",
                "minLength": 2,
                "maxLength": 10,
                "description": "Search language code (e.g. 'en')"
              },
              "domains": {
                "maxItems": 10,
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 3
                },
                "description": "Restrict results to these domains (e.g. ['nature.com', 'arxiv.org']; max 10)"
              }
            },
            "description": "Optional scoping controls"
          },
          "max_sources": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "description": "Override the source count from depth_level (1-10)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch each origin's robots.txt and skip source pages it disallows for CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. A disallowed source is reported with fetched: false and contributes only its search snippet; the reason is named in warnings. The search itself is a documented Google API call and is never gated."
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          }
        },
        "required": [
          "research_query"
        ]
      },
      "ExtractContentRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to extract content from"
          },
          "include_images": {
            "default": true,
            "type": "boolean",
            "description": "Include images found in the content"
          },
          "include_links": {
            "default": false,
            "type": "boolean",
            "description": "Include links found in the content"
          },
          "clean_html": {
            "default": true,
            "type": "boolean",
            "description": "Return clean text instead of HTML"
          },
          "extract_main_content": {
            "default": true,
            "type": "boolean",
            "description": "Use readability algorithms to find main content"
          },
          "timeout": {
            "default": 10000,
            "type": "number",
            "minimum": 1000,
            "maximum": 30000,
            "description": "Request timeout in milliseconds (1000-30000)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "required": [
          "url"
        ]
      },
      "ExtractEmbeddedStateRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to read embedded state from"
          },
          "path": {
            "type": "string",
            "description": "Return one subtree instead of everything, e.g. \"next_data.props.pageProps\" or \"json_scripts[0].data\". Dotted keys and array indexes only — not JSONPath: no wildcards, filters, slices or recursive descent. A path that does not resolve is a 400 naming the keys that were available at the point it stopped, and costs no credits."
          },
          "user_agent": {
            "type": "string",
            "description": "Override the User-Agent sent to the target"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Honour the target's robots.txt. Setting this to false is recorded against your API key."
          },
          "timeout": {
            "default": 20000,
            "type": "integer",
            "minimum": 1000,
            "maximum": 60000,
            "description": "Fetch timeout in ms (1000-60000)"
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          }
        },
        "required": [
          "url"
        ]
      },
      "ExtractLinksRequest": {
        "type": "object",
        "properties": {
          "html": {
            "type": "string",
            "minLength": 1,
            "description": "HTML content to extract links from"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to fetch and extract links from"
          },
          "base_url": {
            "type": "string",
            "format": "uri",
            "description": "Base URL for resolving relative links"
          },
          "filter_domains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Only include links from specified domains"
          },
          "include_external": {
            "default": true,
            "type": "boolean",
            "description": "Include external links"
          },
          "include_internal": {
            "default": true,
            "type": "boolean",
            "description": "Include internal links"
          },
          "include_anchors": {
            "default": false,
            "type": "boolean",
            "description": "Include anchor links (#fragment)"
          },
          "deduplicate": {
            "default": true,
            "type": "boolean",
            "description": "Remove duplicate links"
          },
          "include_metadata": {
            "default": true,
            "type": "boolean",
            "description": "Include link metadata (title, domain, etc.)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          }
        },
        "anyOf": [
          {
            "required": [
              "html"
            ]
          },
          {
            "required": [
              "url"
            ]
          }
        ]
      },
      "ExtractMetadataRequest": {
        "type": "object",
        "properties": {
          "html": {
            "type": "string",
            "minLength": 1,
            "description": "HTML content to extract metadata from"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to fetch and extract metadata from"
          },
          "include_social": {
            "default": true,
            "type": "boolean",
            "description": "Include Open Graph and Twitter Card metadata"
          },
          "include_seo": {
            "default": true,
            "type": "boolean",
            "description": "Include SEO-related metadata"
          },
          "include_technical": {
            "default": true,
            "type": "boolean",
            "description": "Include technical metadata (scripts, stylesheets, etc.)"
          },
          "include_structured_data": {
            "default": true,
            "type": "boolean",
            "description": "Include structured data (JSON-LD, microdata)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          }
        },
        "anyOf": [
          {
            "required": [
              "html"
            ]
          },
          {
            "required": [
              "url"
            ]
          }
        ]
      },
      "ExtractStructuredRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to extract structured data from"
          },
          "schema": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "properties": {
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {}
              },
              "required": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "properties"
            ],
            "description": "JSON Schema defining the data structure to extract"
          },
          "prompt": {
            "type": "string",
            "description": "Accepted for compatibility; only used by the MCP server LLM extraction"
          },
          "llmConfig": {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string"
              },
              "apiKey": {
                "type": "string"
              }
            },
            "description": "Accepted for compatibility; this endpoint never calls an LLM provider"
          },
          "selectorHints": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "string"
            },
            "description": "CSS selectors per schema property, checked before structured data"
          },
          "fallbackToSelectors": {
            "default": true,
            "type": "boolean",
            "description": "When false and required fields are set, extracting nothing returns EXTRACTION_FAILED instead of nulls"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          }
        },
        "required": [
          "url",
          "schema"
        ]
      },
      "ExtractTextRequest": {
        "type": "object",
        "properties": {
          "html": {
            "type": "string",
            "minLength": 1,
            "description": "HTML content to extract text from"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to fetch and extract text from"
          },
          "selector": {
            "type": "string",
            "description": "CSS selector to target specific elements"
          },
          "clean": {
            "default": true,
            "type": "boolean",
            "description": "Remove extra whitespace and formatting"
          },
          "preserve_links": {
            "default": false,
            "type": "boolean",
            "description": "Include links in the extracted text"
          },
          "preserve_formatting": {
            "default": false,
            "type": "boolean",
            "description": "Preserve basic HTML formatting"
          },
          "max_length": {
            "type": "number",
            "minimum": 1,
            "maximum": 1000000,
            "description": "Maximum length of extracted text"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "anyOf": [
          {
            "required": [
              "html"
            ]
          },
          {
            "required": [
              "url"
            ]
          }
        ]
      },
      "ExtractWithLlmRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "URL to fetch and extract from (one of url/content required)"
          },
          "content": {
            "type": "string",
            "description": "Pre-fetched text to extract from (one of url/content required)"
          },
          "prompt": {
            "type": "string",
            "description": "Natural-language extraction instruction"
          },
          "schema": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional JSON-schema for output shape (used as Ollama structured-outputs format when provider is \"ollama\")"
          },
          "provider": {
            "type": "string",
            "enum": [
              "openai",
              "anthropic",
              "ollama",
              "auto"
            ],
            "default": "auto",
            "description": "LLM provider. \"auto\" prefers a local Ollama instance where one exists; \"openai\"/\"anthropic\" use cloud models (require the matching API key on the execution side)"
          },
          "model": {
            "type": "string",
            "description": "Override the model. For ollama, pass a name returned by list_ollama_models. Defaults: openai=\"gpt-4o-mini\", anthropic=\"claude-haiku-4-5-20251001\", ollama=\"llama3.2\""
          },
          "maxTokens": {
            "type": "number",
            "default": 4096,
            "description": "Maximum output tokens"
          }
        },
        "required": [
          "prompt"
        ],
        "anyOf": [
          {
            "required": [
              "url"
            ]
          },
          {
            "required": [
              "content"
            ]
          }
        ]
      },
      "FetchUrlRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to fetch"
          },
          "headers": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "string"
            },
            "description": "Optional HTTP headers to include"
          },
          "timeout": {
            "default": 10000,
            "type": "number",
            "minimum": 1000,
            "maximum": 30000,
            "description": "Request timeout in milliseconds (1000-30000)"
          },
          "follow_redirects": {
            "default": true,
            "type": "boolean",
            "description": "Whether to follow HTTP redirects"
          },
          "user_agent": {
            "type": "string",
            "description": "Custom User-Agent header"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          }
        },
        "required": [
          "url"
        ]
      },
      "GenerateLlmsTxtRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The website URL to generate llms.txt for"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch each origin's robots.txt and skip URLs it disallows for CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED when the target URL itself is disallowed, with no credits charged; disallowed pages are left out of the analysis and counted in warnings. analysisOptions.respectRobots is an alias — either one set to false disables the gate."
          },
          "format": {
            "default": "both",
            "type": "string",
            "enum": [
              "both",
              "llms-txt",
              "llms-full-txt"
            ],
            "description": "Output format; llms-full.txt inlines up to 2000 characters of text per analyzed page"
          },
          "complianceLevel": {
            "default": "standard",
            "type": "string",
            "enum": [
              "basic",
              "standard",
              "strict"
            ],
            "description": "Accepted for compatibility; content is derived from real site data, not canned policy text"
          },
          "analysisOptions": {
            "type": "object",
            "properties": {
              "maxDepth": {
                "default": 3,
                "type": "number",
                "minimum": 1,
                "maximum": 5
              },
              "maxPages": {
                "default": 100,
                "type": "number",
                "minimum": 10,
                "maximum": 500
              },
              "respectRobots": {
                "default": true,
                "type": "boolean"
              },
              "detectAPIs": {
                "default": true,
                "type": "boolean"
              },
              "analyzeContent": {
                "default": true,
                "type": "boolean"
              },
              "checkSecurity": {
                "default": true,
                "type": "boolean"
              }
            },
            "description": "Analysis options. maxPages is clamped to 30 and maxDepth to 2 on the hosted REST API (clamps are reported in clamps_applied). respectRobots skips pages disallowed by robots.txt; detectAPIs flags /api, /docs/api, openapi, and swagger links found in fetched HTML."
          },
          "outputOptions": {
            "type": "object",
            "properties": {
              "organizationName": {
                "type": "string"
              },
              "contactEmail": {
                "type": "string",
                "format": "email",
                "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
              },
              "customGuidelines": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "customRestrictions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "includeDetailed": {
                "default": true,
                "type": "boolean"
              },
              "includeAnalysis": {
                "default": false,
                "type": "boolean"
              }
            },
            "description": "Output customization: organizationName (overrides the site title), contactEmail, customGuidelines, customRestrictions"
          }
        },
        "required": [
          "url"
        ]
      },
      "GetBatchResultsRequest": {
        "type": "object",
        "properties": {
          "batchId": {
            "type": "string",
            "minLength": 1,
            "description": "The batch_id returned by batch_scrape"
          },
          "page": {
            "default": 1,
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991,
            "description": "Page number to retrieve (1-based)"
          },
          "limit": {
            "default": 25,
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Results per page (1-100)"
          }
        },
        "required": [
          "batchId"
        ]
      },
      "ListOllamaModelsRequest": {
        "type": "object",
        "properties": {}
      },
      "LocalizationRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to analyze for localization"
          },
          "target_language": {
            "type": "string",
            "description": "Target language code (e.g., \"en\", \"fr\", \"es\")"
          },
          "target_country": {
            "type": "string",
            "description": "Target country code (e.g., \"US\", \"FR\", \"JP\")"
          },
          "detect_language": {
            "default": true,
            "type": "boolean",
            "description": "Detect the page language from its visible text. Returns an ISO 639-1 code, or \"und\" when the language cannot be determined."
          },
          "extract_hreflang": {
            "default": true,
            "type": "boolean",
            "description": "Extract hreflang alternate language tags"
          },
          "check_geo_targeting": {
            "default": false,
            "type": "boolean",
            "description": "Check for geo-targeting metadata"
          },
          "timeout": {
            "default": 10000,
            "type": "number",
            "minimum": 1000,
            "maximum": 30000,
            "description": "Request timeout in milliseconds (1000-30000)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          }
        },
        "required": [
          "url"
        ]
      },
      "MapSiteRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The starting URL to map"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch each origin's robots.txt and skip URLs it disallows for CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED when the start URL itself is disallowed, with no credits charged; a disallowed sitemap.xml falls back to the crawl, and pages disallowed mid-crawl are skipped and counted in warnings."
          },
          "max_depth": {
            "default": 2,
            "type": "number",
            "minimum": 1,
            "maximum": 5,
            "description": "Maximum crawl depth for the fallback crawl (1-5); unused when a sitemap is found"
          },
          "include_external": {
            "default": false,
            "type": "boolean",
            "description": "Include external links in per-page link lists (crawl mode only; external pages are never crawled)"
          },
          "timeout": {
            "default": 15000,
            "type": "number",
            "minimum": 1000,
            "maximum": 30000,
            "description": "Overall crawl budget in milliseconds (1000-30000; capped at ~18000 to fit the serverless limit)"
          }
        },
        "required": [
          "url"
        ]
      },
      "ProcessDocumentRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL of the document to process (max 25MB)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch the origin's robots.txt and refuse the document if it disallows CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED, and no credits are charged."
          },
          "document_type": {
            "default": "auto",
            "type": "string",
            "enum": [
              "pdf",
              "docx",
              "xlsx",
              "csv",
              "txt",
              "auto"
            ],
            "description": "Document type; auto-detected from Content-Type, URL extension, then content sniffing"
          },
          "extract_text": {
            "default": true,
            "type": "boolean",
            "description": "Extract text content (capped at 200000 characters; PDFs read up to 200 pages)"
          },
          "extract_metadata": {
            "default": true,
            "type": "boolean",
            "description": "Extract document metadata (PDF info dictionary; HTML title/description/author)"
          },
          "extract_tables": {
            "default": false,
            "type": "boolean",
            "description": "Extract tables (PDF via ruled-grid detection, CSV via parsing; not available for HTML). Up to 20 tables of 1000 rows; a table ruled only horizontally keeps its rows but reports one column"
          },
          "extract_images": {
            "default": false,
            "type": "boolean",
            "description": "Not available on the hosted REST API — returns images: null with a note; use the CrawlForge MCP server for image extraction"
          },
          "timeout": {
            "default": 30000,
            "type": "number",
            "minimum": 1000,
            "maximum": 60000,
            "description": "Fetch timeout in milliseconds (1000-60000; capped at 20000 to fit the serverless limit)"
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "required": [
          "url"
        ]
      },
      "ReadResultRequest": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string",
            "minLength": 1,
            "description": "The result_handle returned by a tool (res_ followed by a UUID)"
          },
          "operation": {
            "type": "string",
            "enum": [
              "slice",
              "search",
              "lines",
              "json_path"
            ],
            "description": "slice: a character range. search: every case-insensitive literal occurrence of query, each with its offset and 200 chars of context on each side. lines: a page of lines. json_path: one field of the stored JSON."
          },
          "offset": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "slice: first character index. lines: first line index."
          },
          "length": {
            "type": "integer",
            "minimum": 1,
            "maximum": 200000,
            "description": "slice: characters to return (default 10000, max 200000). lines: lines to return (default 200, max 5000). Both are also capped at max_inline_chars."
          },
          "query": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "search: the literal text to find (1-500 chars); never interpreted as a pattern"
          },
          "max_matches": {
            "default": 20,
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "search: matches to return (1-100); total_matches still counts every occurrence"
          },
          "path": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "json_path: dotted path into the stored result, e.g. \"formats.links[0].href\" or \"formats.links.0.href\". Keys and indexes only — no wildcards. The subject is the stored payload, or the parsed body when the stored text is itself JSON. A path that does not resolve is a 400 naming the keys available where it stopped."
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Cap on the characters this call returns (1000-10000000)"
          }
        },
        "required": [
          "handle",
          "operation"
        ]
      },
      "RedditSearchRequest": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "description": "Keyword search. Posts: matches title+selftext; comments: matches body. Supports \"quoted phrases\", OR, -exclusion"
          },
          "subreddit": {
            "type": "string",
            "minLength": 1,
            "description": "Limit to one subreddit (with or without the r/ prefix)"
          },
          "author": {
            "type": "string",
            "minLength": 1,
            "description": "Limit to one author (with or without the u/ prefix)"
          },
          "mode": {
            "default": "posts",
            "type": "string",
            "enum": [
              "posts",
              "comments",
              "thread"
            ],
            "description": "What to search: posts (default), comments, or thread (full comment tree — requires link_id)"
          },
          "link_id": {
            "type": "string",
            "minLength": 1,
            "description": "Post ID (e.g. '1twm1zh' or 't3_1twm1zh') — required for thread mode, optional filter for comments mode"
          },
          "after": {
            "type": "string",
            "minLength": 1,
            "description": "Only content posted after this date — ISO 8601, epoch seconds, or an offset like '7d'"
          },
          "before": {
            "type": "string",
            "minLength": 1,
            "description": "Only content posted before this date — same formats as after"
          },
          "limit": {
            "default": 25,
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "Max results (1-100; thread mode: max comments returned)"
          },
          "sort": {
            "default": "desc",
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ],
            "description": "Sort by post date (desc = newest first)"
          },
          "source": {
            "default": "auto",
            "type": "string",
            "enum": [
              "auto",
              "arctic_shift",
              "pullpush",
              "web_discovery"
            ],
            "description": "Force a specific backend: auto, arctic_shift, web_discovery (unscoped keyword searches only — posts or comments), or pullpush (no longer serves automated clients; kept for when it returns)"
          }
        }
      },
      "ScrapeRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The URL to scrape"
          },
          "formats": {
            "default": [
              "markdown"
            ],
            "minItems": 1,
            "type": "array",
            "items": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "markdown",
                    "html",
                    "rawHtml",
                    "text",
                    "links",
                    "metadata",
                    "screenshot",
                    "json-schema"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "const": "highlights"
                    },
                    "query": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500
                    },
                    "max_highlights": {
                      "default": 10,
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 50
                    },
                    "mode": {
                      "default": "extractive",
                      "type": "string",
                      "enum": [
                        "extractive",
                        "model"
                      ]
                    }
                  },
                  "required": [
                    "type",
                    "query"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "const": "question"
                    },
                    "question": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 500
                    },
                    "mode": {
                      "default": "extractive",
                      "type": "string",
                      "enum": [
                        "extractive",
                        "model"
                      ]
                    }
                  },
                  "required": [
                    "type",
                    "question"
                  ]
                }
              ]
            },
            "description": "Output formats: markdown, html, rawHtml, text, links, metadata, plus two query-scoped object formats. { type: 'highlights', query, max_highlights (1-50, default 10), mode } returns the sentences and code blocks matching the query, verbatim, each with an offset and length into the markdown format of the same call (table rows too on the MCP server; this API flattens tables to text). { type: 'question', question, mode } returns an answer assembled from the best-matching passages, with its evidence. mode defaults to 'extractive'; mode 'model' needs an LLM and is rejected here, as are the 'screenshot' and 'json-schema' formats, which require the CrawlForge MCP server (browser/LLM)."
          },
          "onlyMainContent": {
            "default": true,
            "type": "boolean",
            "description": "Strip navigation, headers, and footers, returning only the main content"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          },
          "escalate": {
            "default": false,
            "type": "boolean",
            "description": "Retry a blocked page in a stealth browser, in this same call. The plain fetch always runs first; only if it meets a bot wall, an empty shell or an error placeholder does the stealth render run, and the requested formats are then derived from the rendered HTML. The response carries escalated: true|false whenever this is set, plus stealth: { engine, vendor_detected } when the render ran. It costs the escalation add-on only when it ran. The stage needs the CrawlForge execution backend: where that is not configured it returns 503 TOOL_NOT_AVAILABLE and charges nothing. robots.txt is respected on the escalated path too, matched against the same CrawlForge product token."
          },
          "escalate_engine": {
            "default": "playwright",
            "type": "string",
            "enum": [
              "playwright",
              "camoufox"
            ],
            "description": "Browser engine for the escalated render (camoufox only where installed on the backend). Ignored when no escalation runs."
          },
          "max_inline_chars": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 10000000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "required": [
          "url"
        ]
      },
      "ScrapeStructuredRequest": {
        "type": "object",
        "properties": {
          "html": {
            "type": "string",
            "minLength": 1,
            "description": "HTML content to extract data from"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to fetch and extract data from"
          },
          "selectors": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "string"
            },
            "description": "Key-value pairs of field names and CSS selectors",
            "minProperties": 1
          },
          "base_url": {
            "type": "string",
            "format": "uri",
            "description": "Base URL for resolving relative URLs"
          },
          "multiple": {
            "default": false,
            "type": "boolean",
            "description": "Extract multiple items vs single item"
          },
          "clean_text": {
            "default": true,
            "type": "boolean",
            "description": "Clean extracted text by trimming whitespace"
          },
          "include_attributes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "HTML attributes to extract (e.g., href, src, alt)"
          },
          "max_items": {
            "default": 100,
            "type": "number",
            "minimum": 1,
            "maximum": 1000,
            "description": "Maximum number of items to extract (for multiple mode)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean"
          }
        },
        "required": [
          "selectors"
        ],
        "anyOf": [
          {
            "required": [
              "html"
            ]
          },
          {
            "required": [
              "url"
            ]
          }
        ]
      },
      "ScrapeTemplateRequest": {
        "type": "object",
        "properties": {
          "template": {
            "type": "string",
            "description": "A supported template id, \"auto\" to pick one from the url, or \"list\" to discover them."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to scrape. Required unless template === \"list\", or params drive a list connector. Always required for template \"auto\"."
          },
          "params": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "Drives a list connector in place of a url — { \"company\": \"stripe\" } for the job boards, { \"store\": \"www.allbirds.com\", \"collection\": \"mens\" } for shopify-collection, { \"vin\": \"5UXWX7C5*BA\" } for nhtsa-vin. Each connector names its own parameters in the description that template \"list\" returns. A missing or invalid parameter is a 400 naming it, and no credits are charged."
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch the origin's robots.txt and refuse the URL if it disallows CrawlForge. Checked against the URL actually fetched, which is the API endpoint rather than the URL or params you pass wherever a template resolves one. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED, and no credits are charged."
          },
          "timeout": {
            "default": 15000,
            "type": "integer",
            "minimum": 5000,
            "maximum": 60000,
            "description": "Request timeout in milliseconds (5000–60000)."
          }
        },
        "required": [
          "template"
        ],
        "allOf": [
          {
            "anyOf": [
              {
                "properties": {
                  "template": {
                    "const": "list"
                  }
                }
              },
              {
                "required": [
                  "url"
                ]
              },
              {
                "required": [
                  "params"
                ]
              }
            ]
          },
          {
            "anyOf": [
              {
                "properties": {
                  "template": {
                    "not": {
                      "const": "auto"
                    }
                  }
                }
              },
              {
                "required": [
                  "url"
                ]
              }
            ]
          }
        ]
      },
      "ScrapeWithActionsRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "The URL to scrape"
          },
          "actions": {
            "type": "array",
            "description": "Browser actions to perform before scraping (1-20)",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "wait",
                    "click",
                    "type",
                    "press",
                    "scroll",
                    "screenshot",
                    "executeJavaScript",
                    "select",
                    "hover",
                    "navigate"
                  ],
                  "description": "Action to perform"
                },
                "selector": {
                  "type": "string",
                  "description": "CSS selector the action targets"
                },
                "text": {
                  "type": "string",
                  "description": "type: text to enter"
                },
                "key": {
                  "type": "string",
                  "description": "press: key to press"
                },
                "value": {
                  "type": "string",
                  "description": "select: one option to choose in the <select> named by selector. Matches an option by its value or by its visible label."
                },
                "values": {
                  "type": "array",
                  "description": "select: several options to choose, same matching rule as value. Supply value or values, not neither."
                },
                "url": {
                  "type": "string",
                  "description": "navigate: URL to load mid-chain, in the same browser session. Passes the same robots.txt and SSRF checks as the top-level url."
                },
                "waitUntil": {
                  "type": "string",
                  "default": "domcontentloaded",
                  "enum": [
                    "load",
                    "domcontentloaded",
                    "networkidle",
                    "commit"
                  ],
                  "description": "navigate: how long to wait for the new page before continuing"
                },
                "script": {
                  "type": "string",
                  "description": "executeJavaScript: script to run"
                },
                "timeout": {
                  "type": "number",
                  "default": 10000,
                  "description": "Per-action timeout in ms, distinct from browserOptions.timeout which budgets the whole chain"
                },
                "description": {
                  "type": "string",
                  "description": "Human-readable label for this action"
                },
                "continueOnError": {
                  "type": "boolean",
                  "description": "Keep going if this action fails"
                },
                "retries": {
                  "type": "number",
                  "description": "Retry attempts for this action (0-5)"
                },
                "captureAfter": {
                  "type": "boolean",
                  "description": "Capture page content after this action"
                },
                "duration": {
                  "type": "number",
                  "description": "wait: milliseconds to wait (0-30000)"
                },
                "condition": {
                  "type": "string",
                  "enum": [
                    "visible",
                    "hidden",
                    "enabled",
                    "disabled",
                    "stable"
                  ],
                  "description": "wait: condition on selector"
                },
                "button": {
                  "type": "string",
                  "enum": [
                    "left",
                    "right",
                    "middle"
                  ],
                  "description": "click: mouse button"
                },
                "clickCount": {
                  "type": "number",
                  "description": "click: number of clicks (1-3)"
                },
                "delay": {
                  "type": "number",
                  "description": "click/type: delay in ms (0-1000)"
                },
                "force": {
                  "type": "boolean",
                  "description": "click/hover: bypass actionability checks"
                },
                "position": {
                  "type": "object",
                  "properties": {
                    "x": {
                      "type": "number"
                    },
                    "y": {
                      "type": "number"
                    }
                  },
                  "description": "click/hover: relative position { x, y }"
                },
                "clear": {
                  "type": "boolean",
                  "description": "type: clear field before typing"
                },
                "modifiers": {
                  "type": "array",
                  "description": "press: modifier keys (Alt, Control, Meta, Shift)"
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "up",
                    "down",
                    "left",
                    "right"
                  ],
                  "description": "scroll: direction"
                },
                "distance": {
                  "type": "number",
                  "description": "scroll: pixels to scroll"
                },
                "smooth": {
                  "type": "boolean",
                  "description": "scroll: smooth scrolling"
                },
                "toElement": {
                  "type": "string",
                  "description": "scroll: selector to scroll to"
                },
                "x": {
                  "type": "number",
                  "description": "scroll: absolute X coordinate (window.scrollTo; with y, takes precedence over direction/distance)"
                },
                "y": {
                  "type": "number",
                  "description": "scroll: absolute Y coordinate (window.scrollTo; with x, takes precedence over direction/distance)"
                },
                "fullPage": {
                  "type": "boolean",
                  "description": "screenshot: capture full page"
                },
                "quality": {
                  "type": "number",
                  "description": "screenshot: jpeg quality (0-100)"
                },
                "format": {
                  "type": "string",
                  "enum": [
                    "png",
                    "jpeg"
                  ],
                  "description": "screenshot: image format"
                },
                "args": {
                  "type": "array",
                  "description": "executeJavaScript: arguments passed to the script"
                },
                "returnResult": {
                  "type": "boolean",
                  "description": "executeJavaScript: return the script result"
                }
              }
            }
          },
          "formats": {
            "type": "array",
            "default": [
              "json"
            ],
            "description": "Output formats: markdown, html, json, text, screenshots"
          },
          "captureIntermediateStates": {
            "type": "boolean",
            "default": false,
            "description": "Capture page state after each action"
          },
          "captureScreenshots": {
            "type": "boolean",
            "default": true,
            "description": "Take screenshots during action execution"
          },
          "formAutoFill": {
            "type": "object",
            "description": "Form auto-fill configuration",
            "properties": {
              "fields": {
                "type": "array",
                "description": "Fields to fill: { selector, value, type: text|select|checkbox|radio|file, waitAfter }"
              },
              "submitSelector": {
                "type": "string",
                "description": "Selector of the submit control"
              },
              "waitAfterSubmit": {
                "type": "number",
                "default": 2000,
                "description": "Wait after submit in ms (0-30000)"
              }
            }
          },
          "browserOptions": {
            "type": "object",
            "description": "Browser configuration options",
            "properties": {
              "headless": {
                "type": "boolean",
                "default": true
              },
              "userAgent": {
                "type": "string"
              },
              "viewportWidth": {
                "type": "number",
                "default": 1280,
                "description": "800-1920"
              },
              "viewportHeight": {
                "type": "number",
                "default": 720,
                "description": "600-1080"
              },
              "timeout": {
                "type": "number",
                "default": 30000,
                "description": "Browser timeout in ms (10000-120000)"
              },
              "stealth": {
                "type": "boolean",
                "default": false,
                "description": "Run the action chain in the stealth Chromium engine at its medium profile instead of the standard browser pool. The boolean is the only knob — level, fingerprint randomization and engine are not settable from here. Slower to start; it renders JavaScript, it does not solve challenges."
              }
            }
          },
          "respect_robots": {
            "type": "boolean",
            "default": true,
            "description": "Respect the target site's robots.txt. Omitted, the compliant default (true) applies: a URL disallowed for CrawlForge is refused before the browser opens and no credits are charged, and every navigate action is checked the same way. Setting this to false is honoured, returns a warning in the response, and is recorded against your API key."
          },
          "extractionOptions": {
            "type": "object",
            "description": "Content extraction options",
            "properties": {
              "selectors": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Key-value pairs of data to extract using CSS selectors"
              },
              "includeMetadata": {
                "type": "boolean",
                "default": true
              },
              "includeLinks": {
                "type": "boolean",
                "default": true
              },
              "includeImages": {
                "type": "boolean",
                "default": true
              }
            }
          },
          "continueOnActionError": {
            "type": "boolean",
            "default": false,
            "description": "Continue executing actions if one fails"
          },
          "maxRetries": {
            "type": "number",
            "default": 1,
            "description": "Maximum retry attempts on failure (0-3)"
          },
          "screenshotOnError": {
            "type": "boolean",
            "default": true,
            "description": "Capture screenshot when an error occurs"
          },
          "max_inline_chars": {
            "type": "number",
            "default": 40000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "default": false,
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "required": [
          "url",
          "actions"
        ]
      },
      "SearchWebRequest": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "minLength": 1,
            "description": "Search query. Provide exactly one of query or queries."
          },
          "queries": {
            "minItems": 1,
            "maxItems": 10,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "description": "Between 1 and 10 search queries to run in one call, as an alternative to query. Each runs the same search query does, and the payloads come back as results_by_query: a list in the order the queries were sent, each entry carrying its own query plus that search — or query and error when that one search failed. queries echoes the input and count is its length. Charged per query that returned results: the projection is the published price times every query sent, and the actual charge falls below it by every query that failed. A batch in which none succeeded still returns 200, with an error entry for each, and costs nothing. Provide exactly one of query or queries."
          },
          "limit": {
            "default": 10,
            "type": "number",
            "minimum": 1,
            "maximum": 100,
            "description": "Number of results (1-100)"
          },
          "offset": {
            "default": 0,
            "type": "number",
            "minimum": 0,
            "description": "Results offset for pagination"
          },
          "lang": {
            "type": "string",
            "description": "Language code (e.g., \"en\")"
          },
          "site": {
            "type": "string",
            "description": "Limit search to specific site"
          },
          "safe_search": {
            "type": "boolean",
            "description": "Enable safe search"
          },
          "time_range": {
            "type": "string",
            "enum": [
              "day",
              "week",
              "month",
              "year",
              "all"
            ],
            "description": "Time range: day, week, month, year, all"
          },
          "file_type": {
            "type": "string",
            "description": "Filter by file type (e.g., \"pdf\")"
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        },
        "oneOf": [
          {
            "required": [
              "query"
            ]
          },
          {
            "required": [
              "queries"
            ]
          }
        ]
      },
      "SerpRankRequest": {
        "type": "object",
        "properties": {
          "keyword": {
            "type": "string",
            "minLength": 1,
            "description": "The search query to check ranking for"
          },
          "target": {
            "type": "string",
            "minLength": 1,
            "description": "Domain or URL to locate in the results (e.g. 'example.com')"
          },
          "depth": {
            "default": 100,
            "type": "number",
            "minimum": 10,
            "maximum": 200,
            "description": "How many results to scan (10-200; 100 = 1 page of cost)"
          },
          "device": {
            "default": "desktop",
            "type": "string",
            "enum": [
              "desktop",
              "mobile"
            ],
            "description": "Device to emulate: desktop or mobile"
          },
          "language_code": {
            "default": "en",
            "type": "string",
            "description": "Language code (e.g. 'en')"
          },
          "location_name": {
            "default": "United States",
            "type": "string",
            "description": "Location, e.g. 'United States' or 'London,England,United Kingdom'"
          },
          "location_code": {
            "type": "number",
            "description": "Numeric DataForSEO location code (overrides location_name)"
          }
        },
        "required": [
          "keyword",
          "target"
        ]
      },
      "StealthModeRequest": {
        "type": "object",
        "properties": {
          "operation": {
            "type": "string",
            "enum": [
              "scrape",
              "configure",
              "enable",
              "disable",
              "create_context",
              "create_page",
              "get_stats",
              "cleanup"
            ],
            "default": "configure",
            "description": "Stealth operation to perform. \"scrape\" creates a context, navigates to url, returns the requested formats and tears the context down, all in one billed call."
          },
          "stealthConfig": {
            "type": "object",
            "description": "Stealth browser configuration",
            "properties": {
              "level": {
                "type": "string",
                "enum": [
                  "basic",
                  "medium",
                  "advanced"
                ],
                "default": "medium"
              },
              "randomizeFingerprint": {
                "type": "boolean",
                "default": true
              },
              "hideWebDriver": {
                "type": "boolean",
                "default": true
              },
              "blockWebRTC": {
                "type": "boolean",
                "default": true
              },
              "simulateHumanBehavior": {
                "type": "boolean",
                "default": true
              },
              "customUserAgent": {
                "type": "string"
              },
              "locale": {
                "type": "string",
                "default": "en-US"
              },
              "timezone": {
                "type": "string"
              }
            }
          },
          "engine": {
            "type": "string",
            "enum": [
              "playwright",
              "camoufox"
            ],
            "default": "playwright",
            "description": "Browser engine (camoufox only where installed on the backend)"
          },
          "contextId": {
            "type": "string",
            "description": "Browser context ID for page operations (from a create_context call)"
          },
          "urlToTest": {
            "type": "string",
            "description": "URL to navigate to when creating a page"
          },
          "url": {
            "type": "string",
            "description": "URL to scrape. Required for operation \"scrape\"; the other operations use urlToTest instead."
          },
          "formats": {
            "type": "array",
            "default": [
              "markdown"
            ],
            "description": "Formats operation \"scrape\" returns: markdown, html, text, links, metadata, screenshot. A screenshot comes back as a crawlforge://screenshot/{id} resource URI, not inline base64 — this endpoint passes that URI through without resolving it, so for now screenshot is usable only from the CrawlForge MCP server."
          },
          "wait_for": {
            "type": "number",
            "description": "Extra wait after page load, in ms (0-30000), for content that renders after DOMContentLoaded"
          },
          "verbose": {
            "type": "boolean",
            "default": false,
            "description": "Return the full generated fingerprint from create_context instead of a summary"
          },
          "respect_robots": {
            "type": "boolean",
            "default": true,
            "description": "Respect the target site's robots.txt. Omitted, the compliant default (true) applies: a URL disallowed for CrawlForge is refused before any browser is launched and no credits are charged, and every navigation is checked the same way. The rule is matched against the CrawlForge product token even though the stealth browser presents a randomized User-Agent, so a site owner's \"User-agent: CrawlForge\" directive binds stealth traffic too. Setting this to false is honoured, returns a warning in the response, and is recorded against your API key."
          },
          "max_inline_chars": {
            "type": "number",
            "default": 40000,
            "description": "Largest result returned inline, in characters of its JSON (1000-10000000; env CRAWLFORGE_MAX_INLINE_CHARS sets the default). Over it, the result is stored for 1 hour and the response carries a preview, a result_handle, total_chars and truncated: true; read the rest with read_result (1 credit)."
          },
          "redact_pii": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "entities": {
                    "maxItems": 16,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "replace_style": {
                    "type": "string",
                    "enum": [
                      "tag",
                      "mask",
                      "remove"
                    ]
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "fast",
                      "model"
                    ]
                  }
                }
              }
            ],
            "default": false,
            "description": "Remove personal data from the text this call returns, before it is stored or sent back. true is shorthand for { mode: \"fast\" }: every entity, tagged. As an object: entities (any of EMAIL, PHONE, FINANCIAL, SECRET; omitted or empty means all four, and any other name is a 400 rather than a silent no-op), replace_style (\"tag\" → <EMAIL>, \"mask\" → [REDACTED], \"remove\" → nothing; default \"tag\") and mode (\"fast\", the default, is regex-only and costs no extra credits; \"model\" covers PERSON and LOCATION, needs an LLM and is rejected here — use the CrawlForge MCP server). The response carries redaction: { entities, count, mode } inside data, saying what was removed. Detection is deliberately conservative: a card number must pass Luhn and an IBAN mod-97, so a false positive cannot silently destroy real page content. URLs, queries and identifiers the response uses to name what was fetched are left intact, and counters derived from the text (content_length, word_count, character_count) describe the text as it was extracted, before redaction."
          }
        }
      },
      "SummarizeContentRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL to fetch and summarize content from"
          },
          "content": {
            "type": "string",
            "description": "Direct content to summarize (if URL not provided)"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "Fetch the origin's robots.txt and refuse a url it disallows for CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED, and no credits are charged. Ignored when summarizing supplied content."
          },
          "max_sentences": {
            "default": 5,
            "type": "number",
            "minimum": 1,
            "maximum": 20,
            "description": "Maximum sentences in summary (1-20)"
          },
          "summary_type": {
            "default": "extractive",
            "type": "string",
            "enum": [
              "extractive",
              "key_points",
              "brief"
            ],
            "description": "Type of summary to generate"
          },
          "include_metadata": {
            "default": false,
            "type": "boolean",
            "description": "Include page metadata in response"
          },
          "timeout": {
            "default": 10000,
            "type": "number",
            "minimum": 1000,
            "maximum": 30000,
            "description": "Request timeout in milliseconds (1000-30000)"
          }
        },
        "anyOf": [
          {
            "required": [
              "url"
            ]
          },
          {
            "required": [
              "content"
            ]
          }
        ]
      },
      "TrackChangesRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "URL of the webpage to track"
          },
          "operation": {
            "default": "compare",
            "type": "string",
            "enum": [
              "create_baseline",
              "compare",
              "monitor"
            ],
            "description": "create_baseline captures and stores the current page text (kept 90 days). compare fetches the page again and diffs it against the stored baseline. monitor creates a hosted monitor that fetches and compares the page on a schedule; creating it costs nothing, and each scheduled check bills 3 credits per target that was fetched and compared. Manage monitors at /api/v1/monitors or in the dashboard."
          },
          "selector": {
            "type": "string",
            "description": "CSS selector to scope tracking to part of the page (e.g. \".pricing-table\"). Baselines are stored per (url, selector) pair; 422 if the selector matches nothing."
          },
          "update_baseline": {
            "default": false,
            "type": "boolean",
            "description": "compare only: overwrite the stored baseline with the freshly fetched content after diffing"
          },
          "respect_robots": {
            "default": true,
            "type": "boolean",
            "description": "create_baseline and compare: fetch the origin's robots.txt and refuse the URL if it disallows CrawlForge. A missing or unreachable robots.txt is treated as no restrictions. Returns 403 ROBOTS_DISALLOWED, and no credits are charged. Hosted monitors always respect robots.txt; a disallowed target is reported as a page error."
          },
          "schedule": {
            "type": "string",
            "description": "monitor only: five-field cron expression, evaluated in UTC, for how often the monitor checks the page (default \"0 * * * *\", hourly). Consecutive runs must be at least 5 minutes apart; 400 VALIDATION_ERROR otherwise."
          },
          "notify_emails": {
            "maxItems": 5,
            "type": "array",
            "items": {
              "type": "string",
              "format": "email",
              "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
            },
            "description": "monitor only: up to 5 addresses that receive a summary email when a check finds new, changed, blocked or errored pages"
          },
          "webhook_url": {
            "type": "string",
            "format": "uri",
            "description": "monitor only: an https endpoint that receives a signed POST (monitor.page per changed page, then monitor.check.completed) after every check. The signing secret is returned once, as webhook_secret on the created monitor."
          }
        },
        "required": [
          "url"
        ]
      }
    }
  }
}
