---
title: "Pages as Markdown"
summary: "What Kerf does with a web page, how to ask for one, why most pages take a second and some take longer, what each option is for, and what comes back."
category: "developer"
tags: ["kerf", "markdown", "extraction", "web pages", "reading order", "options"]
updated: "2026-09-14T04:25:06.466Z"
url: "https://support.quorumtech.ch/articles/kerf-pages"
markdown: "https://support.quorumtech.ch/articles/kerf-pages.md"
---

# Pages as Markdown

Give Kerf an address and you get the page back as Markdown, in the order a person reads it. That order is the point. A converter walks the HTML top to bottom and hands you a two-column layout as alternating lines from each column. Kerf looks at where the text sits on the page, reads the left column and then the right, keeps a table as rows, and leaves the navigation, cookie notice, share bar and footer out.

Base URL: `https://api.quorumtech.ch`. Your key needs the `kerf` scope.

## The first call

```bash
curl -X POST "https://api.quorumtech.ch/kerf/v1/extract/url" \
  -H "Authorization: Bearer $QUORUM_API_KEY" \
  -H "content-type: application/json" \
  -d '{ "url": "https://example.com/article", "wait": 4000 }'
```

`wait` is how long, in milliseconds, you are willing to hold the request open. Most pages are done inside two seconds, so a wait of a few thousand usually means one call and no job to follow. If the page is not ready in time you get a `202` with an `id`, and `GET /kerf/v1/jobs/{id}` collects it when it is. Collecting is free, however many times you do it.

`POST /kerf/v1/render` does the same work and always holds the request open. It is the simplest call to write and the one most likely to hit your client's own timeout on a slow page. If you are fetching pages you have not seen before, the job route with a `wait` is the safer habit.

## Why some pages take a second and some take ten

Most pages arrive as HTML with the words already in it. Kerf fetches those directly, the way a browser would ask for them, and reads them without running any scripts. That takes about half a second.

Some pages are a shell. The HTML has the layout and the scripts, and the words are written in after the page loads: prices on a product page, the branch count on a code repository, the rows of a comparison table. Reading the HTML of one of those gets you the layout and not the words. Kerf opens those in a real browser, waits for the page to settle, and reads what a person would see. That takes several seconds and costs us about twenty times as much.

You pay the same either way. A page is a tenth of a credit, which is $0.001 at $1.00 per thousand pages, whether it was read directly or in a browser. What a page costs you should not depend on how a site chose to build itself, and your bill should not move when a site rebuilds its front end.

With `mode` left at `auto`, Kerf decides which path a page needs. The direct read is checked before it is served, and a page that looks like a shell is opened in a browser instead. Kerf also remembers which sites need a browser: once it has seen that a site's pages lose content on the direct read, the next page from that site goes straight to the browser, and it keeps checking so a site that changes is noticed. None of this needs anything from you.

`report.path` says which way a page went. Set `mode` to `fast` if you would rather a page that needs a browser failed than took the time, and to `browser` if you want one regardless.

## What comes back

```json
{
  "id": "6f1ec0d2-3f44-4a1f-9c1e-2b0f4f4d9d2a",
  "state": "done",
  "url": "https://example.com/article",
  "result": {
    "url": "https://example.com/article",
    "finalUrl": "https://example.com/article",
    "status": 200,
    "title": "An example article",
    "markdown": "# An example article\n\n...",
    "report": { "path": "fast", "words": 812, "items": 34, "tables": 1, "images": 0, "links": 12 },
    "renderMs": 940
  }
}
```

`markdown` is the page. `report` says what the fetch did. Three fields are worth reading in code:

- `report.words` tells you a page actually arrived. A news article that comes back as forty words is a wall or a paywall, not a short article.
- `report.path` is `fast` or `browser`, and `report.fastFallback`, when present, says why the direct read was not used.
- `report.uncertain`, on the browser path, means the layout did not settle into one reading order. The Markdown is still the page, but its order may not be the one you would read.

The report also carries diagnostics that change as the extractor does. Read it by field name, and do not depend on fields that are not described here.

Text the page holds but does not show, such as the other tabs of a tabbed panel or a closed accordion, is written after the visible text under a `<!-- folded -->` comment. It is often the content you wanted, since a pricing page keeps its yearly prices behind a tab. If you would rather not have it, set `hidden` to `drop`.

## Options

Each option exists because some pages need it. The defaults are right for most.

| Option | Default | What it is for |
| --- | --- | --- |
| `mode` | `auto` | Which fetch path to use. `auto` chooses, `fast` never opens a browser, `browser` always does. See above. |
| `furniture` | `drop` | What to do with navigation, cookie notices, share bars and footers. `end` keeps them and moves them after the content, for when you need the links in a site's menu. |
| `hidden` | `include` | Text the page holds but does not show yet, such as inactive tabs and closed accordions. `drop` leaves it out. |
| `linksEnabled` | `true` | Keep links as `[text](url)`. `false` keeps the text and drops the addresses, which makes the Markdown smaller when you do not need them. |
| `imagesEnabled` | `false` | `true` loads the page's images and keeps every one that has an address. Off, images are blocked for speed, and only those with a description survive, as alt text. Turn it on for a page where the pictures are the content. |
| `followRobots` | `false` | Consult the site's robots.txt first and refuse a page it disallows. Off because this fetches one page you named rather than crawling a site; on for compliance work. |
| `location` | none | Fetch the page from another country, so prices, currency and terms are the ones a visitor there sees. See [Fetching a page from a country](/docs/kerf-location). |
| `strictLocation` | `false` | With `location`: fail rather than fall back to our own address when the country cannot be reached. |
| `optimiseForRAG` | `false` | Shape the Markdown for a system that cuts it into chunks and retrieves by similarity. See [Markdown for retrieval](/docs/kerf-for-rag). |
| `humanizeEnabled` | `false` | Render in a browser that moves its cursor the way a person does. Slower, rarely needed, and it implies `browser`. |

## When you pay

The tenth of a credit is held when the job is accepted and charged when you collect the page. The `202` says `X-Credits-Reserved: 0.1`, and the collection that first sees the job finish says `X-Credits-Charged: 0.1`. Collecting it again is free, and so are the progress and event routes.

A fetch that comes back with nothing costs nothing: a job that failed settles at zero when you collect it. A job nobody ever collects settles at its hold a day later, because the page was fetched whether or not anyone read it.

`POST /kerf/v1/render` has the page by the time it answers, so it charges there and then, and a refusal charges nothing.

## When a page does not come back

A site can refuse to serve a page to anyone but a person at a browser, and some do. That answers `424 site_refused`, on the call itself if you waited for the page and on the collection if you did not, and neither is charged. Kerf asks once more from a different address before giving up, and does not try harder than that: a site that says no is taken at its word. If you hold the document itself rather than its address, extracting the file is the way through.

The address must be public. Private and loopback addresses are refused with `403 url_not_allowed`, including a redirect that leads to one.

## What is kept

The address you send and the Markdown that comes back are held in memory for a short cache window and then forgotten. Nothing is written to a database, and nothing survives a restart of the service. A job is readable only by the workspace that started it; an id from another workspace reads as though it did not exist.

What Kerf does keep is the memory described above, which sites need a browser. It holds the shape of an address, such as `github.com/{s}/{s}`, and a number. It never holds a page or a full address.

The fetch goes out from our address, not yours, so the site sees Quorum rather than your infrastructure.
