# Read API response format, field by field

> Read API response format: every field returned for a page, from markdown and word_count to final_url and rendered, plus job results. No KYC, pay in crypto.

- URL: https://webreaderapi.com/docs/response/
- Last updated: 2026-10-22
- Publisher: WebReaderAPI (https://webreaderapi.com)

Every `/read` call returns one JSON object: the page's content in the format you asked for, its metadata, and three fields that tell you what happened and what it cost. Job results reuse the same object.

**Quick answer — What does the WebReaderAPI read response contain?**

A **WebReaderAPI** `/read` response holds the page content in `markdown` (or `text` or `html`), its metadata (`title`, `description`, `author`, `published_at`, `language`, `site_name`, `word_count`), and the facts of the fetch: `final_url`, `status_code`, `rendered` and `credits_used`. A plain page uses 1 credit, from $0.07/1K pages as of October 2026. No KYC; pay in crypto.

## Which fields describe the fetch?

These fields are always present in a successful response.

| Field | Type | Meaning |
| --- | --- | --- |
| `url` | string | The address you sent |
| `final_url` | string | The address actually read, after redirects (5 at most) |
| `status_code` | integer | The HTTP status the site returned for the final address, normally 200 |
| `fetched_at` | string | Time of the fetch, ISO 8601 in UTC |
| `rendered` | boolean | `true` when a headless browser built the page |
| `credits_used` | integer | Credits billed for this page: 1, 2 with a browser, plus 10 with `country`; PDFs 1 per started 10 pages |

## Which fields describe the page?

Metadata comes from the page itself: its title tag, meta tags, structured data and visible byline. A field the page does not provide is absent, so test for it before use.

| Field | Type | Meaning |
| --- | --- | --- |
| `title` | string | The page or article title |
| `description` | string | The summary the page gives of itself |
| `language` | string | Language code of the content, such as `en` or `de` |
| `author` | string | The author named on the page |
| `published_at` | string | Publication date stated by the page |
| `site_name` | string | The site's own name |
| `word_count` | integer | Words in the returned content, after extraction and before `max_chars` |

## Where is the content itself?

One content field is returned, named after the `format` you chose.

| Field | When | Content |
| --- | --- | --- |
| `markdown` | `format=markdown` (default) | Headings, paragraphs, lists, tables and links in Markdown; images too with `include_images=true` |
| `text` | `format=text` | Plain text, paragraphs separated by blank lines |
| `html` | `format=html` | Cleaned HTML of the main content, without scripts, styles or ads |
| `links` | `include_links=true` | List of `{url, text}` for the links found on the page, absolute URLs |

*A complete response (values are illustrative)*

```json
{
  "url": "https://example.com/guides/compost",
  "final_url": "https://example.com/guides/compost-at-home",
  "status_code": 200,
  "fetched_at": "2026-10-22T10:02:44Z",
  "rendered": false,
  "credits_used": 1,
  "title": "Compost at home: a beginner's guide",
  "description": "What to put in a compost bin, and what to leave out.",
  "language": "en",
  "author": "Sam Rivera",
  "published_at": "2026-09-14",
  "site_name": "Example Garden",
  "markdown": "# Compost at home: a beginner's guide\n\nA compost bin turns kitchen scraps into soil...\n\n## What goes in\n\n- Fruit and vegetable peels\n- Coffee grounds\n\nSee also [Choosing a bin](https://example.com/guides/bins).",
  "word_count": 1184,
  "links": [
    { "url": "https://example.com/guides/bins", "text": "Choosing a bin" }
  ]
}
```

## How do I tell a rendered page from a plain one?

Look at `rendered`. With `render=auto`, the API first reads the raw HTML; it starts a browser only when that HTML holds too little text, and then sets `rendered` to `true` and `credits_used` to 2. If a site you read often always comes back rendered, send `render=true` from the start: the cost is the same and you skip a wasted first step.

If a page comes back unrendered but thin, compare `word_count` with what you see in a browser. A low count usually means the content needs JavaScript; retry once with `render=true`.

## Why can final_url differ from url?

Because sites redirect. A short link, an old address, a missing trailing slash or a language switch all send you elsewhere. The API follows up to 5 redirects and reports the address it finally read in `final_url`.

Store `final_url` as the canonical source of a document in a RAG index. Two different `url` values that end at the same `final_url` are the same page, so you can drop the duplicate before embedding it.

## What does a job result look like?

`GET https://webreaderapi.com/api/v1/jobs/{job_id}` returns `job_id`, `status` (`queued`, `running` or `done`), `results`, `next_cursor` and the job's running `credits_used`. Each item of `results` is one of two shapes:

- a full `/read` response, exactly as described above, for a page that was read;
- `{url, error}` for a page that was not, where `error` has the same `code` and `message` as an [API error](https://webreaderapi.com/docs/errors/).

`next_cursor` is present while more results are ready; send it back as `cursor` to get them. The [batch and crawl page](https://webreaderapi.com/docs/crawl/) has a complete polling loop.

*A job result page (values are illustrative)*

```json
{
  "job_id": "job_5c1e8a90d2",
  "status": "done",
  "results": [
    { "url": "https://example.com/blog/post-1", "final_url": "https://example.com/blog/post-1", "status_code": 200, "rendered": false, "credits_used": 1, "title": "Post one", "markdown": "# Post one\n\n...", "word_count": 802 },
    { "url": "https://example.org/members/area", "error": { "code": "target_refused", "message": "The site refused the request.", "target_status": 403 } }
  ],
  "credits_used": 1
}
```

## What does an error response look like?

An error comes back with an HTTP status of 400 or more and a JSON body `{"error": {"code", "message"}}`. When the site itself refused the request, `target_status` gives the status the site sent. No error is billed. The [errors page](https://webreaderapi.com/docs/errors/) lists every code with its cause and whether a retry can help.

## Response format: common questions

### Is the Markdown cut when I set max_chars?

Yes. The content field stops at that many characters. `word_count` still describes the whole extracted content, so you can see how much was left out.

### Why is author or published_at missing?

Because the page does not state it in a way that can be read. Many pages give no author or date. Absent fields are omitted rather than returned empty.

### Are the links in the Markdown absolute?

Links in the `links` list are absolute. Write your code to accept both forms in the Markdown body, and resolve relative ones against `final_url`.

### Can I get the raw HTML of the whole page?

The closest is `format=html` with `main_content=false`, which returns the whole page cleaned of scripts and styles. The original byte-for-byte HTML is not part of v1.

---

WebReaderAPI: WebReaderAPI is a web reader API for AI agents that turns any public web page into clean Markdown, paid in crypto with no ID upload. Prepaid credits from $0.07/1K pages never expire, and robots.txt is respected. No KYC — no ID, no selfie, no documents. Pay in crypto.
