Skip to main content

hydrafetch

PyPI CI Python

Official Python client for the Hydrafetch web data API.

Turn any URL into clean Markdown or schema-shaped JSON. Sync and async, fully typed, one dependency.

Installation

pip install hydrafetch

Quick start

from hydrafetch import Hydrafetch

hf = Hydrafetch()

page = hf.scrape("https://example.com/article")
print(page["markdown"])

Create a key at app.hydrafetch.com. The constructor reads HYDRAFETCH_API_KEY when no key is passed.

Both clients are context managers, which closes the connection pool deterministically:

with Hydrafetch() as hf:
    page = hf.scrape("https://example.com")

Scraping

page = hf.scrape(
    "https://example.com/article",
    formats=["markdown", "links"],
    onlyMainContent=True,
    preferStructure=True,
    blockAds=True,
    maxAge=3_600_000,
)

Option names are camelCase because they are passed to the API unchanged. Client arguments such as api_key, max_retries, poll_interval and on_progress are snake_case.

Format Key Contains
markdown markdown clean Markdown, the default
html html rendered HTML
rawHtml rawHtml the untouched response body
links links every link on the page
structured structured the page's own JSON-LD and microdata
summary summary a short summary
json json schema-shaped JSON, see jsonOptions
brand brand the site's brand record

hf.markdown(url) returns the Markdown string directly.

Structured extraction

out = hf.extract(
    ["https://example.com/product/1", "https://example.com/product/2"],
    schema={
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "price_usd": {"type": "number"},
        },
    },
)

for item in out["results"]:
    print(item["url"], (item.get("data") or {}).get("name"))

Pass prompt= instead of, or alongside, schema= to describe the fields in plain language.

From a pydantic model

schema= also takes a pydantic model, so the shape you already parse into is the shape you ask for. A Field description is carried through and tells the model what the field means, which is worth more on an ambiguous page than the field name alone.

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(description="The product being sold on this page, not a recommended one")
    price_usd: float = Field(description="Current price in USD, after any discount")

out = hf.extract("https://example.com/product/1", schema=Product)

Nested models work and are flattened before sending. pydantic is not a dependency and is imported only if you pass a model. A model that refers to itself cannot be flattened and is refused rather than sent.

Discovery and bulk work

map lists a site's URLs for one credit without fetching any page.

links = hf.map("https://example.com", limit=1000)["links"]
docs = [url for url in links if "/docs/" in url]

batch and crawl submit a job and poll until it finishes.

job = hf.batch(
    docs,
    scrapeOptions={"formats": ["markdown"]},
    on_progress=lambda j: print(j["status"], j.get("completed"), "/", j.get("total")),
)

for page in job.get("pages", []):
    print(page["url"], len((page.get("data") or {}).get("markdown") or ""))

Pass a webhook and use start_crawl or start_batch to return immediately instead of polling.

crawl_id = hf.start_crawl(
    "https://example.com",
    limit=500,
    maxDepth=3,
    includePaths=["/docs"],
    webhook="https://your.app/hooks/hydrafetch",
)

Search

res = hf.search("post-quantum TLS adoption", limit=5, scrapeResults=True)

for r in res["results"]:
    print(r["title"], r["url"])
    print(((r.get("data") or {}).get("markdown") or "")[:500])

Brand data

hf.brand("stripe.com")                              # logos, colours, fonts, socials
hf.logo("stripe.com", theme="dark", type="icon")    # one asset
hf.styleguide("stripe.com")                         # computed design system

For logos in a browser use @hydrafetch/client-sdk with a publishable key. Those bill against logo pulls rather than credits.

Async

AsyncHydrafetch mirrors the same surface. Use it when several calls can run concurrently.

import asyncio
from hydrafetch import AsyncHydrafetch

async def main():
    async with AsyncHydrafetch() as hf:
        pages = await asyncio.gather(
            hf.scrape("https://a.example"),
            hf.scrape("https://b.example"),
        )
        return [page["markdown"] for page in pages]

