# API batch dan crawl: baca banyak URL atau seluruh situs

> API batch dan crawl: baca hingga 100 URL per job atau seluruh situs hingga 10.000 halaman, dengan robots.txt dan Crawl-delay. Bayar pakai kripto tanpa KYC.

- URL: https://webreaderapi.com/id/docs/crawl/
- Terakhir diperbarui: 2026-10-22
- Penerbit: WebReaderAPI (https://webreaderapi.com)

Dua endpoint memulai job latar belakang: `/batch` untuk daftar hingga 100 URL, `/crawl` untuk satu situs web. Keduanya mengembalikan ID job; Anda mengambil halaman dari `/jobs/{job_id}`, satu halaman hasil setiap kali.

**Jawaban singkat — Bagaimana cara crawl situs web ke Markdown dengan WebReaderAPI?**

Kirim URL awal ke `POST https://webreaderapi.com/api/v1/crawl` dengan halaman `limit`. **WebReaderAPI** membaca situs di latar belakang, hanya domain yang sama, mengikuti sitemap, robots.txt, dan Crawl-delay-nya, hingga 10.000 halaman per job. Ambil halaman Markdown dari job, satu halaman hasil setiap kali. Setiap halaman yang dibaca berbiaya 1 kredit (2 dengan browser), mulai dari US$0,07/1K halaman per Oktober 2026. Tanpa KYC; bayar dengan kripto.

## Job sekilas

| Atribut | Nilai |
| --- | --- |
| Batch | Hingga 100 URL per batch job: `POST https://webreaderapi.com/api/v1/batch` |
| Crawl | Crawl satu situs web per job, hingga 10.000 halaman, mengikuti sitemap, robots.txt dan Crawl-delay-nya: `POST https://webreaderapi.com/api/v1/crawl` |
| Hasil | `GET https://webreaderapi.com/api/v1/jobs/{job_id}`, lalu `?cursor=…` untuk halaman hasil berikutnya |
| Penagihan | Setiap halaman yang dibaca ditagih seperti panggilan `/read`; halaman yang dilewati oleh robots.txt dan halaman yang gagal tidak berbiaya |
| Cakupan crawl | Satu domain; tautan ke domain lain dicantumkan tetapi tidak dibaca |
| Batalkan job | Tidak tersedia di v1: setel `limit` ke jumlah halaman maksimum yang siap Anda bayar |

## Mulai job batch

`POST /api/v1/batch`

Membaca hingga 100 URL di latar belakang dengan opsi yang sama. Opsi menggunakan nama dan nilai dari [/read](https://webreaderapi.com/id/docs/read/). Jawabannya adalah ID job; nilai bersifat ilustratif.

Autentikasi: API key di header `Authorization: Bearer API_KEY`.

**Request body**

| Field | Tipe | Wajib | Deskripsi |
| --- | --- | --- | --- |
| `urls` | array of strings | Ya | 1 hingga 100 URL http atau https. Duplikat dibaca sekali. |
| `options` | object | Tidak | Parameter `/read` apa pun kecuali `url`: `format`, `main_content`, `render`, `include_links`, `include_images`, `country`, `max_chars`, `timeout`. |

*Contoh: 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);
```

## Mulai job crawl

`POST /api/v1/crawl`

Membaca satu website dari URL awal, breadth first. Crawler tetap berada di domain URL awal, menggunakan sitemap yang disebut di robots.txt atau yang ditemukan di `/sitemap.xml`, serta mematuhi robots.txt dan Crawl-delay. Nilai-nilai bersifat ilustratif.

Autentikasi: API key di header `Authorization: Bearer API_KEY`.

**Request body**

| Field | Tipe | Wajib | Deskripsi |
| --- | --- | --- | --- |
| `url` | string | Ya | Halaman awal, misalnya halaman beranda situs dokumentasi. |
| `limit` | integer | Tidak | Jumlah maksimum halaman yang dibaca, hingga 10.000. Job berhenti saat mencapai jumlah ini. |
| `max_depth` | integer | Tidak | Jumlah maksimum langkah tautan menjauh dari URL awal. |
| `include_paths` | array of strings | Tidak | Pola path sederhana yang dipertahankan, misalnya `/docs/*`. |
| `exclude_paths` | array of strings | Tidak | Pola path sederhana yang dilewati, misalnya `/blog/tag/*`. |
| `options` | object | Tidak | Parameter `/read` apa pun kecuali `url`, diterapkan ke setiap halaman. |

*Contoh: 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);
```

## Ambil status dan hasil job

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

Mengembalikan status job dan hasil berikutnya yang sudah siap. Setiap item `results` berupa respons `/read` lengkap atau `{url, error}` untuk halaman yang tidak dapat dibaca. Jika `next_cursor` ada, panggil lagi dengan `cursor` diisi nilainya; jika tidak ada, belum ada hasil lain yang siap. Nilai-nilai bersifat ilustratif.

Autentikasi: API key di header `Authorization: Bearer API_KEY`.

**Parameter**

| Nama | Di | Tipe | Wajib | Deskripsi |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | Ya | ID yang dikembalikan oleh `/batch` atau `/crawl`. |
| `cursor` | query | string | Tidak | Nilai `next_cursor` dari panggilan sebelumnya, untuk mendapatkan hasil berikutnya. |

**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);
```

## Bagaimana cara menunggu job selesai?

Poll `/jobs/{job_id}` dan simpan cursor. Proses hasil yang Anda terima, lalu panggil lagi dengan `cursor` diisi `next_cursor`. Jika `next_cursor` tidak ada dan `status` adalah `done`, Anda sudah mendapatkan semuanya; jika tidak ada dan job masih `running`, tunggu lalu tanyakan lagi dengan cursor yang sama.

Mulai polling setiap 2 detik dan gandakan jeda hingga 30 detik selama tidak ada hasil baru yang masuk. Crawl yang mematuhi Crawl-delay sebuah situs bisa memakan waktu, jadi menunggu lama itu normal dan tidak memakan biaya.

*Python: poll sebuah job dan simpan setiap halaman sebagai file .md*

```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")

```

## Halaman mana yang dibaca oleh crawl?

Crawl dimulai dari URL Anda dan bergerak keluar satu langkah tautan setiap kali, breadth first, hingga `max_depth`. Crawl juga membaca sitemap situs, dari alamat yang diberikan di robots.txt atau dari `/sitemap.xml`, untuk menemukan halaman yang hanya ditunjuk sedikit tautan.

- **Hanya domain yang sama.** Tautan ke domain lain dicantumkan di hasil saat Anda meminta `include_links`, tetapi tidak dibaca.
- **Filter path.** `include_paths` dan `exclude_paths` menerima pola sederhana seperti `/blog/*`.
- **Satu salinan per halaman.** Jika situs mendeklarasikan canonical URL, halaman yang dicapai melalui beberapa alamat hanya dibaca sekali.
- **Batas keras.** Job berakhir saat telah membaca `limit` halaman.

## Bagaimana crawl menghormati situs?

Setiap request membawa User-Agent `WebReaderAPI/1.0 (+https://webreaderapi.com/bot/)`, dan crawler mengikuti aturan robots.txt untuk `WebReaderAPI`, atau untuk semua robot bila tidak ada yang menyebutkannya. Halaman yang tidak diizinkan dilewati dan muncul di `results` dengan error `blocked_by_robots`; halaman tersebut tidak memakan biaya.

Jika robots.txt menetapkan `Crawl-delay`, crawler menunggu selama itu di antara dua request ke situs. Jika suatu situs mendeklarasikan Content-Signal: ai-input=no di robots.txt-nya, kami tidak membaca halaman tersebut untuk Anda. Jika suatu situs menolak permintaan kami, kami mengembalikan jawabannya: kami tidak pernah menyelesaikan CAPTCHA, masuk ke akun, atau mencoba mengatasi blokir atau paywall.

## Berapa biaya sebuah crawl?

Setiap halaman yang dibaca memakan biaya seperti panggilan `/read`: 1 kredit, 2 jika halaman membutuhkan browser, ditambah 10 dengan `country`. Halaman yang dilewati dan gagal gratis. `credits_used` pada job menjumlahkan apa yang telah ditagih sejauh ini.

Situs dokumentasi 500 halaman yang dibaca tanpa browser menggunakan sekitar 500 kredit, yang ditanggung berkali-kali lipat oleh paket 10.000 halaman (US$3,75). Karena job tidak dapat dibatalkan di v1, `limit` adalah batas pengeluaran Anda: atur ke jumlah halaman maksimum yang siap Anda bayar.

## Batch dan crawl: pertanyaan umum

### Haruskah saya memakai /batch atau banyak panggilan /read?

Gunakan `/batch` bila Anda sudah memiliki URL-nya dan tidak perlu setiap jawaban sekaligus. Ini menghindarkan Anda dari mengelola concurrency dan retry. Gunakan `/read` bila agent membutuhkan satu halaman sekarang.

### Berapa lama hasil job disimpan?

Ambil segera setelah siap. Hasil menunggu sampai Anda mengambilnya; setelah terkirim, tidak ada salinan konten halaman yang disimpan, jadi simpan sendiri apa yang Anda butuhkan.

### Bisakah crawl mengikuti tautan ke website lain?

Tidak. Crawl membaca satu domain. Domain lain muncul sebagai tautan saat Anda memintanya, dan Anda dapat mengirim URL tersebut ke `/batch` jika ingin dibaca.

### Bisakah saya menghentikan crawl yang sudah berjalan?

Tidak di v1. Job berhenti pada `limit` halaman, jadi pilih batas yang sesuai anggaran Anda. Halaman yang sudah dibaca tetap ditagih; halaman yang tidak pernah dibaca tidak memakan biaya.

---

WebReaderAPI: WebReaderAPI adalah API pembaca web untuk AI agent yang mengubah halaman web publik apa pun menjadi Markdown bersih, dibayar dengan kripto tanpa unggah identitas. Kredit prabayar mulai dari US$0,07/1K halaman tidak pernah kedaluwarsa, dan robots.txt dihormati. Tanpa KYC — tanpa identitas, tanpa selfie, tanpa dokumen. Bayar dengan kripto.
