---
seo_title: Batch File Conversion API: Bulk URL to PDF & ZIP | EnConvert
meta_desc: Convert multiple URLs to PDF or screenshots in one request with POST /v1/convert/url-to-pdf. Batch file conversion API with ZIP output, webhooks, and polling.
keywords: batch file conversion api, bulk url to pdf api zip, convert multiple urls to pdf api, batch screenshot api, batch status polling api, zip output pdf api, async batch conversion api, batch conversion webhook callback
---

# Batch File Conversion API

The EnConvert batch API converts multiple URLs in a single request: pass an array of URLs to `/v1/convert/url-to-pdf` or `/v1/convert/url-to-screenshot` and receive an HTTP 202 response with a `batch_id`. Every URL is converted asynchronously in the background, and results are delivered as presigned download URLs, either one per file or bundled into a single ZIP archive. Track progress by polling `GET /v1/convert/batch/{batch_id}`, or get notified via webhook callback or email on completion.

<div class="alert alert-warning">
<strong>Private Keys Only:</strong> Batch processing is only available when authenticating with a <strong>private API key</strong> (<code>X-API-Key: sk_...</code>). Public keys are restricted to single-URL synchronous requests.
</div>

---

## How It Works

1. Send a request with an **array of URLs** in the `url` parameter to either `/v1/convert/url-to-pdf` or `/v1/convert/url-to-screenshot`.
2. The API validates the batch against your plan's limits and returns **HTTP 202** with a `batch_id`.
3. Each URL is converted in the background and bills one op. The entire batch count is pre-checked against your remaining monthly ops allowance before any processing begins.
4. Track progress via `GET /v1/convert/batch/{batch_id}`, or receive a webhook callback or email notification on completion.
5. Download the results via presigned URLs in the batch status response.

---

## Output Modes

### Individual Mode (Default)

Each URL produces a separate file. Each file gets its own presigned download URL in the batch status response.

**Request:**

```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"
    ]
  }'
```

**Response (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>Note:</strong> When passing multiple URLs, <code>async_mode</code> is automatically set to <code>true</code> regardless of whether you explicitly include it in the request.
</div>

### ZIP Bundle Mode

Set `output_format` to `true` to receive all converted files bundled into a single ZIP archive. Requires a plan with ZIP output access.

**Request:**

```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"
  }'
```

**Response (HTTP 202 Accepted):**

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

In ZIP mode, all URLs are processed sequentially, and the successful results are bundled into a single ZIP archive named `{output_filename}_{timestamp}.zip` (or `batch_{timestamp}.zip` if no custom name is provided).

---

## Batch Parameters

| Parameter | Type | Default | Description | Plan Gating |
|-----------|------|---------|-------------|-------------|
| `url` | `string[]` | *(required)* | Array of URLs to convert. | -- |
| `output_format` | `boolean` | `false` | Set to `true` to bundle all results into a ZIP archive. Requires multiple URLs. | Requires ZIP output access |
| `output_filename` | `string` | Auto-generated | Custom filename for the output. In ZIP mode, this names the ZIP archive. | -- |
| `async_mode` | `boolean` | `true` (implicit) | Always `true` for batch. Automatically enabled when multiple URLs are provided. | Requires async access |
| `notification_email` | `string` | Project owner email | Email address to notify on completion. If omitted, defaults to the project owner's email. | -- |
| `callback_url` | `string` | `null` | Webhook URL to receive a POST on completion. | Requires webhook access |
| `direct_download` | -- | -- | **Not supported** for batch. Returns 400 error if set with multiple URLs. | -- |

### Browser & Rendering Parameters

These settings apply to every URL in the batch:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `viewport_width` | `integer` | `1920` | Browser viewport width in pixels. |
| `viewport_height` | `integer` | `1080` | Browser viewport height in pixels. |
| `single_page` | `boolean` | `true` | Render as a single continuous page (url-to-pdf only). |
| `load_media` | `boolean` | `true` | Wait for images and media to load. |
| `enable_scroll` | `boolean` | `true` | Scroll pages to trigger lazy-loaded content. |
| `handle_sticky_header` | `boolean` | `true` | Detect and handle sticky/fixed headers. |
| `handle_cookies` | `boolean` | `true` | Auto-dismiss cookie consent banners. |
| `wait_for_images` | `boolean` | `true` | Wait for all images to finish loading. |

### Authentication & Custom Requests

These apply to every URL in the batch. Require a plan with basic auth access.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `auth` | `object` | `null` | HTTP Basic Auth credentials: `{"username": "...", "password": "..."}`. |
| `cookies` | `array` | `null` | Array of cookie objects injected before each page load. Max 50. |
| `headers` | `object` | `null` | Custom HTTP headers sent with every request. Max 20. |

### PDF Options (url-to-pdf only)

Pass a `pdf_options` object to control PDF output formatting for every page in the batch:

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page_size` | `string` | `"A4"` | Named page size. |
| `orientation` | `string` | `"portrait"` | `"portrait"` or `"landscape"`. |
| `margins` | `object` | `{"top": 10, "bottom": 10, "left": 10, "right": 10}` | Margins in mm. |
| `grayscale` | `boolean` | `false` | Grayscale output via Ghostscript. |

---

## Batch Status Polling

Use the batch status endpoint to check progress and retrieve download URLs.

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

Replace `{batch_id}` with the `batch_id` returned from the initial request.

### Processing State

While conversions are still running:

```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
        }
    ]
}
```

### Completed State

When all URLs have been converted successfully:

```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"
        }
    ]
}
```

### Partial State

When all URLs have finished but some failed:

```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"
        }
    ]
}
```

### ZIP Mode Completed

In ZIP mode, a single `zip_download_url` is provided for the entire archive:

```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"
        }
    ]
}
```

### Batch Status Values

| Status | Meaning |
|--------|---------|
| `processing` | At least one URL is still being converted. |
| `completed` | All URLs converted successfully. |
| `partial` | All URLs finished, but some failed. |
| `failed` | All URLs failed. |

---

## Webhook Callbacks

Provide a `callback_url` in the request to receive an automatic POST notification on completion. The webhook is sent with `Content-Type: application/json` and a 30-second timeout. No retries are attempted on delivery failure.

### Individual Mode Callback

In individual mode, a **separate webhook POST is sent for each URL** as it completes:

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

### ZIP Mode Callback

In ZIP mode, a **single webhook POST is sent when the entire batch completes**:

```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>Notification behavior difference:</strong> In individual mode, you receive N separate webhook POSTs (one per URL) and N separate emails. In ZIP mode, you receive one webhook POST and one email for the entire batch. Plan accordingly when designing your webhook handler.
</div>

