{
  "openapi": "3.1.0",
  "info": {
    "title": "Fastcrawl API",
    "version": "0.6.0",
    "description": "Turn any URL into clean markdown, screenshots, PDFs, or JSON. Failed calls are never charged."
  },
  "servers": [
    {
      "url": "https://fastcrawl.net/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  },
  "paths": {
    "/scrape": {
      "post": {
        "summary": "URL to markdown",
        "operationId": "scrape",
        "description": "PDF URLs auto-route to the document parser (metadata.parser=pdf-inspector).",
        "requestBody": {
          "required": [
            "url"
          ],
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "formats": {
                    "type": "array",
                    "items": {
                      "enum": [
                        "markdown",
                        "html",
                        "links",
                        "json",
                        "changeTracking"
                      ]
                    },
                    "description": "Output formats. changeTracking adds metadata.change_information: {change_status: first|unchanged|added|removed|different, diff} vs your previous scrape of this URL."
                  },
                  "maxAge": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 172800,
                    "description": "Cache freshness window seconds (default 172800 = 48h; 0=always fresh). Cache hits are free."
                  },
                  "timeout": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 30,
                    "description": "Per-page timeout in seconds (5-30, default 30). Fail fast on blocking pages. Capped at 30 to bound browser-rendering cost per credit."
                  },
                  "fetchMode": {
                    "enum": [
                      "auto",
                      "http"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scraped content.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "markdown": {
                      "type": "string"
                    },
                    "html": {
                      "type": "string"
                    },
                    "links": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "json": {
                      "type": "object"
                    },
                    "metadata": {
                      "type": "object",
                      "description": "incl. change_information when formats has changeTracking; parser=pdf-inspector for PDFs"
                    },
                    "duration_ms": {
                      "type": "integer"
                    },
                    "from_cache": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    },
                    "error_code": {
                      "enum": [
                        "target_dns_error",
                        "target_unreachable",
                        "target_timeout",
                        "target_tls_error",
                        "target_redirect_loop",
                        "target_blocked",
                        "target_not_found",
                        "target_rate_limited",
                        "target_server_error",
                        "scrape_http_status",
                        "scrape_invalid_url",
                        "scrape_engine_error",
                        "scrape_antibot_error",
                        "scrape_pdf_detected",
                        "scrape_empty_content",
                        "unknown_format",
                        "invalid_request",
                        "invalid_api_key",
                        "missing_api_key",
                        "monthly_limit",
                        "daily_limit",
                        "rate_limit"
                      ]
                    },
                    "retryable": {
                      "type": "boolean",
                      "description": "true: timeout/unreachable/rate-limit/engine errors \u2014 retry helps. false: dns/404/blocked/tls/redirect-loop \u2014 do not retry."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request / unknown_format"
          },
          "401": {
            "description": "missing_api_key / invalid_api_key \u2014 body carries remediation hint + WWW-Authenticate header"
          },
          "429": {
            "description": "plan limit reached (error_code monthly_limit|daily_limit|rate_limit)"
          },
          "422": {
            "description": "engine failure (business result): error_code + retryable"
          }
        }
      }
    },
    "/crawl": {
      "post": {
        "summary": "Start a crawl",
        "operationId": "crawl"
      }
    },
    "/map": {
      "post": {
        "summary": "Discover URLs",
        "operationId": "map"
      }
    },
    "/search": {
      "post": {
        "summary": "Web search",
        "operationId": "search",
        "description": "Ranked web results. Add include_content to also get the first N results' page markdown in the same call: 1 credit for the search, 1 credit per content page (a scrape-cache hit is free), and a page that fails comes back content: null + content_error while the request still returns 200. REST only - the MCP search tool stays one credit per call.",
        "requestBody": {
          "required": [
            "query"
          ],
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Search query."
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20,
                    "description": "Max results (default 5)."
                  },
                  "include_content": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 5,
                    "description": "Also return page markdown for the first N results (0 or absent = results only; 400 above 5). The whole request - search plus fetches - has a 60s budget; on budget the unfetched rows come back content: null + content_error: budget exceeded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Results; with include_content the first N rows carry page markdown in content.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "query": {
                      "type": "string"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "snippet": {
                            "type": "string"
                          },
                          "content": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Page markdown for the rows include_content covered; null for rows past N and when the fetch failed."
                          },
                          "content_error": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why content is null (target_not_found, scrape_empty_content, target_blocked, budget exceeded, ...). null on success."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "query missing, or include_content above 5 (invalid_request)"
          },
          "401": {
            "description": "missing_api_key / invalid_api_key - body carries remediation hint + WWW-Authenticate header"
          },
          "422": {
            "description": "search failed (search backend down)"
          },
          "429": {
            "description": "plan limit (error_code monthly_limit|daily_limit|rate_limit)"
          }
        }
      }
    },
    "/extract": {
      "post": {
        "summary": "Schema extract",
        "operationId": "extract"
      }
    },
    "/screenshot": {
      "post": {
        "summary": "PNG capture",
        "operationId": "screenshot"
      }
    },
    "/pdf": {
      "post": {
        "summary": "PDF capture",
        "operationId": "pdf"
      }
    },
    "/image": {
      "post": {
        "summary": "HTML + CSS to PNG",
        "operationId": "image",
        "description": "Route POST /api/v1/image (server url + path). Markup in, hosted PNG out: returns the URL of the rendered PNG (Content-Type image/png). html is required and capped at 1MB, rejected locally before the upload. url is not accepted here - for a URL capture use /screenshot. 1 credit per image; an identical repeat within 30 minutes is a content-hash cache hit, returns cached:true and is free. Failed calls are never charged.",
        "requestBody": {
          "required": [
            "html"
          ],
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "html"
                ],
                "properties": {
                  "html": {
                    "type": "string",
                    "description": "The HTML document to render (max 1MB)."
                  },
                  "css": {
                    "type": "string",
                    "description": "Extra CSS applied to the document."
                  },
                  "viewport_width": {
                    "type": "integer",
                    "description": "Viewport width in px (default 800)."
                  },
                  "viewport_height": {
                    "type": "integer",
                    "description": "Viewport height in px (default 600)."
                  },
                  "google_fonts": {
                    "type": "string",
                    "description": "Google Fonts to load, e.g. Inter:wght@400;700."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "PNG URL, plus cached:true on a free cache hit.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Serves content-type image/png."
                    },
                    "cached": {
                      "type": "boolean",
                      "description": "true on an upstream cache hit - the repeat is not billed."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request - html missing, html over 1MB, or url passed (use /screenshot)"
          },
          "401": {
            "description": "missing_api_key / invalid_api_key - body carries remediation hint + WWW-Authenticate header"
          },
          "429": {
            "description": "plan limit (error_code monthly_limit|daily_limit|rate_limit) or upstream render capacity reached (render_capacity_reached)"
          },
          "502": {
            "description": "upstream render failed (image_render_failed)"
          }
        }
      }
    },
    "/parse": {
      "post": {
        "summary": "PDF to markdown",
        "operationId": "parse"
      }
    },
    "/email/verify": {
      "post": {
        "summary": "Verify an email address (deliverability)",
        "operationId": "verifyEmail",
        "description": "Is this address worth sending to? Syntax, gibberish, disposable and webmail domains, MX records (over DNS-over-HTTPS), then a real SMTP handshake at the mailbox (port-25 RCPT TO). status: valid = deliverable; invalid = do not send; risky = catch-all domain, so the mailbox may or may not exist; unknown = unproven (the probe IP is rate-limited by the large providers, so retry later - never treat unknown as invalid). 1 credit per address, up to 10 addresses per request; failed calls are never charged.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "A single address to verify (takes the fast single-address path)."
                  },
                  "emails": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "type": "string"
                    },
                    "description": "Up to 10 addresses in one request. Billed 1 credit each."
                  }
                },
                "anyOf": [
                  {
                    "required": [
                      "email"
                    ]
                  },
                  {
                    "required": [
                      "emails"
                    ]
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/batch/scrape": {
      "post": {
        "summary": "Batch scrape",
        "operationId": "batchScrape",
        "description": "Up to 50 URLs, 10-wide parallel, same pipeline as /scrape (formats, cache, PDF auto-route, changeTracking). Plan limits checked once up front. Failed requests are never charged.",
        "requestBody": {
          "required": [
            "urls"
          ],
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "urls"
                ],
                "properties": {
                  "urls": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "string",
                      "format": "uri"
                    }
                  },
                  "formats": {
                    "type": "array",
                    "items": {
                      "enum": [
                        "markdown",
                        "html",
                        "links",
                        "json",
                        "changeTracking"
                      ]
                    },
                    "description": "Output formats. changeTracking adds metadata.change_information: {change_status: first|unchanged|added|removed|different, diff} vs your previous scrape of this URL."
                  },
                  "maxAge": {
                    "type": "integer"
                  },
                  "timeout": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 30,
                    "description": "Per-page timeout in seconds (5-30, default 30), applied to each URL in the batch."
                  },
                  "fetchMode": {
                    "enum": [
                      "auto",
                      "http"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item results (aligned to input order) + totals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "total": {
                      "type": "integer"
                    },
                    "succeeded": {
                      "type": "integer"
                    },
                    "failed": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "success": {
                            "type": "boolean"
                          },
                          "url": {
                            "type": "string"
                          },
                          "markdown": {
                            "type": "string"
                          },
                          "html": {
                            "type": "string"
                          },
                          "links": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "json": {
                            "type": "object"
                          },
                          "metadata": {
                            "type": "object",
                            "description": "incl. change_information when formats has changeTracking; parser=pdf-inspector for PDFs"
                          },
                          "duration_ms": {
                            "type": "integer"
                          },
                          "from_cache": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": "string"
                          },
                          "error_code": {
                            "enum": [
                              "target_dns_error",
                              "target_unreachable",
                              "target_timeout",
                              "target_tls_error",
                              "target_redirect_loop",
                              "target_blocked",
                              "target_not_found",
                              "target_rate_limited",
                              "target_server_error",
                              "scrape_http_status",
                              "scrape_invalid_url",
                              "scrape_engine_error",
                              "scrape_antibot_error",
                              "scrape_pdf_detected",
                              "unknown_format",
                              "invalid_request",
                              "invalid_api_key",
                              "missing_api_key",
                              "monthly_limit",
                              "daily_limit",
                              "rate_limit"
                            ]
                          },
                          "retryable": {
                            "type": "boolean",
                            "description": "true: timeout/unreachable/rate-limit/engine errors \u2014 retry helps. false: dns/404/blocked/tls/redirect-loop \u2014 do not retry."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/monitors": {
      "get": {
        "summary": "List monitors",
        "operationId": "listMonitors"
      },
      "post": {
        "summary": "Create monitor",
        "operationId": "createMonitor"
      }
    },
    "/usage": {
      "get": {
        "summary": "Credit usage",
        "operationId": "usage"
      }
    },
    "/me": {
      "get": {
        "summary": "Current account",
        "operationId": "me"
      }
    },
    "/health": {
      "get": {
        "summary": "Liveness",
        "operationId": "health",
        "security": []
      }
    }
  }
}