> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hydrafetch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> What's new in the Hydrafetch API.

<Update label="September 2026" tags={["Brand"]}>
  ## Style guides you can paste

  `/v1/web/styleguide` now returns the design it found as code as well as data.

  * `css` is a stylesheet: an `@font-face` rule for each webfont and a `:root` block of CSS variables for colours, fonts, the type scale, radius, shadows and spacing
  * `tailwind` is the same set of variables as a Tailwind CSS v4 theme, so classes such as `bg-accent`, `font-heading` and `text-h1` pick up the site's design
  * Only plain colours, lengths, font names and https font files make it into either one, so a page cannot write anything else into your stylesheet

  Webfont files are also more accurate. When a site served an italic and an upright file for the same weight, the italic one could be returned as the regular font; the upright file now wins. Style guides saved before this change are read again the next time you ask for them.
</Update>

<Update label="September 2026" tags={["Web Scraping"]}>
  ## Endpoints that only answer a POST

  Some data never appears in a page. It sits behind a JSON or GraphQL endpoint that will not answer a `GET`, and until now the URL was the only thing you could describe.

  * `method: "POST"` and `body` send the request the endpoint expects. The body goes verbatim, and you set your own content type through `headers`, so JSON, form-encoded and anything else are all just a body and a header
  * `GET` stays the default. Every existing call behaves exactly as before
  * Bodies are capped at 64KB. A request body selects what an endpoint returns; anything larger is a file upload, which is a different thing and not one we do
  * A `POST` and a `GET` to the same URL are cached separately, and so are two `POST`s with different bodies

  Two limits worth knowing. A `POST` target is fetched, never rendered, because a browser navigation is a `GET` and rendering one would quietly ask the origin a different question than you did. And `PUT`, `PATCH` and `DELETE` are deliberately not supported: `POST` is what makes unreachable data reachable, and a narrower surface is easier to reason about for a service that takes arbitrary URLs.
</Update>

<Update label="September 2026" tags={["MCP"]}>
  ## Crawl and batch are tools now

  Last time these were left off, on the grounds that they are jobs and a tool call is not. That was the wrong call for an agent that is perfectly happy to come back later.

  * `crawl` and `batch` start the job and hand back an id; `crawl_status` and `batch_status` read it
  * `logo` joins them, so an agent can fetch a company's mark without building a URL itself
  * `screenshot` now takes the same sizing and timing options as the REST call
  * Thirteen tools, all under the existing `web:scrape` scope, so a grant you have already approved needs no re-authorising

  Costs are unchanged: a tool costs exactly what the equivalent REST call costs.
</Update>

<Update label="September 2026" tags={["Platform"]}>
  ## Bring your team in

  A workspace was effectively one person. It is now a team, with the awkward cases handled rather than left to support.

  * Invite people, set their role, and remove them. A removed member's seat comes back to the plan immediately
  * Sign up with an email at a company that already has a workspace and you are not turned away: you ask to join, and an owner approves or declines it from the team page
  * An invitation now survives sign-in and sign-up, so following an invite link and then creating an account lands you in the right workspace instead of a new empty one
  * One workspace per company domain, enforced everywhere a workspace can be created

  We never show one member another's email address, including the owner's.
</Update>

<Update label="September 2026" tags={["Platform"]}>
  ## Told in the product, not just by email

  Things that only ever arrived in an inbox now appear in the app, as they happen.

  * Credits running low, credits exhausted, a failed payment, a cancelled plan, your logo allowance, a publishable key being used off-domain, and join requests waiting on you
  * They arrive live. No reload, no polling
  * Email is now something you choose per category in settings, split into account alerts and product updates, so you can turn off the newsletter without also turning off "your payment failed"

  Account alerts are the ones we would rather you did not miss, which is exactly why they are in the product now as well as your inbox.
</Update>

<Update label="August 2026" tags={["Brand"]}>
  ## Colours and marks you can trust

  A brand's palette is only useful if it is the brand's palette. Several cases where it was not are fixed.

  * The primary colour now comes from the company's own mark rather than from whatever colour the page happened to use most, and a palette leads with that colour instead of the page background
  * Your operating system's font is no longer reported as a company's typeface
  * A mark that cannot say what colour it is loses to one that can, and a saturated mark is classified as mono rather than by brightness alone
  * We never serve an SVG that renders blank, which is what happened when a mark was being coloured by a stylesheet we do not have
  * A domain whose certificate does not cover the bare name is retried on `www` rather than failing to resolve
  * A styleguide that cannot read one value returns the rest instead of nothing

  Logos are cached for a week now rather than a day, so a page rendering the same mark repeatedly stays fast.
