{
  "id": "tinyfish.fetch",
  "object": "tool",
  "name": "TinyFish Fetch",
  "summary": "Fetch up to 10 URLs as clean Markdown/HTML/JSON — free.",
  "description": "Render pages in a real browser and return clean, model-ready content for 1-10 URLs in a single call, fetched in parallel. 'format' selects markdown (default), semantic html, or a structured json document tree. Scope extraction with CSS selectors: 'include_selectors' keeps only matching regions and returns 'candidate_selectors' retry hints on a total miss rather than silently falling back to the whole page, while 'exclude_selectors' prunes boilerplate first. 'ttl' controls cache freshness — omit it for any cached copy, 0 to force a live fetch, or a maximum age in seconds. To poll a page for changes cheaply, fetch it once with 'include_etag_and_last_modified' AND 'ttl' of 0 to capture its 'etag' / 'last_modified', then replay those as 'if_none_match' / 'if_modified_since' on later calls: an unchanged page comes back as 'not_modified: true' with no body at all. Optional 'links' and 'image_links' return absolute URLs. HTML, PDF, JSON, and plain text are supported; images are not. A per-URL failure never fails the batch — it appears in 'errors[]' with a code such as target_http_error, page_not_found, timeout, or bot_blocked. No anti-bot bypass — protected pages fail with 'bot_blocked'.\n\nRaw tinyfish#/fetch endpoint: input is { body, queryParams, pathParams } as the provider documents them (https://docs.tinyfish.ai/fetch-api/reference).",
  "category": "web",
  "hosted_by": "TinyFish",
  "runtime": "partner",
  "side_effects": false,
  "status": "live",
  "credits": 0,
  "price": "$0.0000",
  "providers": [
    {
      "id": "tinyfish",
      "name": "TinyFish",
      "price": "$0.0000"
    }
  ],
  "mcp_name": "tinyfish_fetch",
  "input_schema": {
    "type": "object",
    "properties": {
      "body": {
        "type": "object",
        "properties": {
          "urls": {
            "minItems": 1,
            "maxItems": 10,
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "1-10 http(s) URLs to fetch, processed in parallel. Private IPs, localhost, and cloud metadata endpoints are rejected. A failure on one URL never fails the batch — it appears in errors[] instead."
          },
          "purpose": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Optional short statement of the task these results are for — the goal behind the request, not the request itself. TinyFish uses it as an extra ranking signal. Example: 'Compare pricing tiers across vendors for a procurement report'. Max 2000 characters."
          },
          "format": {
            "description": "Output format for each result's `text`. 'markdown' (default) is best for LLM consumption, 'html' returns cleaned semantic HTML, 'json' returns a structured document tree.",
            "type": "string",
            "enum": [
              "markdown",
              "html",
              "json"
            ]
          },
          "links": {
            "description": "Include every <a href> on the page as absolute URLs in the result's `links` array. Defaults to false.",
            "type": "boolean"
          },
          "image_links": {
            "description": "Include every <img src> on the page as absolute URLs in the result's `image_links` array. Defaults to false.",
            "type": "boolean"
          },
          "ttl": {
            "description": "Cache freshness tolerance in seconds. Omit to accept any cached entry, 0 to force a live fetch, or a positive number to accept entries younger than that many seconds.",
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "per_url_timeout_ms": {
            "description": "Per-URL wall-clock budget in milliseconds (1-110000). A URL exceeding it returns a 'timeout' entry in errors[] while the rest of the batch still completes.",
            "type": "integer",
            "minimum": 1,
            "maximum": 110000
          },
          "if_none_match": {
            "description": "An ETag saved from a prior fetch of this URL, forwarded verbatim as the If-None-Match request header. Single URL only (the batch combination is rejected upstream with a 400). An unchanged page returns not_modified: true instead of a body.",
            "type": "string",
            "minLength": 1
          },
          "if_modified_since": {
            "description": "A Last-Modified value saved from a prior fetch of this URL, forwarded verbatim as the If-Modified-Since request header. Single URL only (the batch combination is rejected upstream with a 400).",
            "type": "string",
            "minLength": 1
          },
          "include_etag_and_last_modified": {
            "description": "Return `etag` and `last_modified` on each result so they can be replayed as if_none_match / if_modified_since on a later call. Defaults to false. Fetch does not store them for you. Pair it with ttl=0: validators come from the origin, so a cached hit returns none.",
            "type": "boolean"
          },
          "include_selectors": {
            "description": "1-20 CSS selectors scoping extraction to elements matching ANY entry, concatenated in document order. Automatic boilerplate removal is bypassed. A partial miss still succeeds and reports unmatched_selectors; a total miss fails that URL with selector_not_matched plus candidate_selectors retry hints.",
            "minItems": 1,
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1000
            }
          },
          "exclude_selectors": {
            "description": "1-20 CSS selectors for elements to remove BEFORE extraction, applied before include_selectors so it also prunes inside selected regions. Entries that match nothing are a no-op.",
            "minItems": 1,
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1000
            }
          }
        },
        "required": [
          "urls"
        ],
        "additionalProperties": false
      }
    },
    "required": [
      "body"
    ],
    "additionalProperties": false
  },
  "kind": "endpoint"
}