---

## Email Notifications

A completion email is sent to `notification_email` when the batch finishes. If no `notification_email` is provided, the email is sent to the project owner's email by default.

The email includes:

- Job status (success/failed) with a colored banner
- Batch ID
- For batch jobs: a table listing each URL, its status, and output filename
- A link to download results from the dashboard

---

## Subscription Plan Gating

| Feature | Founding | Indie | Studio | Enterprise |
|---------|------|---------|-----|------------|
| Batch processing | No | Yes | Yes | Yes |
| Async mode | No | Yes | Yes | Yes |
| ZIP output bundling | No | No | Yes | Yes |
| Webhook callbacks | No | No | Yes | Yes |
| HTTP Basic Auth / Cookies / Headers | No | Yes | Yes | Yes |
| Batch size limit | 0 | Plan-based | Plan-based | Unlimited |
| Monthly conversions | 100 | Plan-based | Plan-based | Unlimited |

---

## Code Examples

### Python -- Individual Mode with Polling

```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 -- ZIP Mode with 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 -- Batch Screenshots

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

---

## Error Responses

| Status | Condition |
|--------|-----------|
| `400 Bad Request` | `url` is empty or missing |
| `400 Bad Request` | `output_format=true` with a single URL (requires multiple URLs) |
| `400 Bad Request` | `direct_download=true` with multiple URLs (not supported) |
| `400 Bad Request` | `direct_download=true` with `async_mode=true` |
| `400 Bad Request` | Public key attempting multiple URLs |
| `402 Payment Required` | Batch would exceed remaining monthly ops allowance |
| `402 Payment Required` | Storage limit reached |
| `403 Forbidden` | Async processing not available on current plan |
| `403 Forbidden` | Batch processing not available (batch limit is 0) |
| `403 Forbidden` | Batch size exceeds plan's batch limit |
| `403 Forbidden` | ZIP output not available on current plan |
| `403 Forbidden` | Webhook callbacks not available on current plan |
| `404 Not Found` | Batch not found (wrong batch_id or wrong project) |

---

## Limits

| Limit | Value |
|-------|-------|
| Batch size | Plan-dependent (Founding: disabled) |
| Monthly conversions | Plan-dependent (entire batch pre-checked) |
| Webhook delivery timeout | 30 seconds (no retries) |
| Maximum cookies per request | 50 |
| Maximum custom headers per request | 20 |
| File retention | Plan-dependent |

## Frequently asked questions

### How do I convert multiple URLs to PDF in a single API request?

Send a POST to `/v1/convert/url-to-pdf` with an array of URLs in the `url` parameter, authenticated with a private API key (`sk_...`). The API responds with `HTTP 202` and a `batch_id`, and each URL is converted in the background.

### Can I get all batch conversion results in a single ZIP file?

Yes. Set `output_format` to `true` to bundle all successful results into one ZIP archive named `{output_filename}_{timestamp}.zip` (or `batch_{timestamp}.zip` if no custom name is given). ZIP output requires a plan with ZIP output access (Studio, Production, or Enterprise) and multiple URLs in the request.

### How do I check the status of a batch conversion job?

Poll `GET /v1/convert/batch/{batch_id}` with your private API key. The response reports `processing`, `completed`, `partial`, or `failed`, along with per-URL items containing `download_url`, `output_file_size`, and `duration`.

### Why does my batch request return 403 Forbidden?

A `403 Forbidden` means a plan restriction was hit: async processing is not available on your plan, batch processing is disabled (batch limit is 0), the batch size exceeds your plan's limit, or a gated feature such as ZIP output or webhook callbacks is not included in your plan.

### Can I use batch processing with a public API key?

No. Batch processing requires a private API key (`sk_...`). Public keys are restricted to single-URL synchronous requests, and submitting multiple URLs with a public key returns `400 Bad Request`.
