---
seo_title: Sitio Web a PDF API | Rastrea un Sitio Completo | EnConvert
meta_desc: Rastrea un sitio web y convierte cada página a PDF con POST /v1/convert/website-to-pdf. Sitemap o rastreo completo, salida ZIP, asíncrono con webhooks.
keywords: convertir sitio web completo a pdf api, api sitio web a pdf, archivar sitio web completo como pdf, convertir todas las páginas de un sitio web a pdf, convertidor de sitemap a pdf api, exportar pdf de rastreo de sitio completo, api para pdf masivo de páginas web, guardar sitio web completo como pdf mediante api
---

# 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](#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:

1. Obtiene `{base_url}/sitemap.xml` (tiempo de espera de 30 segundos)
2. Si el elemento raíz es `<sitemapindex>`, obtiene recursivamente cada sitemap hijo
3. Extrae todas las entradas `<url><loc>` de los elementos `<urlset>`
4. 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:**

1. Analiza `robots.txt` en busca de directivas de sitemap y reglas de rastreo
2. Comprueba las rutas estándar de sitemap (`/sitemap.xml`, `/wp-sitemap.xml`, `/sitemap_index.xml`, etc.)
3. Descubre feeds RSS/Atom a partir de etiquetas `<link>` y rutas de feed comunes
4. Extrae URLs semilla de todas las fuentes descubiertas

**Fase 2 -- Rastreo de enlaces en anchura:**

1. Comienza desde la URL base más todas las URLs semilla
2. Visita cada página y encola los enlaces del mismo dominio
3. Aplica `include_patterns` y `exclude_patterns` para filtrar enlaces
4. Respeta las reglas de `robots.txt`
5. Detecta y evita trampas de URL infinitas (páginas de calendario, filtros facetados, etc.)
6. 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)

```json
{
    "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](/es/docs/endpoints/convert/web-pages/url-to-pdf.md#batch-status-polling-private-keys-only) 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:

```json
{
    "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 |

<div class="alert alert-warning">
<strong>Plan Founding:</strong> La captura de sitios web no está disponible en el plan gratuito. Intentar usar este endpoint devuelve <code>403 Forbidden</code>.
</div>

---

## Ejemplos de código

### Python (clave privada)

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

```php
$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)

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

```go
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

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

```json
{
    "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.
