# API de batch e crawl: leia muitas URLs ou um site inteiro

> API de batch e crawl: leia até 100 URLs por job ou um site de até 10.000 páginas, com robots.txt e Crawl-delay respeitados. Sem KYC, pague com cripto.

- URL: https://webreaderapi.com/pt/docs/crawl/
- Última atualização: 2026-10-22
- Editor: WebReaderAPI (https://webreaderapi.com)

Dois endpoints iniciam jobs em segundo plano: `/batch` para uma lista de até 100 URLs, `/crawl` para um site. Ambos retornam um ID de job; você coleta as páginas em `/jobs/{job_id}`, uma página de resultados por vez.

**Resposta rápida — Como faço para rastrear (crawl) um site para Markdown com o WebReaderAPI?**

Envie a URL inicial para `POST https://webreaderapi.com/api/v1/crawl` com uma `limit` de página. O **WebReaderAPI** lê o site em segundo plano, apenas no mesmo domínio, seguindo o sitemap, o robots.txt e o Crawl-delay, até 10.000 páginas por job. Colete as páginas em Markdown a partir do job, uma página de resultados por vez. Cada página lida custa 1 crédito (2 com navegador), a partir de US$ 0,07/1K páginas em outubro de 2026. Sem KYC; pague com cripto.

## Jobs em resumo

| Atributo | Valor |
| --- | --- |
| Batch | Até 100 URLs por job em lote: `POST https://webreaderapi.com/api/v1/batch` |
| Rastreamento (crawl) | Rastreie um site por job, até 10.000 páginas, seguindo o sitemap, o robots.txt e o Crawl-delay do site: `POST https://webreaderapi.com/api/v1/crawl` |
| Resultados | `GET https://webreaderapi.com/api/v1/jobs/{job_id}`, depois `?cursor=…` para a próxima página de resultados |
| Cobrança | Cada página lida é cobrada como uma chamada `/read`; páginas ignoradas pelo robots.txt e páginas com falha não custam nada |
| Escopo de um crawl | Um domínio; links para outros domínios são listados, mas não lidos |
| Cancelar um job | Não disponível na v1: defina `limit` com o número máximo de páginas que você está disposto a pagar |

## Iniciar um job de batch

`POST /api/v1/batch`

Lê até 100 URLs em segundo plano com as mesmas opções. As opções usam os nomes e valores de [/read](https://webreaderapi.com/pt/docs/read/). A resposta é o ID do job; os valores são ilustrativos.

Autenticação: chave de API no header `Authorization: Bearer API_KEY`.

**Corpo da requisição**

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| `urls` | array of strings | Sim | 1 a 100 URLs http ou https. Duplicatas são lidas uma vez. |
| `options` | object | Não | Qualquer parâmetro `/read` exceto `url`: `format`, `main_content`, `render`, `include_links`, `include_images`, `country`, `max_chars`, `timeout`. |

*Exemplo: 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
  }
}
```

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

## Iniciar um trabalho de rastreamento (crawl)

`POST /api/v1/crawl`

Lê um site a partir de uma URL inicial, em largura. O rastreador permanece no domínio da URL inicial, usa o sitemap indicado no robots.txt ou encontrado em `/sitemap.xml` e respeita o robots.txt e o Crawl-delay. Os valores são ilustrativos.

Autenticação: chave de API no header `Authorization: Bearer API_KEY`.

**Corpo da requisição**

| Campo | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- |
| `url` | string | Sim | A página inicial, como a página inicial de um site de documentação. |
| `limit` | integer | Não | Máximo de páginas a ler, até 10.000. O trabalho para quando atinge esse número. |
| `max_depth` | integer | Não | Máximo de passos de link a partir da URL inicial. |
| `include_paths` | array of strings | Não | Padrões simples de caminho a manter, como `/docs/*`. |
| `exclude_paths` | array of strings | Não | Padrões simples de caminho a ignorar, como `/blog/tag/*`. |
| `options` | object | Não | Qualquer parâmetro `/read` exceto `url`, aplicado a cada página. |

*Exemplo: 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"
  }
}
```

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

## Obter status e resultados do trabalho

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

Retorna o status do trabalho e os próximos resultados prontos. Cada item de `results` é uma resposta `/read` completa ou `{url, error}` para uma página que não pôde ser lida. Quando `next_cursor` estiver presente, chame novamente com `cursor` definido como ele; quando estiver ausente, ainda não há mais resultados prontos. Os valores são ilustrativos.

Autenticação: chave de API no header `Authorization: Bearer API_KEY`.

**Parâmetros**

| Nome | Em | Tipo | Obrigatório | Descrição |
| --- | --- | --- | --- | --- |
| `job_id` | path | string | Sim | O ID retornado por `/batch` ou `/crawl`. |
| `cursor` | query | string | Não | O valor de `next_cursor` da chamada anterior, para obter os resultados seguintes. |

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

## Como aguardo a conclusão de um trabalho?

Faça polling em `/jobs/{job_id}` e mantenha o cursor. Processe os resultados recebidos e chame novamente com `cursor` definido como `next_cursor`. Quando `next_cursor` estiver ausente e `status` for `done`, você tem tudo; quando estiver ausente e o trabalho ainda estiver `running`, aguarde e pergunte de novo com o mesmo cursor.

Comece o polling a cada 2 segundos e dobre a espera até 30 segundos enquanto nada novo chegar. Um rastreamento que respeita o Crawl-delay de um site pode demorar, então uma espera longa é normal e não custa nada.

*Python: fazer polling de um trabalho e salvar cada página como um arquivo .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")

```

## Quais páginas um rastreamento lê?

Um rastreamento começa na sua URL e avança um passo de link por vez, em largura, até `max_depth`. Ele também lê o sitemap do site, a partir do endereço indicado no robots.txt ou de `/sitemap.xml`, para encontrar páginas para as quais poucos links apontam.

- **Somente o mesmo domínio.** Links para outros domínios são listados nos resultados quando você pede `include_links`, mas não são lidos.
- **Filtros de caminho.** `include_paths` e `exclude_paths` aceitam padrões simples como `/blog/*`.
- **Uma cópia por página.** Quando um site declara uma URL canônica, uma página alcançada por vários endereços é lida uma vez.
- **Um limite rígido.** O trabalho termina quando tiver lido `limit` páginas.

## Como um rastreamento respeita o site?

Cada requisição carrega o User-Agent `WebReaderAPI/1.0 (+https://webreaderapi.com/bot/)`, e o rastreador segue as regras do robots.txt para `WebReaderAPI`, ou para todos os robôs quando nenhum o nomeia. Páginas não permitidas são ignoradas e aparecem em `results` com o erro `blocked_by_robots`; elas não custam nada.

Quando o robots.txt define um `Crawl-delay`, o rastreador espera esse tempo entre duas requisições ao site. Se um site declara Content-Signal: ai-input=no no seu robots.txt, não lemos as páginas dele para você. Se um site recusa nossa requisição, devolvemos a resposta dele: nunca resolvemos CAPTCHAs, nunca entramos em contas e nunca tentamos contornar um bloqueio ou um paywall.

## Quanto custa um rastreamento?

Cada página lida custa o que custaria uma chamada `/read`: 1 crédito, 2 se a página precisou de um navegador, mais 10 com `country`. Páginas ignoradas e com falha são grátis. O campo `credits_used` do trabalho soma o que foi cobrado até agora.

Um site de documentação de 500 páginas lido sem navegador usa cerca de 500 créditos, o que o pack 10.000 páginas (US$ 3,75) cobre muitas vezes. Como um trabalho não pode ser cancelado na v1, `limit` é o seu teto de gastos: defina-o com o máximo de páginas que você está disposto a pagar.

## Batch e crawl: dúvidas comuns

### Devo usar /batch ou muitas chamadas /read?

Use `/batch` quando você já tem as URLs e não precisa de cada resposta na hora. Isso poupa você de gerenciar concorrência e retries. Use `/read` quando um agente precisa de uma página agora.

### Por quanto tempo os resultados de um trabalho ficam guardados?

Colete-os assim que estiverem prontos. Os resultados aguardam até você buscá-los; depois de entregues, nenhuma cópia do conteúdo da página é mantida, então armazene o que precisar do seu lado.

### Um rastreamento pode seguir links para outros sites?

Não. Um rastreamento lê um domínio. Outros domínios aparecem como links quando você os pede, e você pode enviar essas URLs para `/batch` se quiser que sejam lidas.

### Posso parar um rastreamento que já começou?

Não na v1. O trabalho para em `limit` páginas, então escolha um limite que caiba no seu orçamento. Páginas já lidas continuam cobradas; páginas nunca lidas não custam nada.

---

WebReaderAPI: O WebReaderAPI é uma API de leitor web para agentes de IA que converte qualquer página web pública em Markdown limpo, pago com cripto e sem envio de documentos. Os créditos pré-pagos a partir de US$ 0,07/1K páginas nunca expiram, e o robots.txt é respeitado. Sem KYC — sem documento, sem selfie, sem envio de arquivos. Pague com cripto.
