---
seo_title: API de Conversión por Lotes: URL a PDF y ZIP Masivo | EnConvert
meta_desc: Convierte varias URLs a PDF o capturas de pantalla en una solicitud con POST /v1/convert/url-to-pdf. API de conversión por lotes con salida ZIP, webhooks y sondeo.
keywords: api conversión de archivos por lotes, convertir varias urls a pdf api, api conversión masiva a pdf zip, api capturas de pantalla por lotes, consultar estado de lote api, api salida zip pdf, conversión asíncrona por lotes api, webhook para conversión por lotes
---

# API de Conversión de Archivos por Lotes

La API de lotes de EnConvert convierte varias URLs en una sola solicitud: pasa un array de URLs a `/v1/convert/url-to-pdf` o `/v1/convert/url-to-screenshot` y recibe una respuesta HTTP 202 con un `batch_id`. Cada URL se convierte de forma asíncrona en segundo plano, y los resultados se entregan como URLs de descarga prefirmadas, ya sea una por archivo o agrupadas en un único archivo ZIP. Sigue el progreso consultando `GET /v1/convert/batch/{batch_id}`, o recibe una notificación por webhook o email al completarse.

<div class="alert alert-warning">
<strong>Solo claves privadas:</strong> El procesamiento por lotes solo está disponible al autenticarte con una <strong>clave de API privada</strong> (<code>X-API-Key: sk_...</code>). Las claves públicas están restringidas a solicitudes síncronas de una sola URL.
</div>

---

## Cómo Funciona

1. Envía una solicitud con un **array de URLs** en el parámetro `url` a `/v1/convert/url-to-pdf` o `/v1/convert/url-to-screenshot`.
2. La API valida el lote según los límites de tu plan y devuelve **HTTP 202** con un `batch_id`.
3. Cada URL se convierte en segundo plano y factura una op. La cantidad total del lote se verifica previamente contra tu cuota mensual de ops restante antes de que comience cualquier procesamiento.
4. Sigue el progreso mediante `GET /v1/convert/batch/{batch_id}`, o recibe una notificación por webhook o email al completarse.
5. Descarga los resultados mediante URLs prefirmadas en la respuesta de estado del lote.

---

## Modos de Salida

### Modo Individual (Predeterminado)

Cada URL genera un archivo separado. Cada archivo obtiene su propia URL de descarga prefirmada en la respuesta de estado del lote.

**Solicitud:**

```bash
curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": [
      "https://example.com/page-1",
      "https://example.com/page-2",
      "https://example.com/page-3"
    ]
  }'
```

**Respuesta (HTTP 202 Accepted):**

```json
{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 3,
    "output_format": "individual"
}
```

<div class="alert alert-info">
<strong>Nota:</strong> Al pasar varias URLs, <code>async_mode</code> se establece automáticamente en <code>true</code>, independientemente de si lo incluyes explícitamente en la solicitud.
</div>

### Modo de Paquete ZIP

Establece `output_format` en `true` para recibir todos los archivos convertidos agrupados en un único archivo ZIP. Requiere un plan con acceso a salida ZIP.

**Solicitud:**

```bash
curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": [
      "https://example.com/page-1",
      "https://example.com/page-2",
      "https://example.com/page-3"
    ],
    "output_format": true,
    "output_filename": "monthly-reports"
  }'
```

**Respuesta (HTTP 202 Accepted):**

```json
{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 3,
    "output_format": "zip"
}
```

En modo ZIP, todas las URLs se procesan secuencialmente, y los resultados exitosos se agrupan en un único archivo ZIP llamado `{output_filename}_{timestamp}.zip` (o `batch_{timestamp}.zip` si no se proporciona un nombre personalizado).

---

## Parámetros del Lote

| Parámetro | Tipo | Predeterminado | Descripción | Restricción de Plan |
|-----------|------|---------|-------------|-------------|
| `url` | `string[]` | *(obligatorio)* | Array de URLs a convertir. | -- |
| `output_format` | `boolean` | `false` | Establece en `true` para agrupar todos los resultados en un archivo ZIP. Requiere varias URLs. | Requiere acceso a salida ZIP |
| `output_filename` | `string` | Autogenerado | Nombre de archivo personalizado para la salida. En modo ZIP, este nombra el archivo ZIP. | -- |
| `async_mode` | `boolean` | `true` (implícito) | Siempre `true` para lotes. Se habilita automáticamente cuando se proporcionan varias URLs. | Requiere acceso asíncrono |
| `notification_email` | `string` | Email del propietario del proyecto | Dirección de correo a notificar al completarse. Si se omite, usa por defecto el email del propietario del proyecto. | -- |
| `callback_url` | `string` | `null` | URL de webhook para recibir un POST al completarse. | Requiere acceso a webhooks |
| `direct_download` | -- | -- | **No compatible** con lotes. Devuelve un error 400 si se establece con varias URLs. | -- |

