API de Sitio Web a PDF#
El endpoint POST /v1/convert/website-to-pdf rastrea un sitio web completo (mediante el análisis del sitemap o un rastreo completo en anchura), convierte cada página descubierta a un PDF de alta fidelidad y agrupa los resultados en un único archivo ZIP. Los trabajos siempre se ejecutan de forma asíncrona: la API devuelve HTTP 202 con un batch_id de inmediato, la finalización se señala mediante sondeo del estado del lote, un callback de webhook o una notificación por correo electrónico, y la respuesta del estado del lote incluye una URL de descarga prefirmada para el ZIP terminado. Requiere un plan de pago y una clave de API privada.
Endpoint#
POST /v1/convert/website-to-pdf
Content-Type: application/json
Formato de salida: Archivo ZIP que contiene un PDF por cada página descubierta.
Modo: Siempre asíncrono. Devuelve HTTP 202 de inmediato.
Autenticación#
Este endpoint requiere una clave de API privada. Las claves públicas no son compatibles con la captura de sitios web.
X-API-Key: sk_your_private_key
Parámetros de la solicitud#
Parámetros de descubrimiento del sitio web#
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de plan |
|---|---|---|---|---|---|
url |
string |
Sí | -- | La URL base del sitio web (p. ej. https://example.com). Se usa como raíz para el descubrimiento de páginas. |
-- |
crawl_mode |
string |
No | "auto" |
Método de descubrimiento de URLs. Uno de "auto", "sitemap" o "full". Consulta Modos de rastreo más abajo. |
Sitemap requiere Indie+, Full requiere Studio+ |
include_patterns |
string[] |
No | null |
Patrones regex para incluir en la lista blanca las URLs descubiertas. Solo se usa en el modo de rastreo full. |
-- |
exclude_patterns |
string[] |
No | Valores predeterminados del sistema | Patrones regex para excluir URLs (lista negra). Solo se usa en el modo de rastreo full. Cuando se omite, usa los valores predeterminados integrados que excluyen recursos estáticos, páginas de login/admin/carrito y paginación profunda. |
-- |
Parámetros de notificación#
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de plan |
|---|---|---|---|---|---|
output_filename |
string |
No | Generado automáticamente | Nombre base personalizado para el archivo ZIP de salida. La marca de tiempo se añade automáticamente. | -- |
notification_email |
string |
No | Correo del propietario del proyecto | Dirección de correo electrónico a la que notificar cuando finalice el trabajo. | -- |
callback_url |
string |
No | -- | URL de webhook que recibirá una solicitud POST al finalizar. | Requiere acceso a webhooks |
Parámetros de navegador y renderizado#
Estos ajustes se aplican a la conversión de cada página individual dentro del sitio web.
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de plan |
|---|---|---|---|---|---|
viewport_width |
integer |
No | 1920 |
Ancho del viewport del navegador en píxeles. | -- |
viewport_height |
integer |
No | 1080 |
Alto del viewport del navegador en píxeles. | -- |
single_page |
boolean |
No | true |
true renderiza cada página como una única página PDF continua. false produce una salida paginada usando el tamaño de página de pdf_options. |
-- |
load_media |
boolean |
No | true |
Espera a que todas las imágenes y videos se carguen por completo antes de la conversión. | -- |
enable_scroll |
boolean |
No | true |
Desplaza cada página para activar el contenido de carga diferida (lazy-loading). | -- |
handle_sticky_header |
boolean |
No | true |
Detecta encabezados fijos/sticky y los gestiona antes de la captura. | -- |
handle_cookies |
boolean |
No | true |
Cierra automáticamente los avisos de consentimiento de cookies. | -- |
wait_for_images |
boolean |
No | true |
Espera a que todos los elementos <img> terminen de cargar. |
-- |
wait_for_selector |
string |
No | null |
Selector CSS que se espera antes de la captura, aplicado a cada página. Devuelve 422 si nunca aparece dentro de wait_for_selector_timeout. Útil para SPAs que hidratan el contenido después de la carga. |
-- |
wait_for_selector_timeout |
integer |
No | 10000 |
Milisegundos de espera para wait_for_selector (máximo 60000). |
-- |
block_ads |
boolean |
No | false |
Aborta las solicitudes a dominios conocidos de anuncios/rastreadores para que nunca se carguen, rendericen ni ralenticen la captura. | -- |
block_media |
boolean |
No | false |
Aborta por completo las solicitudes de imágenes y audio/vídeo para un renderizado más rápido y ligero. A diferencia de load_media (que solo controla la espera), esto evita por completo que los medios se descarguen. |
-- |
Autenticación y solicitudes personalizadas#
| Parámetro | Tipo | Obligatorio | Predeterminado | Descripción | Restricción de plan |
|---|---|---|---|---|---|
auth |
object |
No | null |
Credenciales de HTTP Basic Auth aplicadas a cada página. Formato: {"username": "...", "password": "..."}. |
Requiere acceso a autenticación básica |
cookies |
array |
No | null |
Array de objetos de cookies inyectados antes de cada carga de página. Máximo 50 cookies. | Requiere acceso a autenticación básica |
headers |
object |
No | null |
Encabezados HTTP personalizados enviados con cada solicitud. Máximo 20 encabezados. | Requiere acceso a autenticación básica |
Opciones de PDF#
Pasa estas opciones dentro de un objeto pdf_options. Se aplican a cada página del sitio web.
| Parámetro | Tipo | Predeterminado | Descripción |
|---|---|---|---|
page_size |
string |
"A4" |
Tamaño de página con nombre. Se ignora cuando page_width y page_height están ambos definidos. |
page_width |
float |
null |
Ancho de página personalizado en milímetros. page_width y page_height deben definirse juntos. |
page_height |
float |
null |
Alto de página personalizado en milímetros. |
orientation |
string |
"portrait" |
"portrait" o "landscape". |
margins |
object |
{"top": 10, "bottom": 10, "left": 10, "right": 10} |
Márgenes de página en milímetros. |
scale |
float |
1.0 |
Factor de escala del contenido. Rango: 0.1 a 2.0. Solo en modo paginado. |
grayscale |
boolean |
false |
Convierte cada página PDF a escala de grises. |
header |
object |
null |
Encabezado de página para el modo paginado. Formato: {"content": "<html>", "height": 15}. Admite variables de plantilla: {{page}}, {{total_pages}}, {{date}}, {{title}}, {{url}}. |
footer |
object |
null |
Pie de página para el modo paginado. Mismo formato que header. |
Tamaños de página admitidos: A0, A1, A2, A3, A4, A5, A6, B0, B1, B2, B3, B4, B5, Letter, Legal, Tabloid, Ledger
Modos de rastreo#
"auto" (predeterminado)#
Usa el modo de rastreo más alto que permite tu plan. Si tu plan admite el rastreo completo, ejecuta un rastreo completo. Si tu plan solo admite sitemap, ejecuta el descubrimiento por sitemap.
"sitemap"#
Descubre páginas analizando el sitemap.xml del sitio web:
- Obtiene
{base_url}/sitemap.xml(tiempo de espera de 30 segundos) - Si el elemento raíz es
<sitemapindex>, obtiene recursivamente cada sitemap hijo - Extrae todas las entradas
<url><loc>de los elementos<urlset> - Devuelve la lista completa de URLs descubiertas
Devuelve un error si el sitemap no existe, devuelve un estado distinto de 200, contiene XML inválido o no tiene URLs.
"full"#
Realiza un rastreo exhaustivo en dos fases:
Fase 1 -- Descubrimiento de semillas:
- Analiza
robots.txten busca de directivas de sitemap y reglas de rastreo - Comprueba las rutas estándar de sitemap (
/sitemap.xml,/wp-sitemap.xml,/sitemap_index.xml, etc.) - Descubre feeds RSS/Atom a partir de etiquetas
<link>y rutas de feed comunes - Extrae URLs semilla de todas las fuentes descubiertas
Fase 2 -- Rastreo de enlaces en anchura:
- Comienza desde la URL base más todas las URLs semilla
- Visita cada página y encola los enlaces del mismo dominio
- Aplica
include_patternsyexclude_patternspara filtrar enlaces - Respeta las reglas de
robots.txt - Detecta y evita trampas de URL infinitas (páginas de calendario, filtros facetados, etc.)
- Deduplica URLs normalizando el esquema, el host y los parámetros de consulta, y eliminando parámetros de seguimiento (
utm_*,fbclid,gclid, etc.)
Patrones de exclusión predeterminados (cuando no se proporciona exclude_patterns):
- Recursos estáticos:
*.pdf,*.zip,*.jpg,*.png,*.gif,*.svg,*.css,*.js,*.xml,*.json,*.mp4,*.webm,*.woff,*.woff2 - Rutas protegidas:
/login,/admin,/cart,/checkout - Paginación profunda: URLs con parámetros
page=que superan los 3 dígitos
Respuesta#
202 Accepted (inmediata)#
{
"status": "processing",
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"url_count": 42,
"total_discovered": 42,
"discovery_method": "sitemap",
"output_format": "zip"
}
| Campo | Descripción |
|---|---|
batch_id |
UUID para hacer seguimiento del trabajo mediante sondeo del estado del lote o webhook. |
url_count |
Número de páginas que se convertirán. |
total_discovered |
Total de páginas descubiertas por el rastreo. |
discovery_method |
"sitemap" o "full_crawl" según el modo de rastreo efectivo. |
Sondeo del estado del lote#
Sondea con el batch_id de la respuesta 202:
GET /v1/convert/batch/{batch_id}
X-API-Key: sk_your_private_key
Devuelve el estado agregado, los estados por URL y una URL de descarga prefirmada para el ZIP cuando se complete. Consulta Sondeo del estado del lote para ver el esquema completo de la respuesta.
Payload del callback de webhook#
Cuando se proporciona callback_url, EnConvert envía una solicitud POST al finalizar:
{
"job_id": "batch-uuid",
"status": "success",
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"gcs_uri": "env/files/{project_id}/url-to-pdf/website_20260405_123456789.zip",
"filename": "website_20260405_123456789.zip",
"file_size": 12345678,
"total_tasks": 42,
"successful_tasks": 40,
"failed_tasks": 2,
"tasks": [
{"url": "https://example.com/", "status": "success", "filename": "example_20260405_001.pdf"},
{"url": "https://example.com/about", "status": "success", "filename": "example_20260405_002.pdf"},
{"url": "https://example.com/broken", "status": "failed", "error": "Timeout"}
]
}
Notificación por correo electrónico#
Se envía un correo de finalización a notification_email (o al correo del propietario del proyecto de forma predeterminada) cuando el trabajo finaliza, independientemente del éxito o el fracaso.
Restricciones por plan de suscripción#
| Función | Founding | Indie | Studio | Enterprise |
|---|---|---|---|---|
| Captura de sitios web | No | Sí | Sí | Sí |
| Modo de rastreo por sitemap | No | Sí | Sí | Sí |
| Modo de rastreo completo | No | No | Sí | Sí |
| Callbacks de webhook | No | No | Sí | Sí |
| HTTP Basic Auth | No | Sí | Sí | Sí |
| Inyección de cookies | No | Sí | Sí | Sí |
| Encabezados personalizados | No | Sí | Sí | Sí |
| Límite de tamaño de lote | 0 | Según el plan | Según el plan | Sin límite |
| Conversiones mensuales | 100 | Según el plan | Según el plan | Sin límite |
403 Forbidden.
Ejemplos de código#
Python (clave privada)#
import requests
import time
# Start the website capture
response = requests.post(
"https://api.enconvert.com/v1/convert/website-to-pdf",
headers={"X-API-Key": "sk_your_private_key"},
json={
"url": "https://example.com",
"crawl_mode": "sitemap",
"output_filename": "example-website",
"pdf_options": {
"page_size": "A4",
"orientation": "portrait"
}
}
)
data = response.json()
print(f"Batch ID: {data['batch_id']}")
print(f"Pages found: {data['url_count']}")
# Poll for completion
batch_id = data["batch_id"]
while True:
status = requests.get(
f"https://api.enconvert.com/v1/convert/batch/{batch_id}",
headers={"X-API-Key": "sk_your_private_key"}
).json()
print(f"Status: {status['status']} ({status['completed']}/{status['total']})")
if status["status"] in ("completed", "partial", "failed"):
if status.get("zip_download_url"):
print(f"Download: {status['zip_download_url']}")
break
time.sleep(5)
PHP (clave privada)#
$ch = curl_init("https://api.enconvert.com/v1/convert/website-to-pdf");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: sk_your_private_key"
],
CURLOPT_POSTFIELDS => json_encode([
"url" => "https://example.com",
"crawl_mode" => "sitemap",
"output_filename" => "example-website",
"pdf_options" => [
"page_size" => "A4",
"orientation" => "portrait"
]
])
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Batch ID: " . $response["batch_id"] . "\n";
echo "Pages found: " . $response["url_count"] . "\n";
Node.js (clave privada)#
const response = await fetch("https://api.enconvert.com/v1/convert/website-to-pdf", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "sk_your_private_key"
},
body: JSON.stringify({
url: "https://example.com",
crawl_mode: "sitemap",
output_filename: "example-website",
pdf_options: {
page_size: "A4",
orientation: "portrait"
}
})
});
const data = await response.json();
console.log(`Batch ID: ${data.batch_id}`);
console.log(`Pages found: ${data.url_count}`);
// Poll for completion
const poll = async () => {
const status = await fetch(
`https://api.enconvert.com/v1/convert/batch/${data.batch_id}`,
{ headers: { "X-API-Key": "sk_your_private_key" } }
).then(r => r.json());
console.log(`Status: ${status.status} (${status.completed}/${status.total})`);
if (["completed", "partial", "failed"].includes(status.status)) {
if (status.zip_download_url) console.log(`Download: ${status.zip_download_url}`);
return;
}
setTimeout(poll, 5000);
};
poll();
Go (clave privada)#
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
body, _ := json.Marshal(map[string]interface{}{
"url": "https://example.com",
"crawl_mode": "sitemap",
"output_filename": "example-website",
"pdf_options": map[string]interface{}{
"page_size": "A4",
"orientation": "portrait",
},
})
req, _ := http.NewRequest("POST", "https://api.enconvert.com/v1/convert/website-to-pdf", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", "sk_your_private_key")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
Con callback de webhook#
{
"url": "https://example.com",
"crawl_mode": "full",
"callback_url": "https://your-server.com/webhook/enconvert",
"output_filename": "example-full-site",
"include_patterns": [".*\\/blog\\/.*", ".*\\/docs\\/.*"],
"pdf_options": {
"page_size": "Letter",
"margins": {"top": 20, "bottom": 20, "left": 15, "right": 15}
}
}
Con autenticación (sitio protegido con contraseña)#
{
"url": "https://staging.example.com",
"crawl_mode": "sitemap",
"auth": {
"username": "admin",
"password": "staging-password"
},
"cookies": [
{"name": "session_token", "value": "abc123", "domain": "staging.example.com"}
]
}
Respuestas de error#
| Estado | Condición |
|---|---|
400 Bad Request |
Falta el parámetro url o está vacío |
400 Bad Request |
No se encontraron URLs en el sitemap |
400 Bad Request |
Tiempo de espera agotado al obtener el sitemap (límite de 30 segundos) |
400 Bad Request |
Respuesta distinta de 200 desde la URL del sitemap |
400 Bad Request |
XML inválido en el sitemap |
400 Bad Request |
Formato de sitemap no reconocido |
400 Bad Request |
No se descubrieron páginas (el rastreo completo encontró cero URLs) |
400 Bad Request |
Estructura de auth, cookies o headers inválida |
402 Payment Required |
El número de páginas descubiertas supera la cuota mensual de ops |
402 Payment Required |
Se alcanzó el límite de almacenamiento |
403 Forbidden |
El rastreo de sitios web no está disponible en el plan actual (plan Founding) |
403 Forbidden |
El modo de rastreo completo requiere el plan Studio o superior |
403 Forbidden |
El número de páginas descubiertas supera el límite de tamaño de lote |
403 Forbidden |
Función no disponible en el plan (webhook, autenticación básica) |
500 Internal Server Error |
Fallo de rastreo o conversión |
Límites#
| Límite | Valor |
|---|---|
| Tiempo de espera para obtener el sitemap | 30 segundos |
| Tiempo de espera global del rastreo (modo full) | 10 minutos |
| Profundidad máxima de rastreo (modo full) | 10 niveles |
| Tiempo de espera de rastreo por página (modo full) | 30 segundos |
| Límite de memoria del rastreador | 512 MB |
| Umbral de trampa infinita | 20 URLs por patrón de URL |
| Tiempo de espera para obtener robots.txt | 10 segundos |
| Máximo de páginas por rastreo | Límite de tamaño de lote del plan |
| Máximo de cookies por solicitud | 50 |
| Máximo de encabezados personalizados por solicitud | 20 |
| Tiempo de espera de entrega del webhook | 30 segundos |
| Conversiones mensuales | Según el plan |
| Retención de archivos | Según el plan |
Preguntas frecuentes#
¿Cómo convierto un sitio web completo a PDF con una API?#
Envía una solicitud POST a /v1/convert/website-to-pdf con la url base del sitio y tu clave privada en el encabezado X-API-Key. La API descubre todas las páginas (sitemap o rastreo completo), convierte cada una a PDF, las agrupa en un ZIP y devuelve HTTP 202 con un batch_id que puedes sondear para obtener el enlace de descarga.
¿Cuál es la diferencia entre el modo de rastreo por sitemap y el modo completo?#
crawl_mode: "sitemap" analiza el sitemap.xml del sitio (incluidos los índices de sitemap anidados) y está disponible en planes Indie y superiores. crawl_mode: "full" ejecuta un rastreo de dos fases: primero el descubrimiento de semillas a partir de robots.txt, sitemaps y feeds RSS/Atom, y después un rastreo de enlaces en anchura dentro del mismo dominio con filtrado mediante include_patterns/exclude_patterns. Este modo requiere el plan Studio o superior. El valor predeterminado "auto" usa el modo más alto que permite tu plan.
¿Cómo sé cuándo ha terminado mi trabajo de sitio web a PDF?#
Sondea GET /v1/convert/batch/{batch_id} con tu clave privada para obtener el estado agregado, los estados por URL y una URL de descarga prefirmada del ZIP, o pasa un callback_url para recibir un POST de webhook al finalizar. También se envía un correo de finalización a notification_email (o al propietario del proyecto de forma predeterminada) independientemente del éxito o el fracaso.
¿Puedo archivar como PDF un sitio protegido con contraseña o de staging?#
Sí, en planes con acceso a autenticación básica: pasa auth con username y password para aplicar HTTP Basic Auth a cada página, inyecta hasta 50 cookies de sesión o envía hasta 20 headers personalizados.
¿Por qué el endpoint de sitio web a PDF devuelve 403 Forbidden?#
Las causas más comunes: la captura de sitios web no está disponible en el plan Founding, crawl_mode: "full" requiere el plan Studio o superior, el número de páginas descubiertas supera el límite de tamaño de lote de tu plan, o una función solicitada (webhook, autenticación básica) no está incluida en tu plan.