Respuesta rápida
¿Cómo rastreo un sitio web a Markdown con WebReaderAPI?
Envía la URL de inicio a POST https://webreaderapi.com/api/v1/crawl con una página limit. WebReaderAPI lee el sitio en segundo plano, solo el mismo dominio, siguiendo su sitemap, robots.txt y Crawl-delay, hasta 10.000 páginas por trabajo. Recoge las páginas Markdown del trabajo, una página de resultados a la vez. Cada página leída cuesta 1 crédito (2 con navegador), desde 0,07 US$/1K páginas a octubre de 2026. Sin KYC; paga con criptomonedas.
Trabajos de un vistazo
| Batch | Hasta 100 URLs por trabajo por lotes: POST https://webreaderapi.com/api/v1/batch |
|---|---|
| Rastreo (crawl) | Rastrea un sitio web por trabajo, hasta 10.000 páginas, siguiendo su sitemap, su robots.txt y su Crawl-delay: POST https://webreaderapi.com/api/v1/crawl |
| Resultados | GET https://webreaderapi.com/api/v1/jobs/{job_id}, luego ?cursor=… para la siguiente página de resultados |
| Facturación | Cada página leída se factura como una llamada a /read; las páginas omitidas por robots.txt y las páginas fallidas no cuestan nada |
| Alcance de un rastreo | Un dominio; los enlaces a otros dominios se listan pero no se leen |
| Cancelar un trabajo | No disponible en v1: fija limit en el número máximo de páginas que estás dispuesto a pagar |
Iniciar un trabajo de batch
POST/api/v1/batch
Lee hasta 100 URL en segundo plano con las mismas opciones. Las opciones usan los nombres y valores de /read. La respuesta es el ID del trabajo; los valores son ilustrativos.
Autenticación: clave API en el encabezado Authorization: Bearer API_KEY.
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
urls | array of strings | Sí | De 1 a 100 URLs http o https. Los duplicados se leen una vez. |
options | object | No | Cualquier parámetro /read excepto 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
}
}Respuesta 202 Accepted
{
"job_id": "job_5c1e8a90d2"
}Ejemplos 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 un trabajo de rastreo (crawl)
POST/api/v1/crawl
Lee un sitio web desde una URL de inicio, en anchura (breadth first). El rastreador se mantiene en el dominio de la URL de inicio, usa el sitemap indicado en robots.txt o encontrado en /sitemap.xml, y respeta robots.txt y Crawl-delay. Los valores son ilustrativos.
Autenticación: clave API en el encabezado Authorization: Bearer API_KEY.
Cuerpo de la solicitud
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
url | string | Sí | La página de inicio, como la portada de un sitio de documentación. |
limit | integer | No | Número máximo de páginas a leer, hasta 10.000. El trabajo se detiene al alcanzar este número. |
max_depth | integer | No | Número máximo de pasos de enlace desde la URL de inicio. |
include_paths | array of strings | No | Patrones de ruta simples que conservar, como /docs/*. |
exclude_paths | array of strings | No | Patrones de ruta simples que omitir, como /blog/tag/*. |
options | object | No | Cualquier parámetro /read excepto 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"
}
}Respuesta 202 Accepted
{
"job_id": "job_8b27f41c0e"
}Ejemplos 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);Consultar el estado y los resultados de un trabajo
GET/api/v1/jobs/{job_id}
Devuelve el estado del trabajo y los siguientes resultados que estén listos. Cada elemento de results es una respuesta /read completa o {url, error} para una página que no se pudo leer. Cuando next_cursor está presente, vuelve a llamar con cursor ajustado a él; cuando no está, todavía no hay más resultados listos. Los valores son ilustrativos.
Autenticación: clave API en el encabezado Authorization: Bearer API_KEY.
Parámetros
| Nombre | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
job_id | path | string | Sí | El ID devuelto por /batch o /crawl. |
cursor | query | string | No | El valor de next_cursor de la llamada anterior, para obtener los resultados siguientes. |
Respuesta 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
}Ejemplos 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);¿Cómo espero a que termine un trabajo?
Haz polling a /jobs/{job_id} y conserva el cursor. Procesa los resultados que recibas y vuelve a llamar con cursor ajustado a next_cursor. Cuando next_cursor está ausente y status es done, ya tienes todo; cuando está ausente y el trabajo sigue en running, espera y vuelve a preguntar con el mismo cursor.
Empieza a hacer polling cada 2 segundos y duplica la espera hasta 30 segundos mientras no llegue nada nuevo. Un rastreo que respeta el Crawl-delay de un sitio puede tardar, así que una espera larga es normal y no cuesta 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")
¿Qué páginas lee un rastreo?
Un rastreo parte de tu URL y avanza hacia fuera un paso de enlace a la vez, en anchura, hasta max_depth. También lee el sitemap del sitio, desde la dirección indicada en robots.txt o desde /sitemap.xml, para encontrar páginas a las que apuntan pocos enlaces.
- Solo el mismo dominio. Los enlaces a otros dominios se listan en los resultados cuando pides
include_links, pero no se leen. - Filtros de ruta.
include_pathsyexclude_pathsaceptan patrones simples como/blog/*. - Una copia por página. Cuando un sitio declara una URL canónica, una página alcanzada por varias direcciones se lee una vez.
- Un tope estricto. El trabajo termina cuando ha leído
limitpáginas.
¿Cómo respeta un rastreo al sitio?
Cada solicitud lleva el User-Agent WebReaderAPI/1.0 (+https://webreaderapi.com/bot/), y el rastreador sigue las reglas de robots.txt para WebReaderAPI, o para todos los robots cuando ninguno lo nombra. Las páginas no permitidas se omiten y aparecen en results con el error blocked_by_robots; no cuestan nada.
Cuando robots.txt define un Crawl-delay, el rastreador espera ese tiempo entre dos solicitudes al sitio. Si un sitio declara Content-Signal: ai-input=no en su robots.txt, no leemos sus páginas para ti. Si un sitio rechaza nuestra solicitud, devolvemos su respuesta: nunca resolvemos CAPTCHAs, iniciamos sesión en cuentas ni intentamos saltar un bloqueo o un paywall.
¿Cuánto cuesta un rastreo?
Cada página leída cuesta lo mismo que una llamada /read: 1 crédito, 2 si la página necesitó un navegador, más 10 con country. Las páginas omitidas y las fallidas son gratis. El campo credits_used del trabajo suma lo facturado hasta el momento.
Un sitio de documentación de 500 páginas leído sin navegador usa unos 500 créditos, que el pack 10.000 páginas (3,75 US$) cubre muchas veces. Como un trabajo no se puede cancelar en v1, limit es tu límite de gasto: ajústalo al máximo de páginas que estás dispuesto a pagar.
Batch y crawl: preguntas frecuentes
¿Debo usar /batch o muchas llamadas a /read?
Usa /batch cuando ya tengas las URLs y no necesites cada respuesta de inmediato. Te ahorra gestionar la concurrencia y los reintentos. Usa /read cuando un agente necesite una página ahora.
¿Cuánto tiempo se conservan los resultados de un trabajo?
Recógelos en cuanto estén listos. Los resultados esperan hasta que los descargues; una vez entregados, no se conserva ninguna copia del contenido de la página, así que guarda lo que necesites de tu lado.
¿Puede un rastreo seguir enlaces a otros sitios web?
No. Un rastreo lee un solo dominio. Los otros dominios aparecen como enlaces cuando los pides, y puedes enviar esas URLs a /batch si quieres que se lean.
¿Puedo detener un rastreo que ya ha empezado?
No en v1. El trabajo se detiene a las limit páginas, así que elige un límite que se ajuste a tu presupuesto. Las páginas ya leídas siguen facturándose; las páginas que nunca se leyeron no cuestan nada.