### Parámetros de Navegador y Renderizado

Estos ajustes se aplican a cada URL del lote:

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `viewport_width` | `integer` | `1920` | Ancho del viewport del navegador en píxeles. |
| `viewport_height` | `integer` | `1080` | Alto del viewport del navegador en píxeles. |
| `single_page` | `boolean` | `true` | Renderiza como una única página continua (solo url-to-pdf). |
| `load_media` | `boolean` | `true` | Espera a que las imágenes y los medios se carguen. |
| `enable_scroll` | `boolean` | `true` | Desplaza las páginas para activar el contenido de carga diferida. |
| `handle_sticky_header` | `boolean` | `true` | Detecta y gestiona encabezados fijos/sticky. |
| `handle_cookies` | `boolean` | `true` | Descarta automáticamente los banners de consentimiento de cookies. |
| `wait_for_images` | `boolean` | `true` | Espera a que todas las imágenes terminen de cargarse. |

### Autenticación y Solicitudes Personalizadas

Se aplican a cada URL del lote. Requieren un plan con acceso a autenticación básica.

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `auth` | `object` | `null` | Credenciales de HTTP Basic Auth: `{"username": "...", "password": "..."}`. |
| `cookies` | `array` | `null` | Array de objetos de cookies inyectados antes de cada carga de página. Máx. 50. |
| `headers` | `object` | `null` | Encabezados HTTP personalizados enviados con cada solicitud. Máx. 20. |

### Opciones de PDF (solo url-to-pdf)

Pasa un objeto `pdf_options` para controlar el formato de salida del PDF de cada página del lote:

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `page_size` | `string` | `"A4"` | Tamaño de página con nombre. |
| `orientation` | `string` | `"portrait"` | `"portrait"` o `"landscape"`. |
| `margins` | `object` | `{"top": 10, "bottom": 10, "left": 10, "right": 10}` | Márgenes en mm. |
| `grayscale` | `boolean` | `false` | Salida en escala de grises mediante Ghostscript. |

---

## Consulta del Estado del Lote {: #batch-status-polling }

Usa el endpoint de estado del lote para verificar el progreso y obtener las URLs de descarga.

```
GET /v1/convert/batch/{batch_id}
X-API-Key: sk_your_private_key
```

Reemplaza `{batch_id}` con el `batch_id` devuelto por la solicitud inicial.

### Estado de Procesamiento

Mientras las conversiones siguen en curso:

```json
{
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "processing",
    "total": 3,
    "completed": 1,
    "failed": 0,
    "in_progress": 2,
    "output_mode": "individual",
    "zip_download_url": null,
    "items": [
        {
            "source_url": "https://example.com/page-1",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 184320,
            "duration": "3.12"
        },
        {
            "source_url": "https://example.com/page-2",
            "status": "In Progress",
            "download_url": null,
            "output_file_size": null,
            "duration": null
        },
        {
            "source_url": "https://example.com/page-3",
            "status": "In Progress",
            "download_url": null,
            "output_file_size": null,
            "duration": null
        }
    ]
}
```

### Estado Completado

Cuando todas las URLs se han convertido correctamente:

```json
{
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "total": 3,
    "completed": 3,
    "failed": 0,
    "in_progress": 0,
    "output_mode": "individual",
    "zip_download_url": null,
    "items": [
        {
            "source_url": "https://example.com/page-1",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 184320,
            "duration": "3.12"
        },
        {
            "source_url": "https://example.com/page-2",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 210944,
            "duration": "4.55"
        },
        {
            "source_url": "https://example.com/page-3",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 97280,
            "duration": "2.87"
        }
    ]
}
```

### Estado Parcial

Cuando todas las URLs han finalizado pero algunas fallaron:

```json
{
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "partial",
    "total": 3,
    "completed": 2,
    "failed": 1,
    "in_progress": 0,
    "output_mode": "individual",
    "zip_download_url": null,
    "items": [
        {
            "source_url": "https://example.com/page-1",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 184320,
            "duration": "3.12"
        },
        {
            "source_url": "https://example.com/page-2",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 210944,
            "duration": "4.55"
        },
        {
            "source_url": "https://invalid-url.example",
            "status": "Failed",
            "download_url": null,
            "output_file_size": null,
            "duration": "0.87"
        }
    ]
}
```

### Modo ZIP Completado

