Quick answer
What parameters does the WebReaderAPI read endpoint take?
WebReaderAPI's /read endpoint needs one parameter, url. Optional ones choose the output (format), how much of the page to keep (main_content), whether a browser runs (render), links and images, the reading country, a character cap and a timeout. A plain page costs 1 credit, 2 with a browser, from $0.07/1K pages as of October 2026. No KYC; pay in crypto.
/read at a glance
| Methods | GET with query parameters, or POST with a JSON body using the same names |
|---|---|
| URL | https://webreaderapi.com/api/v1/read |
| Required | url: an http or https address, up to 2,048 characters |
| Default output | Markdown of the main content |
| Billing | 1 credit = 1 page read without a browser; 2 credits when the page needs a browser to render JavaScript. A request that fails, or that a site's robots.txt refuses, is not charged |
| Country | Choosing the country a page is read from adds 10 credits, because the page is then read through a residential connection in that country |
| PDF files up to 20 MB and 100 pages are converted to Markdown, at 1 credit per started block of 10 pages | |
| Crawler identity | WebReaderAPI/1.0 (+https://webreaderapi.com/bot/) |
Read one page (GET)
GET/api/v1/read
Fetches one public page and returns its main content as Markdown, with the page's metadata. Parameters go in the query string, URL-encoded. The example reads a blog post; IDs, dates and values are illustrative.
Authentication: API key in the Authorization: Bearer API_KEY header.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
url | query | string | Yes | The page to read: http or https, at most 2,048 characters. Private, local and cloud metadata addresses are refused. |
format | query | string | No | markdown (default), text (plain text, no markup) or html (cleaned HTML of the content). |
main_content | query | boolean | No | true (default) keeps the article or main content only; false converts the whole page, menus and footer included. |
render | query | string | No | auto (default) starts a browser only when the raw HTML holds too little text; true always; false never. A rendered page costs 2 credits. |
include_links | query | boolean | No | true adds a links list of {url, text}. Default false. |
include_images | query | boolean | No | true keeps images as  in the Markdown. Default false. |
country | query | string | No | Two-letter country code (ISO 3166-1 alpha-2). The page is read from an address in that country, for 10 extra credits (residential connection). |
max_chars | query | integer | No | Cuts the output at this many characters, from 1,000 to 500,000. Useful to fit a model's context. |
timeout | query | integer | No | Seconds to wait for the site, from 5 to 60. Default 30. |
api_key | query | string | No | Your key, if you do not send the Authorization header. |
Response 200
{
"url": "https://example.com/blog/first-post",
"final_url": "https://example.com/blog/first-post",
"status_code": 200,
"fetched_at": "2026-10-22T09:41:12Z",
"rendered": false,
"credits_used": 1,
"title": "Our first post",
"description": "Why we started this blog and what comes next.",
"language": "en",
"author": "Jane Doe",
"published_at": "2026-10-01",
"site_name": "Example Blog",
"markdown": "# Our first post\n\nWe started this blog to share what we learn...\n\n## What comes next\n\n- A post every month\n- Notes from our team",
"word_count": 612
}Code examples
curl -sG "https://webreaderapi.com/api/v1/read" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
--data-urlencode "url=https://example.com/blog/first-post" \
-d format=markdown -d max_chars=20000import os
import requests
resp = requests.get(
"https://webreaderapi.com/api/v1/read",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
params={"url": "https://example.com/blog/first-post", "max_chars": 20000},
timeout=70,
)
resp.raise_for_status()
print(resp.json()["markdown"][:300])const params = new URLSearchParams({ url: 'https://example.com/blog/first-post', max_chars: '20000' });
const res = await fetch(`https://webreaderapi.com/api/v1/read?${params}`, {
headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}` },
});
const page = await res.json();
console.log(page.markdown.slice(0, 300));Read one page (POST)
POST/api/v1/read
Same endpoint, same parameters, sent as a JSON body. Prefer POST when the URL is very long or when your HTTP client builds JSON more easily than query strings. The response is identical to the GET form.
Authentication: API key in the Authorization: Bearer API_KEY header.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The page to read. |
format, main_content, render, include_links, include_images, country, max_chars, timeout | mixed | No | As in the GET table above, with JSON types: booleans as true/false, numbers unquoted. |
{
"url": "https://example.com/app/pricing",
"render": "true",
"country": "de",
"include_links": true,
"max_chars": 30000
}Response 200
{
"url": "https://example.com/app/pricing",
"final_url": "https://example.com/de/app/pricing",
"status_code": 200,
"fetched_at": "2026-10-22T09:44:03Z",
"rendered": true,
"credits_used": 3,
"title": "Pricing",
"language": "de",
"markdown": "# Pricing\n\n| Plan | Price |\n|---|---|\n| Basic | ... |",
"word_count": 214,
"links": [
{
"url": "https://example.com/de/contact",
"text": "Contact"
}
]
}Code examples
curl -s "https://webreaderapi.com/api/v1/read" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/app/pricing", "render": "true", "country": "de", "include_links": true}'import os
import requests
resp = requests.post(
"https://webreaderapi.com/api/v1/read",
headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
json={"url": "https://example.com/app/pricing", "render": "true", "country": "de", "include_links": True},
timeout=70,
)
print(resp.json()["credits_used"], "credits")const res = await fetch('https://webreaderapi.com/api/v1/read', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: 'https://example.com/app/pricing', render: 'true', country: 'de', include_links: true }),
});
console.log((await res.json()).credits_used);Which output format should I choose?
Choose markdown for language models and RAG: headings, lists, tables and links survive, and the text stays compact. Choose text when you only count words, classify or search, and markup would be noise. Choose html when your own pipeline already parses HTML and you only want the clutter gone.
The metadata fields are the same in all three formats. Only the name of the content field changes: markdown, text or html.
When should I set main_content to false?
Set it to false when the parts around the article are what you need. Examples are a site's navigation to map its sections, a footer with legal or contact details, or a product page whose specifications sit in a sidebar.
With the default true, the extractor keeps the main content block and drops navigation, cookie banners, ads, share buttons and footers. That is what most agents and RAG pipelines want, and it often cuts a page to a fraction of its HTML size.
How does render=auto decide to use a browser?
It first fetches the raw HTML, which is fast and costs 1 credit. If that HTML holds too little readable text, typically an app that builds the page with JavaScript, it loads the page again in a headless browser and the call costs 2 credits. The response tells you which path ran: rendered is true or false.
Set render=false when you know the site serves full HTML and want a fixed cost. Set render=true for single-page apps, to skip the first attempt.
What do country and max_chars change?
country reads the page from an address in the country you name, with the same User-Agent. Use it for pages whose prices, language or availability change by country. It adds 10 credits, because the page is then read through a residential connection, and it cannot be used with the countries the service does not operate in.
max_chars cuts the output after that many characters. It does not change the price, but it protects an agent's context window: 20,000 characters is a sensible default for a single page passed to a model.
Can /read handle PDF files?
Yes. When the URL returns a PDF, the text comes back as Markdown in the same markdown field. PDF files up to 20 MB and 100 pages are converted to Markdown, at 1 credit per started block of 10 pages. Scanned PDFs without a text layer return little or no text, because there is no character recognition step.
Read endpoint: common questions
Does /read follow redirects?
Yes, up to 5. The response gives the address you asked for in url and the address actually read in final_url. A redirect to a private or local address is refused.
Can I read a page that needs a login?
No. If a site refuses our request, we return its answer: we never solve CAPTCHAs, log in to accounts, or try to get around a block or a paywall. Pages behind a login or a paywall return the site's refusal, and the call is free.
Is the robots.txt check done on every call?
Yes, against a copy of the site's robots.txt that is at most 24 hours old. A disallowed URL returns 403 blocked_by_robots and uses no credit.
How many pages can I read at the same time?
Every account has a limit on how many pages it reads at once; your dashboard shows it, and support can raise it. For many URLs, a batch job is simpler than running many parallel calls.