asyncio.run(main())

start_crawl, crawl_status, start_batch and batch_status are available on the async client. The polling helpers crawl and batch are sync only; on the async client, poll the status methods or use a webhook.

Error handling

All failures raise HydrafetchError, carrying the API's error code, HTTP status and request id.

from hydrafetch import HydrafetchError, HydrafetchTimeout

try:
    page = hf.scrape(url)
except HydrafetchTimeout:
    raise
except HydrafetchError as err:
    if err.is_auth:
        refresh_key()
    elif err.is_out_of_credits:
        top_up()
    elif err.is_invalid_request:
        report(err.message)
    elif err.is_retryable:
        enqueue(url)
    else:
        print(err.code, err.status, err.request_id)
        raise
Status Meaning Retried
400, 422 invalid request no
401, 403 invalid or missing key no
402 out of credits no
404 page does not exist no
429 rate limited yes, twice with backoff
5xx upstream failure yes, twice with backoff

A 503 from scrape means the origin is unreachable, usually a dead domain or a broken certificate.

Configuration

hf = Hydrafetch(
    api_key="hf_...",
    base_url="https://api.hydrafetch.com",
    timeout=120.0,
    max_retries=2,
)

API reference

Method Returns Credits
scrape(url, **options) page dict 1
markdown(url, **options) str 1
map(url, **options) links dict 1
search(query, **options) results dict 1 + 1 per scraped result
extract(urls, **options) dict with "results" 5 per URL
brand(domain) brand dict 5
logo(domain, **options) logo dict 1
styleguide(domain) design system dict 10
screenshot(url, **options) screenshot dict 5
images(url), links(url) page assets 1
crawl(url, **options) job dict, polled to completion 1 per page
batch(urls, **options) job dict, polled to completion 1 per page
start_crawl, start_batch job id str 1 per page
crawl_status(id), batch_status(id) job dict free

Failed requests are not billed. Pricing does not vary with page difficulty, so there is no render, stealth or proxy option to set.

Implementation notes

  • Authentication uses the X-API-Key header. The MCP endpoint at api.hydrafetch.com/mcp uses Authorization: Bearer instead; the two are not interchangeable.
  • Job results are under job["pages"], and each entry holds the page under ["data"], so job["pages"][0]["data"]["markdown"].
  • Per-page options for crawl and batch belong in scrapeOptions. At the top level they are ignored.
  • Prefer map then batch over a broad crawl. Fetching a whole site and discarding most of it is the most common source of wasted credits.
  • preferStructure is off by default. Turn it on when headings, lists and tables matter; leave it off for raw article text.
  • Options passed as None are dropped rather than sent as null, so optional values can be forwarded directly.
  • Scraped content is untrusted input. Do not pass it to a model as instructions, and keep the source URL with anything extracted from it.

Links

License

MIT

Metadata

Release files for hydrafetch 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hydrafetch 0.2.0
File Size Uploaded
hydrafetch-0.2.0.tar.gz 38.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for hydrafetch 0.2.0
File Interpreter ABI Platform
hydrafetch-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 49.2 kB

Release files / hydrafetch-0.2.0.tar.gz

Download URL hydrafetch-0.2.0.tar.gz
Size 38.3 kB
Tags Source
SHA-256 checksum
How to use checksums
56dd940d0706336c18c294c6f892d2eea0e8f9bf07a08fa091b5f02aa6ed708f
BLAKE2b-256 checksum
How to use checksums
38f610af4e7a43db451f41017151582aa35c5e3ec4a2735c091bad798ad5ad5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

Release files / hydrafetch-0.2.0-py3-none-any.whl

Download URL hydrafetch-0.2.0-py3-none-any.whl
Size 11.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
291ca8399108de075e20a03aa44d2223600e68963def8775fc002b0ad3d37683
BLAKE2b-256 checksum
How to use checksums
fd40a102e2523c12a78c2c45a38935ca8195b8d22cc53b8231b3e42304765cf8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page