- readfetch
- examples
Recipes
Eight jobs, one request each
Each block is a complete call against https://readfetch.com. The payment step — 402, sign accepts[0], resend with PAYMENT-SIGNATURE — is the same for all of them and is written out once in /docs. No SDK, no key: curl and jq are enough to run every one of these.
Ingest one article into a RAG store
/v1/text
You have a URL and want the body as Markdown, with the metadata a vector store wants to filter on.
curl -s https://readfetch.com/v1/text \
-H 'content-type: application/json' \
-d '{"url":"https://example.com/blog/post","format":"markdown"}' \
| jq '{id: .url, title, text, author: .metadata.author, date: .metadata.date, bytes: .size}'
const res = await fetch("https://readfetch.com/v1/text", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ url: "https://example.com/blog/post", format: "markdown" }),
});
if (res.status === 402) {
// quote is in the body's accepts[0] and in the PAYMENT-REQUIRED header:
// sign it, then resend this exact request with PAYMENT-SIGNATURE.
throw new Error("unpaid");
}
const page = await res.json();
if (page.billed === false) console.warn(page.hint); // nothing was charged
console.log({ url: page.url, title: page.title, text: page.text });
Research loop: search, then read the top results
/v1/search → /v1/text
You have a question and no URLs. One search, then a text call per result you decide to read.
curl -s https://readfetch.com/v1/search \
-H 'content-type: application/json' \
-d '{"query":"rust async runtime","limit":5}' \
| jq -r '.results[].url' \
| head -3 \
| while read -r u; do
curl -s https://readfetch.com/v1/text \
-H 'content-type: application/json' \
-d "$(jq -nc --arg u "$u" '{url:$u}')" \
| jq -c '{url, title, chars: (.text | length)}'
done
The search is one payment; each text call is another. Read three of five results, not all of them — that is the whole cost argument for doing search and reading as separate calls.
A page that only fills in after JavaScript
/v1/scrape
The HTML is a shell: the results arrive after a fetch, so text extraction gets nothing. Render it once in Chromium and wait for the selector.
curl -s https://readfetch.com/v1/scrape \
-H 'content-type: application/json' \
-d '{
"url": "https://example.com/app",
"mode": "render",
"format": "markdown",
"advanced": {
"wait_for": "#results",
"scan_full_page": true,
"include_links": true
}
}' \
| jq '{title, markdown, links: (.links | length), billed}'
Use /v1/text instead whenever the content is already in the HTML — it is cheaper and it does not hold a browser slot. used_browser on a text response tells you whether a render rescued it.
Read a list of URLs for one payment
/v1/batch
A list of pages, one quote, results in the order you sent them.
curl -s https://readfetch.com/v1/batch \
-H 'content-type: application/json' \
-d '{
"urls": [
"https://example.com/a",
"https://example.com/b",
"https://example.com/c"
],
"pipeline": "text",
"format": "markdown"
}' \
| jq '{delivered, failed, credit, pages: [.results[] | {url, billed, title}]}'
Priced per delivered page with a minimum, and the 402 quotes the whole list before anything runs. A page that does not deliver is not billed and shows up as credit: check billed per result, not just the top-level status.
Know when a page changes
/v1/diff
No storage on our side: you keep the hash, we tell you whether it moved and what changed.
curl -s https://readfetch.com/v1/diff \
-H 'content-type: application/json' \
-d '{"url":"https://example.com/pricing"}' \
| jq -r '.hash' > pricing.hash
curl -s https://readfetch.com/v1/diff \
-H 'content-type: application/json' \
-d "$(jq -nc --rawfile h pricing.hash --arg u 'https://example.com/pricing' '{url:$u, previous_hash:($h|rtrimstr("\n"))}')" \
| jq '{changed, added, removed, diff}'
A no-change answer is billable: it is a fresh read of the page, not a cache hit. Send previous_content as well if you want a line diff rather than a hash comparison.
Text out of a PDF
/v1/pdf
A report, a paper or an invoice — page text plus one Markdown body, priced per document.
curl -s https://readfetch.com/v1/pdf \
-H 'content-type: application/json' \
-d '{"url":"https://example.com/report.pdf","max_pages":20}' \
| jq '{page_count, title, markdown, billed, hint}'
Priced per document, so raising max_pages does not raise the price — it only bounds the work. Only %PDF- bodies are accepted; a PDF served as HTML comes back unbilled with a hint.
One section instead of a whole page
/v1/section
You need the "Authentication" part of a long API page, not forty thousand tokens of it.
curl -s https://readfetch.com/v1/section \
-H 'content-type: application/json' \
-d '{
"url": "https://docs.example.com/api",
"heading": "Authentication",
"max_chars": 4000
}' \
| jq '{matched, chars, text}'
A heading that is not found is a 200 with an empty body and available_headings — pick one of those names and call again. selector works the same way for a CSS selector, up to twenty matches.
A whole site, a few pages deep
/v1/crawl → /v1/crawl/{id}
You want /blog, twenty pages, two levels. The cap is quoted before the job starts.
curl -s https://readfetch.com/v1/crawl \
-H 'content-type: application/json' \
-d '{
"url": "https://example.com",
"max_pages": 20,
"max_depth": 2,
"include": ["/blog/*"]
}'
# {"job_id":"job_ab12","status":"queued","pages_quoted":20,"hint":"GET /v1/crawl/{id} for progress"}
curl -s https://readfetch.com/v1/crawl/job_ab12
Billing is per delivered page, bounded by the quoted cap. If you do not know the shape of the site yet, /v1/map returns its sitemaps, feeds and robots rules for the price of one call.
When a recipe returns billed: false
Nothing was charged, and the response carries a hint naming the next call: a wall means /v1/scrape, empty text means the page needs a render, a binary body means /v1/fetch. /SKILL.md has the full symptom-to-next-call table, and /choose has the decision if you would rather pick the endpoint before writing the loop.