- readfetch
- docs
API reference
One header pays for the call
Every endpoint is a POST to https://readfetch.com with a JSON body. No account, no API key: the first call comes back 402 with the price, you sign it, and the retry runs. Here is the whole contract — payment, errors, billing and the endpoint table.
Base URL
One host, one content type, no auth header.
Every call is POST https://readfetch.com/v1/… with content-type: application/json. The body is JSON and so is the response, including for origin 4xx and 5xx on /v1/fetch. A request body over 1 MiB is rejected before any work starts.
Discovery documents — /openapi.json, /llms.txt, /SKILL.md — are plain GETs and cost nothing. /v1/probe is the only free POST.
Payment
x402: the payment is the credential.
Call the endpoint without payment. It answers 402 with the quote, both in the body and in a base64 PAYMENT-REQUIRED header:
{
"error": "payment required",
"price_usd": "…",
"pay_to": "0x26C2308aDC8fe5FFE6d4183588DD2DD22ed86071",
"network": "…",
"asset": "USDC",
"accepts": [
{
"scheme": "exact",
"network": "…",
"amount": "…",
"maxAmountRequired": "…",
"asset": "…",
"payTo": "0x26C2308aDC8fe5FFE6d4183588DD2DD22ed86071",
"resource": "https://readfetch.com/v1/text",
"mimeType": "application/json"
}
]
}
Only pay_to is fixed here. amount, asset and network come from the quote for that call and that deployment — read them, do not hardcode them. amount is a string of atomic USDC units (six decimals), never a float.
Sign an EIP-3009 TransferWithAuthorization for exactly accepts[0].amount, base64 the x402 payload, and resend the identical call with PAYMENT-SIGNATURE. The retry carries both headers:
curl -s https://readfetch.com/v1/text \
-H 'content-type: application/json' \
-H "PAYMENT-SIGNATURE: $(cat payment.b64)" \
-d '{"url":"https://example.com"}'
A delivered response carries PAYMENT-RESPONSE (the settlement, with its transaction) and RECEIPT (our signed receipt — keep it, a claim needs it). A nonce can be used once: a replayed payment gets 402 payment already used, so sign a new authorization.
In test mode any non-empty x-payment-proof header passes and nothing settles. That is the mode to build against.
x402 settlement is live on eip155:8453: sign a USDC transfer and retry with PAYMENT-SIGNATURE.
Errors
Every failure is a body you can branch on.
| Status | Body | What to do |
|---|---|---|
| 402 | { "error": "…", "accepts": [ … ] } | Sign accepts[0] and resend with PAYMENT-SIGNATURE. |
| 413 | "response exceeds N bytes" | Lower max_bytes, or read a section instead of the page. |
| 422 | "error", "hint" | Validation refused the request — a private address, a missing field. Nothing ran. |
| 429 | "error" + Retry-After | Wait the advertised time, then retry the same call. Nothing settled. |
| 502 | "upstream fetch failed" | Retry once; if it repeats, /v1/probe to see what the origin answers. |
| 504 | "upstream request timed out" | The budget ran out. Give a longer timeout_s or ask for less. |
| 200 | billed: false + hint | Not charged. The hint names the next call to try. |
A wall, an empty extraction or an unsupported type is a 200 with billed: false, not an error: the origin answered, so the answer is a description of what came back.
Billing
Settled after the work, only on delivery.
The handler runs first and the payment settles after it. billed: false means the rail was never touched — an origin 4xx or 5xx with no content, a binary body, a bot wall, an empty extraction after the browser attempt, or a map with zero URLs.
There is no free daily tier on this instance. Every published endpoint except /v1/probe answers 402 first and is charged on delivery.
Each paid response carries content_sha256 and a provenance block: final URL, origin status, bytes, renderer, parser, extracted characters and whether the text was truncated. That is what makes a wrong answer refutable without trusting us.
Claims
A credit, not a refund — the rail cannot refund.
If a paid call delivered nothing, POST /v1/claims issues a single-use credit when the defect is provable from our own log. The signature is EIP-191 over sha256(receipt || rid), which proves you hold the payer key rather than a leaked receipt.
{
"receipt": "<RECEIPT header value>",
"reason": "empty",
"address": "0xpayer",
"signature": "0x…",
"note": "optional, 500 characters"
}
empty and truncated are decided automatically from the record. wrong_content needs a human. A rejection never consumes the receipt: 404 unknown, 409 already claimed, 422 not corroborated, 429 cap reached.
The answer is {"credit": "…", "amount_atomic": "…", "expires_at": "…"}. Present it on the retry as X-CREDIT; it is bound to the payer address, single use, expires after 30 days, and covers one call. PAYMENT-SIGNATURE and X-CREDIT are mutually exclusive on one request. Claims are capped per address per day.
Limits
What is enforced, not what is hoped.
- Request body: 1 MiB of JSON.
- Redirects: 5, each hop re-validated as a public address; loopback, private, link-local and metadata addresses are refused.
- Time: every
timeout_sis a whole-call budget that starts at the first DNS lookup. DNS, connect, redirects, every sitemap fetch, every retry and every render share that clock; no step starts a fresh timer. The default and maximum for an endpoint are printed on its page. - Nothing that was not delivered is billed, including a budget that expires mid-walk — that returns
status: "partial".
Machine-readable
Everything above, in a form an agent can parse.
Endpoints
Generated from the catalog. Each row links to its page.
| Method | Endpoint | Price | Returns |
|---|---|---|---|
POST /v1/text | $0.005 | Article text and metadata, boilerplate stripped. No browser by default; falls back to one when a page blocks plain HTTP. | |
POST /v1/map | $0.005 | Sitemaps, robots rules, feeds and link counts for a whole site, in one call. Plan a crawl before paying for it. | |
POST /v1/scrape | $0.005 | Chromium renders the page, runs interactions, and returns Markdown, text or HTML with its links. | |
POST /v1/scrape/residential | $0.02 | Scrape through a rotating residential exit, for targets that refuse a datacentre address. Same contract as /v1/scrape, one attempt, priced for the bandwidth. | |
POST /v1/fetch | $0.003 | Raw HTTP through the same egress. JSON APIs, GraphQL, PDFs, byte ranges - anything that is not a page. | |
POST /v1/batch | $0.0015/page | Up to 50 URLs in one call and one payment. Results in input order; undelivered pages come back as credit. | |
POST /v1/pdf | $0.005/document | Documents, not pages: page text plus one Markdown body, priced per document rather than per page. | |
POST /v1/section | $0.003 | Read one heading or one selector instead of the whole page - ten to a hundred times fewer tokens. | |
POST /v1/diff | $0.003 | Stateless change detection. Send the hash you hold, get back whether it changed and a bounded diff. | |
POST /v1/screenshot | $0.003 | PNG, JPEG or PDF of a rendered page: viewport, full page, or a single element. | |
POST /v1/search | $0.005 | Web search without a search subscription, returning titles, URLs and snippets. | |
POST /v1/crawl | $0.003/page | Bounded async crawl with a job id, a page cap quoted up front, and per-page Markdown. | |
POST /v1/probe | free | Metadata before spending: status, type, title, size, language, whether a bot wall is waiting. No page body. |
Flat prices run $0.003–$0.02 per call; /v1/batch and /v1/crawl are priced per delivered page and quoted before the work starts.