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
| 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. 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. |
{
"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
{
"job_id": "job_5c1e8a90d2"
}Exemplos de código
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"}}'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"])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. |
{
"url": "https://docs.example.com/",
"limit": 500,
"max_depth": 3,
"include_paths": [
"/docs/*"
],
"exclude_paths": [
"/docs/archive/*"
],
"options": {
"format": "markdown"
}
}Resposta 202 Accepted
{
"job_id": "job_8b27f41c0e"
}Exemplos de código
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/*"]}'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"])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
{
"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
}Exemplos de código
curl -sG "https://webreaderapi.com/api/v1/jobs/job_8b27f41c0e" \
-H "Authorization: Bearer $WEBREADER_API_KEY" \
-d cursor=c_000002import 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"))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.
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_pathseexclude_pathsaceitam 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
limitpá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.