</Update>

<Update label="August 2026" tags={["Brand"]}>
  ## Logos you can put straight in an `<img>` tag

  A company's logo as an image URL, with no request to make first and no credits spent.

  ```html theme={null}
  <img src="https://img.hydrafetch.com/logo/stripe.com?token=pk_your_publishable_key" alt="Stripe" />
  ```

  * **Publishable keys** (`hf_pk_`) are safe to commit and safe to ship in client-side markup. They serve logos and nothing else, so a key lifted from your page source cannot scrape on your account
  * **Logo serves never spend credits.** They have their own per-cycle allowance, so a page rendering forty logos costs forty serves and zero credits
  * Lock a key to your own domains if you want, and pick the size, theme and shape per URL
  * Served from the edge, so the second visitor gets it immediately

  There are drop-in components for React and a browser client if you would rather not build the URL yourself.
</Update>

<Update label="August 2026" tags={["Web Scraping"]}>
  ## PDFs come back as Markdown

  Point `scrape` at a PDF and you get Markdown, the same as any page. No separate endpoint, no extra credits, one scrape.

  * Headings stay headings and tables stay tables, instead of collapsing into a flat run of text in reading order
  * Works on anything with a text layer, including exports from Word, LaTeX and design tools
  * Previously a PDF could come back carrying page markup rather than clean Markdown. It no longer does

  <Warning>
    **Scanned PDFs are not supported.** A scan is a picture of a document with no text to read, so you get empty `markdown`, `complete: false`, `confidence: 0` and a `warning` saying exactly that. Retrying will not change it.
  </Warning>
</Update>

<Update label="August 2026" tags={["Web Extraction"]}>
  ## A publication date only when the page has one

  `metadata.publishedTime` is now read from what the page actually declares, and is `null` when it declares nothing.

  * A copyright year in a footer is no longer treated as a publication date. An undated pricing page used to come back "published 1 January" of whatever year the footer mentioned
  * The same applies to the date line at the top of the Markdown: present when the page states one, absent when it does not
  * Pages that declare a real date are unaffected

  A wrong date is worse than no date, because you cannot tell it apart from a real one, and anything sorting or filtering on recency quietly inherits the error.
</Update>

<Update label="August 2026" tags={["Search"]}>
  ## Search returns results; scraping them is opt-in

  `search` used to fetch every result's page by default, so a call that reads like one credit billed six. Scraping is now something you ask for.

  * A search costs **1 credit** and returns the ranked title, url and snippet
  * Set `scrapeResults: true` to also fetch each result, at **1 credit per page scraped**, the same as scraping that page yourself
  * `data` on each result is `null` unless you asked for the scrape

  If you were relying on the old default, add `scrapeResults: true` and the behaviour and cost are exactly what they were.

  The `engine` parameter and the `data.engine` response field are also gone. Which index answered is an implementation detail we route on for cost and availability, and pinning it stopped us falling back when an engine was unavailable.
</Update>

<Update label="August 2026" tags={["Web Scraping"]}>
  ## One URL, one response shape

  A scrape used to wait, then hand back a job id if the page ran long. That meant a second code path most callers never wrote, appearing only on the rare slow page. It is gone.

  * A single URL is answered inline, always: `/scrape`, `/markdown`, `/html`, `/raw-html`, `/links` and `/structured`
  * The synchronous budget is 90 seconds. Past it you get `504` with code `REQUEST_TIMEOUT`, naming the budget
  * The fetch keeps running in the background after a timeout, so the capture usually lands in the cache and an immediate retry is fast
  * Want a job instead? Send `async: true`. It is opt-in now, never automatic
  * Crawl and batch are unchanged: they span many URLs and can genuinely run for minutes

  Set your own client timeout below 90 seconds if you want to give up sooner.
</Update>

<Update label="August 2026" tags={["MCP"]}>
  ## Every sync endpoint is an MCP tool

  The MCP server now exposes the whole synchronous surface, and describes itself so you never have to guess what it can do.

  * New tools: `styleguide`, `screenshot` and `images`, joining `scrape`, `map`, `search`, `extract` and `brand`
  * `GET /mcp/tools` lists every tool with its description, credit cost and full JSON Schema — no authentication required, so a client can see the capabilities before connecting
  * Tools cost exactly what the equivalent REST call costs
  * The new tools sit under the existing `web:scrape` scope, so an existing grant needs no re-authorising

  Crawl and batch stay off MCP for now: they are jobs, and a tool call is not.
</Update>

