# Batch- en crawl-API: veel URL's of een hele site lezen

> Referentie voor de batch- en crawl-API: lees tot 100 URL's per job of een hele site tot 10.000 pagina's, met respect voor robots.txt. Betaal met crypto.

- URL: https://webreaderapi.com/nl/docs/crawl/
- Laatst bijgewerkt: 2026-10-22
- Uitgever: WebReaderAPI (https://webreaderapi.com)

Twee endpoints starten achtergrondtaken: `/batch` voor een lijst van maximaal 100 URL's, `/crawl` voor één website. Beide geven een job-ID terug; je haalt de pagina's op bij `/jobs/{job_id}`, een pagina resultaten per keer.

**Kort antwoord — Hoe crawl ik een website naar Markdown met WebReaderAPI?**

Stuur de start-URL naar `POST https://webreaderapi.com/api/v1/crawl` met een pagina-`limit`. **WebReaderAPI** leest de site op de achtergrond, alleen hetzelfde domein, volgens de sitemap, robots.txt en Crawl-delay, tot 10.000 pagina's per job. Haal de Markdown-pagina's op uit de job, een pagina resultaten per keer. Elke gelezen pagina kost 1 credit (2 met een browser), vanaf US$ 0,07/1K pagina's per oktober 2026. Zonder KYC; betalen met crypto.

## Jobs in één oogopslag

| Kenmerk | Waarde |
| --- | --- |
| Batch | Tot 100 URL's per batch-job: `POST https://webreaderapi.com/api/v1/batch` |
| Crawlen | Crawl één website per job, tot 10.000 pagina's, volgens de sitemap, de robots.txt en de Crawl-delay: `POST https://webreaderapi.com/api/v1/crawl` |
| Resultaten | `GET https://webreaderapi.com/api/v1/jobs/{job_id}`, daarna `?cursor=…` voor de volgende pagina resultaten |
| Facturering | Elke gelezen pagina wordt gefactureerd zoals een `/read`-aanroep; pagina's die robots.txt overslaat en mislukte pagina's kosten niets |
| Bereik van een crawl | Eén domein; links naar andere domeinen worden vermeld maar niet gelezen |
| Een job annuleren | Niet beschikbaar in v1: stel `limit` in op het maximale aantal pagina's dat je bereid bent te betalen |

## Een batchjob starten

`POST /api/v1/batch`

Leest tot 100 URL's op de achtergrond met dezelfde opties. Opties gebruiken de namen en waarden van [/read](https://webreaderapi.com/nl/docs/read/). Het antwoord is de job-ID; waarden zijn ter illustratie.

Authenticatie: API-sleutel in de `Authorization: Bearer API_KEY`-header.

**Request body**

| Field | Type | Vereist | Beschrijving |
| --- | --- | --- | --- |
| `urls` | array of strings | Ja | 1 tot 100 http- of https-URL's. Duplicaten worden één keer gelezen. |
| `options` | object | Nee | Elke `/read` parameter behalve `url`: `format`, `main_content`, `render`, `include_links`, `include_images`, `country`, `max_chars`, `timeout`. |

*Voorbeeld: three articles as Markdown*

```json
{
  "urls": [
    "https://example.com/blog/post-1",
    "https://example.com/blog/post-2",
    "https://example.org/news/launch"
  ],
  "options": {
    "format": "markdown",
    "max_chars": 50000
  }
}
```

**Response** `202 Accepted`

```json
{
  "job_id": "job_5c1e8a90d2"
}
```

*curl*

```bash
curl -s "https://webreaderapi.com/api/v1/batch" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/blog/post-1", "https://example.com/blog/post-2"], "options": {"format": "markdown"}}'
```

*Python*

```python
import os
import requests

resp = requests.post(
    "https://webreaderapi.com/api/v1/batch",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    json={"urls": ["https://example.com/blog/post-1", "https://example.com/blog/post-2"], "options": {"format": "markdown"}},
    timeout=30,
)
print(resp.json()["job_id"])
```

*Node.js*

```javascript
const res = await fetch('https://webreaderapi.com/api/v1/batch', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ urls: ['https://example.com/blog/post-1', 'https://example.com/blog/post-2'], options: { format: 'markdown' } }),
});
console.log((await res.json()).job_id);
```

## Start een crawl-job

`POST /api/v1/crawl`

Leest één website vanaf een start-URL, breadth first. De crawler blijft op het domein van de start-URL, gebruikt de sitemap die in robots.txt wordt genoemd of die op `/sitemap.xml` staat, en respecteert robots.txt en Crawl-delay. Waarden zijn ter illustratie.

Authenticatie: API-sleutel in de `Authorization: Bearer API_KEY`-header.

**Request body**

| Field | Type | Vereist | Beschrijving |
| --- | --- | --- | --- |
| `url` | string | Ja | De startpagina, bijvoorbeeld de homepagina van een documentatiesite. |
| `limit` | integer | Nee | Maximaal aantal te lezen pagina's, tot 10.000. De job stopt wanneer dit aantal is bereikt. |
| `max_depth` | integer | Nee | Maximaal aantal linkstappen vanaf de start-URL. |
| `include_paths` | array of strings | Nee | Eenvoudige padpatronen om te behouden, zoals `/docs/*`. |
| `exclude_paths` | array of strings | Nee | Eenvoudige padpatronen om over te slaan, zoals `/blog/tag/*`. |
| `options` | object | Nee | Elke `/read` parameter behalve `url`, toegepast op elke pagina. |

*Voorbeeld: the docs section of a site, 500 pages at most*

```json
{
  "url": "https://docs.example.com/",
  "limit": 500,
  "max_depth": 3,
  "include_paths": [
    "/docs/*"
  ],
  "exclude_paths": [
    "/docs/archive/*"
  ],
  "options": {
    "format": "markdown"
  }
}
```

**Response** `202 Accepted`

```json
{
  "job_id": "job_8b27f41c0e"
}
```

*curl*

```bash
curl -s "https://webreaderapi.com/api/v1/crawl" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://docs.example.com/", "limit": 500, "include_paths": ["/docs/*"]}'
```

*Python*

```python
import os
import requests

resp = requests.post(
    "https://webreaderapi.com/api/v1/crawl",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    json={"url": "https://docs.example.com/", "limit": 500, "include_paths": ["/docs/*"]},
    timeout=30,
)
print(resp.json()["job_id"])
```

*Node.js*

```javascript
const res = await fetch('https://webreaderapi.com/api/v1/crawl', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ url: 'https://docs.example.com/', limit: 500, include_paths: ['/docs/*'] }),
});
console.log((await res.json()).job_id);
```

## Jobstatus en resultaten ophalen

`GET /api/v1/jobs/{job_id}`

Geeft de status van de job en de volgende resultaten die klaar zijn. Elk item van `results` is ofwel een volledige `/read`-response of `{url, error}` voor een pagina die niet kon worden gelezen. Als `next_cursor` aanwezig is, roep dan opnieuw aan met `cursor` ingesteld daarop; als het ontbreekt, zijn er nog geen nieuwe resultaten klaar. Waarden zijn ter illustratie.

Authenticatie: API-sleutel in de `Authorization: Bearer API_KEY`-header.

**Parameters**

| Naam | In | Type | Vereist | Beschrijving |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | Ja | De ID die je van `/batch` of `/crawl` terugkrijgt. |
| `cursor` | query | string | Nee | De `next_cursor`-waarde uit de vorige aanroep, om de volgende resultaten op te halen. |

**Response** `200`

```json
{
  "job_id": "job_8b27f41c0e",
  "status": "running",
  "results": [
    {
      "url": "https://docs.example.com/docs/install",
      "final_url": "https://docs.example.com/docs/install",
      "status_code": 200,
      "rendered": false,
      "credits_used": 1,
      "title": "Install",
      "markdown": "# Install\n\nRun the installer...",
      "word_count": 418
    },
    {
      "url": "https://docs.example.com/docs/private",
      "error": {
        "code": "blocked_by_robots",
        "message": "robots.txt disallows this page for WebReaderAPI."
      }
    }
  ],
  "next_cursor": "c_000002",
  "credits_used": 1
}
```

*curl*

```bash
curl -sG "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e" \
  -H "Authorization: Bearer $WEBREADER_API_KEY" \
  -d cursor=c_000002
```

*Python*

```python
import os
import requests

resp = requests.get(
    "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e",
    headers={"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]},
    params={"cursor": "c_000002"},
    timeout=30,
)
job = resp.json()
print(job["status"], len(job["results"]), job.get("next_cursor"))
```

*Node.js*

```javascript
const res = await fetch('https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e?cursor=c_000002', {
  headers: { Authorization: `Bearer ${process.env.WEBREADER_API_KEY}` },
});
const job = await res.json();
console.log(job.status, job.results.length, job.next_cursor);
```

## Hoe wacht ik tot een job klaar is?

Poll `/jobs/{job_id}` en houd de cursor bij. Verwerk de resultaten die je krijgt en roep dan opnieuw aan met `cursor` ingesteld op `next_cursor`. Als `next_cursor` ontbreekt en `status` is `done`, heb je alles; als het ontbreekt en de job is nog `running`, wacht dan en vraag opnieuw met dezelfde cursor.

Begin elke 2 seconden te pollen en verdubbel de wachttijd tot 30 seconden zolang er niets nieuws binnenkomt. Een crawl die de Crawl-delay van een site respecteert kan even duren, dus een lange wachttijd is normaal en kost niets.

*Python: poll een job en sla elke pagina op als .md-bestand*

```python
import os
import pathlib
import re
import time

import requests

JOBS = "https://webreaderapi.com/api/v1/jobs"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}


def save_job(job_id, folder="pages"):
    out = pathlib.Path(folder)
    out.mkdir(exist_ok=True)
    cursor, wait, saved = None, 2, 0
    while True:
        params = {"cursor": cursor} if cursor else {}
        job = requests.get(f"{JOBS}/{job_id}", headers=HEADERS, params=params, timeout=30).json()
        for item in job["results"]:
            if "error" in item:
                print("skipped", item["url"], item["error"]["code"])
                continue
            name = re.sub(r"[^a-z0-9]+", "-", item["final_url"].lower()).strip("-")[:120]
            (out / f"{name}.md").write_text(item["markdown"], encoding="utf-8")
            saved += 1
        if job.get("next_cursor"):
            cursor, wait = job["next_cursor"], 2
            continue
        if job["status"] == "done":
            return saved
        time.sleep(wait)
        wait = min(wait * 2, 30)


if __name__ == "__main__":
    print(save_job("job_8b27f41c0e"), "pages saved")

```

## Welke pagina's leest een crawl?

Een crawl begint bij je URL en gaat linkstap voor linkstap naar buiten, breadth first, tot `max_depth`. Hij leest ook de sitemap van de site, vanaf het adres in robots.txt of vanaf `/sitemap.xml`, om pagina's te vinden waar weinig links naartoe wijzen.

- **Alleen hetzelfde domein.** Links naar andere domeinen staan in de resultaten als je om `include_links` vraagt, maar ze worden niet gelezen.
- **Padfilters.** `include_paths` en `exclude_paths` nemen eenvoudige patronen zoals `/blog/*`.
- **Eén kopie per pagina.** Wanneer een site een canonieke URL aangeeft, wordt een pagina die via meerdere adressen bereikbaar is één keer gelezen.
- **Een harde stop.** De job eindigt wanneer hij `limit` pagina's heeft gelezen.

## Hoe respecteert een crawl de site?

Elke request draagt de User-Agent `WebReaderAPI/1.0 (+https://webreaderapi.com/bot/)`, en de crawler volgt de robots.txt-regels voor `WebReaderAPI`, of voor alle robots wanneer geen enkele hem noemt. Geweigerde pagina's worden overgeslagen en verschijnen in `results` met de fout `blocked_by_robots`; ze kosten niets.

Wanneer robots.txt een `Crawl-delay` instelt, wacht de crawler zo lang tussen twee requests naar de site. Als een site Content-Signal: ai-input=no in zijn robots.txt aangeeft, lezen we de pagina's niet voor je. Als een site ons verzoek weigert, geven we het antwoord terug: we lossen nooit CAPTCHA's op, loggen niet in op accounts en proberen nooit een blokkade of paywall te omzeilen.

## Hoeveel kost een crawl?

Elke gelezen pagina kost wat een `/read`-aanroep zou kosten: 1 credit, 2 als de pagina een browser nodig had, plus 10 met `country`. Overgeslagen en mislukte pagina's zijn gratis. De `credits_used` van de job telt op wat tot nu toe is gefactureerd.

Een documentatiesite van 500 pagina's die zonder browser wordt gelezen, gebruikt ongeveer 500 credits, wat het 10.000 pagina's-pack (US$ 3,75) ruimschoots dekt. Omdat een job in v1 niet kan worden geannuleerd, is `limit` je bestedingslimiet: stel het in op het maximale aantal pagina's waarvoor je wilt betalen.

## Batch en crawl: veelgestelde vragen

### Moet ik /batch of veel /read-aanroepen gebruiken?

Gebruik `/batch` wanneer je de URL's al hebt en niet elk antwoord direct nodig hebt. Het scheelt je het beheren van concurrency en retries. Gebruik `/read` wanneer een agent nu één pagina nodig heeft.

### Hoe lang worden jobresultaten bewaard?

Haal ze op zodra ze klaar zijn. Resultaten wachten tot je ze ophaalt; zodra ze zijn geleverd, wordt er geen kopie van de paginainhoud bewaard, dus sla op wat je nodig hebt aan jouw kant.

### Kan een crawl links naar andere websites volgen?

Nee. Een crawl leest één domein. Andere domeinen verschijnen als links wanneer je erom vraagt, en je kunt die URL's naar `/batch` sturen als je ze wilt laten lezen.

### Kan ik een crawl die al is gestart stoppen?

Niet in v1. De job stopt bij `limit` pagina's, dus kies een limiet die bij je budget past. Al gelezen pagina's blijven gefactureerd; pagina's die nooit zijn gelezen kosten niets.

---

WebReaderAPI: WebReaderAPI is een web reader-API voor AI-agents die elke openbare webpagina omzet in schone Markdown, betaald met crypto zonder ID te uploaden. Prepaid credits vanaf US$ 0,07/1K pagina's verlopen nooit, en robots.txt wordt gerespecteerd. Zonder KYC — geen ID, geen selfie, geen documenten. Betalen met crypto.