En modo ZIP, se proporciona un único `zip_download_url` para todo el archivo:

```json
{
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "total": 3,
    "completed": 3,
    "failed": 0,
    "in_progress": 0,
    "output_mode": "zip",
    "zip_download_url": "https://spaces.example.com/...",
    "items": [
        {
            "source_url": "https://example.com/page-1",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 184320,
            "duration": "3.12"
        },
        {
            "source_url": "https://example.com/page-2",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 210944,
            "duration": "4.55"
        },
        {
            "source_url": "https://example.com/page-3",
            "status": "Success",
            "download_url": "https://spaces.example.com/...",
            "output_file_size": 97280,
            "duration": "2.87"
        }
    ]
}
```

### Valores de Estado del Lote

| Estado | Significado |
|--------|---------|
| `processing` | Al menos una URL todavía se está convirtiendo. |
| `completed` | Todas las URLs se convirtieron correctamente. |
| `partial` | Todas las URLs finalizaron, pero algunas fallaron. |
| `failed` | Todas las URLs fallaron. |

---

## Callbacks de Webhook

Proporciona un `callback_url` en la solicitud para recibir una notificación POST automática al completarse. El webhook se envía con `Content-Type: application/json` y un timeout de 30 segundos. No se realizan reintentos si falla la entrega.

### Callback en Modo Individual

En modo individual, se envía un **POST de webhook separado por cada URL** a medida que se completa:

```json
{
    "job_id": "12345",
    "status": "success",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "gcs_uri": "env/files/{project_id}/url-to-pdf/page1_20260405_123456789.pdf",
    "filename": "page1_20260405_123456789.pdf",
    "file_size": 184320
}
```

### Callback en Modo ZIP

En modo ZIP, se envía un **único POST de webhook cuando se completa todo el lote**:

```json
{
    "job_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "success",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "gcs_uri": "env/files/{project_id}/url-to-pdf/batch_20260405_123456789.zip",
    "filename": "batch_20260405_123456789.zip",
    "file_size": 456789,
    "total_tasks": 3,
    "successful_tasks": 2,
    "failed_tasks": 1,
    "tasks": [
        {"url": "https://example.com/page-1", "status": "success", "filename": "page1.pdf"},
        {"url": "https://example.com/page-2", "status": "success", "filename": "page2.pdf"},
        {"url": "https://invalid.example", "status": "failed", "error": "Page load timeout"}
    ]
}
```

<div class="alert alert-info">
<strong>Diferencia en el comportamiento de las notificaciones:</strong> En modo individual, recibes N POSTs de webhook separados (uno por URL) y N emails separados. En modo ZIP, recibes un POST de webhook y un email para todo el lote. Ten esto en cuenta al diseñar tu manejador de webhooks.
</div>

---

## Notificaciones por Email

Se envía un email de finalización a `notification_email` cuando el lote termina. Si no se proporciona `notification_email`, el email se envía por defecto al correo del propietario del proyecto.

El email incluye:

- Estado del trabajo (success/failed) con un banner de color
- ID del Lote
- Para trabajos por lotes: una tabla que enumera cada URL, su estado y el nombre del archivo de salida
- Un enlace para descargar los resultados desde el panel de control

---

## Restricciones por Plan de Suscripción

| Función | Founding | Indie | Studio | Enterprise |
|---------|------|---------|-----|------------|
| Procesamiento por lotes | No | Sí | Sí | Sí |
| Modo asíncrono | No | Sí | Sí | Sí |
| Agrupación de salida ZIP | No | No | Sí | Sí |
| Callbacks de webhook | No | No | Sí | Sí |
| HTTP Basic Auth / Cookies / Headers | No | Sí | Sí | Sí |
| Límite de tamaño de lote | 0 | Según el plan | Según el plan | Ilimitado |
| Conversiones mensuales | 100 | Según el plan | Según el plan | Ilimitado |

---

## Ejemplos de Código

### Python -- Modo Individual con Sondeo

```python
import requests
import time

# Submit batch
response = requests.post(
    "https://api.enconvert.com/v1/convert/url-to-pdf",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": [
            "https://example.com/page-1",
            "https://example.com/page-2",
            "https://example.com/page-3"
        ]
    }
)

data = response.json()
batch_id = data["batch_id"]
print(f"Batch started: {batch_id} ({data['url_count']} URLs)")

# Poll for completion
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"):
        for item in status["items"]:
            if item["download_url"]:
                print(f"  {item['source_url']} -> {item['download_url']}")
        break

    time.sleep(5)
```

### Python -- Modo ZIP con Webhook