<Update label="August 2026" tags={["Brand"]}>
  ## Brand and styleguide

  Answer "what is this company" and "what does it look like" from nothing but a domain, instead of scraping a homepage and guessing.

  * `GET /v1/web/brand` returns logos suited to light and dark backgrounds, the real brand colours, name, description and social profiles. Every field states whether the company declared it or we harvested it
  * `POST /v1/web/brand` resolves up to 50 domains in one call
  * `GET /v1/web/brand/search` finds brands we already hold by company name or domain, so you can turn what a user typed into a domain before resolving it. Returns enough to render a picker, never resolves a new domain, and is **not billed**
  * `GET /v1/web/styleguide` reads the design system a site actually paints, from computed styles in a real browser, so a colour defined through four layers of CSS variables comes back as the hex it resolved to. Colours arrive by role with contrast ratios, so they drop into a theme without guesswork

  A brand resolves once and is served from storage after that, so the second call for a domain is the same price and immediate.
</Update>

<Update label="August 2026" tags={["Platform"]}>
  ## Billing that matches what you got

  Several cases where a call could be charged without delivering a usable page are now not billed at all.

  * An origin's own error page is no longer recorded as a delivered scrape
  * A response with no bytes is never treated as a success, whatever status the origin returned
  * A failed render no longer discards the static capture it was meant to improve on
  * Upstream failures surface as errors rather than empty `200`s

  Failures were always meant to be free. These were the paths where that promise leaked.
</Update>

<Update label="August 2026" tags={["Web Scraping"]}>
  ## Ask for structure when you need it

  `preferStructure` keeps headings, lists and tables as real markdown. It was already on the REST API; it is now on the MCP tools too, so agents can ask for it.

  * `metadata.format` is now `metadata.structure`, reporting `markdown` or `plain`
  * `plain` is not a failure — the default extractor optimises for capturing every word and wins on listing and forum pages — but if you wanted headings, retry with `preferStructure: true`
  * Available on `scrape`, `search` and `extract` over both REST and MCP
</Update>

<Update label="August 2026" tags={["Web Scraping"]}>
  ## See whether your webhooks landed

  Every callback we attempt for a crawl or batch is now recorded and readable, so a webhook that never arrives is something you can diagnose instead of guess at.

  * `GET /v1/web/crawl/{id}/deliveries` and `GET /v1/web/batch/{id}/deliveries`
  * Each delivery reports its status, attempt count, the HTTP status your endpoint returned, and why the last attempt failed
  * A `failed` row with a 5xx means we couldn't reach you; a 4xx means we did and you rejected it
</Update>

<Update label="August 2026" tags={["Platform"]}>
  ## Errors you can branch on

  Every error now returns the same envelope with a stable, machine-readable `code`. Branch on the code — the wording of `message` may change, the code won't. Each response also carries a `requestId` to quote if you need us to look something up.
</Update>

<Update label="July 2026" tags={["Web Extraction"]}>
  ## Trustworthy extraction

  Structured extraction now returns, per field, how confident it is and the exact passage a value came from. Turn it on with `showConfidence` and `showSources`.

  * Extract across a whole site: pass a `/*` scope in `urls` and every discovered page is merged into one result.
  * New `entities` primitive returns normalized people, companies, and products found on a page.
  * Every response reports the credits it consumed and how fresh the data is.
</Update>

<Update label="July 2026" tags={["Web Scraping"]}>
  ## Dedicated format endpoints

  Prefer a single, purpose-built route? Each output now has its own endpoint, alongside the flexible `/scrape`.

  * `POST /v1/web/markdown` for clean Markdown
  * `POST /v1/web/html` for cleaned main-content HTML
  * `POST /v1/web/raw-html` for unmodified page HTML
  * `POST /v1/web/links` for a page's links
  * `POST /v1/web/structured` for the page's own structured data

  They share every option and response shape with `/scrape`, so you can switch anytime.
</Update>

<Update label="June 2026" tags={["Web Scraping"]}>
  ## Search, crawl, and media

  * **Search** the web and get ranked results, each scraped to clean data. Filter by time, domain, and country.
  * **Crawl** a whole site as one asynchronous job, with completion webhooks so you do not have to poll.
  * **Media**: capture a full-page screenshot or collect a page's images.
</Update>

<Update label="June 2026" tags={["Platform"]}>
  ## Launch

  The first public version of the Hydrafetch API.

  * `POST /v1/web/scrape` takes one URL and returns clean Markdown and structured data.
  * `POST /v1/web/map` discovers every URL on a site.
  * Credit-based billing, charged only on success, with a live balance on every response.
  * API-key authentication through the `X-API-Key` header.
</Update>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.