```python
import requests

response = requests.post(
    "https://api.enconvert.com/v1/convert/url-to-pdf",
    headers={"X-API-Key": "sk_your_private_key"},
    json={
        "url": [
            "https://example.com/page-1",
            "https://example.com/page-2",
            "https://example.com/page-3"
        ],
        "output_format": True,
        "output_filename": "monthly-reports",
        "callback_url": "https://your-server.com/webhook/enconvert",
        "pdf_options": {
            "page_size": "A4",
            "orientation": "portrait"
        }
    }
)

data = response.json()
print(f"Batch started: {data['batch_id']}")
print(f"URLs: {data['url_count']}, Format: {data['output_format']}")
# Results will be delivered to your webhook URL
```

### Node.js -- Capturas de Pantalla por Lotes

```javascript
const response = await fetch("https://api.enconvert.com/v1/convert/url-to-screenshot", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_your_private_key"
    },
    body: JSON.stringify({
        url: [
            "https://example.com/page-1",
            "https://example.com/page-2",
            "https://example.com/page-3"
        ],
        output_format: true,
        output_filename: "screenshots-bundle"
    })
});

const data = await response.json();
console.log(`Batch ${data.batch_id}: ${data.url_count} screenshots queued`);
```

---

## Respuestas de Error

| Estado | Condición |
|--------|-----------|
| `400 Bad Request` | `url` está vacío o falta |
| `400 Bad Request` | `output_format=true` con una sola URL (requiere varias URLs) |
| `400 Bad Request` | `direct_download=true` con varias URLs (no compatible) |
| `400 Bad Request` | `direct_download=true` con `async_mode=true` |
| `400 Bad Request` | Clave pública intentando usar varias URLs |
| `402 Payment Required` | El lote excedería la cuota mensual de ops restante |
| `402 Payment Required` | Se alcanzó el límite de almacenamiento |
| `403 Forbidden` | El procesamiento asíncrono no está disponible en el plan actual |
| `403 Forbidden` | El procesamiento por lotes no está disponible (límite de lote es 0) |
| `403 Forbidden` | El tamaño del lote excede el límite de lote del plan |
| `403 Forbidden` | La salida ZIP no está disponible en el plan actual |
| `403 Forbidden` | Los callbacks de webhook no están disponibles en el plan actual |
| `404 Not Found` | Lote no encontrado (batch_id incorrecto o proyecto incorrecto) |

---

## Límites

| Límite | Valor |
|-------|-------|
| Tamaño de lote | Depende del plan (Founding: deshabilitado) |
| Conversiones mensuales | Depende del plan (lote completo verificado previamente) |
| Timeout de entrega de webhook | 30 segundos (sin reintentos) |
| Máximo de cookies por solicitud | 50 |
| Máximo de encabezados personalizados por solicitud | 20 |
| Retención de archivos | Depende del plan |

## Preguntas frecuentes

### ¿Cómo convierto varias URLs a PDF en una sola solicitud de API?

Envía un POST a `/v1/convert/url-to-pdf` con un array de URLs en el parámetro `url`, autenticado con una clave de API privada (`sk_...`). La API responde con `HTTP 202` y un `batch_id`, y cada URL se convierte en segundo plano.

### ¿Puedo obtener todos los resultados de la conversión por lotes en un único archivo ZIP?

Sí. Establece `output_format` en `true` para agrupar todos los resultados exitosos en un archivo ZIP llamado `{output_filename}_{timestamp}.zip` (o `batch_{timestamp}.zip` si no se indica un nombre personalizado). La salida ZIP requiere un plan con acceso a salida ZIP (Studio, Production o Enterprise) y varias URLs en la solicitud.

### ¿Cómo verifico el estado de un trabajo de conversión por lotes?

Consulta `GET /v1/convert/batch/{batch_id}` con tu clave de API privada. La respuesta indica `processing`, `completed`, `partial`, o `failed`, junto con elementos por URL que contienen `download_url`, `output_file_size`, y `duration`.

### ¿Por qué mi solicitud por lotes devuelve 403 Forbidden?

Un `403 Forbidden` significa que se alcanzó una restricción del plan: el procesamiento asíncrono no está disponible en tu plan, el procesamiento por lotes está deshabilitado (límite de lote es 0), el tamaño del lote excede el límite de tu plan, o una función restringida como la salida ZIP o los callbacks de webhook no está incluida en tu plan.

### ¿Puedo usar el procesamiento por lotes con una clave de API pública?

No. El procesamiento por lotes requiere una clave de API privada (`sk_...`). Las claves públicas están restringidas a solicitudes síncronas de una sola URL, y enviar varias URLs con una clave pública devuelve `400 Bad Request`.
