Batch Processing

Batch processing allows you to convert multiple URLs in a single API request. Each URL is converted asynchronously, and you can track progress via polling, webhook callbacks, or email notifications. Batch processing is available for both url-to-pdf and url-to-screenshot endpoints.

Private Keys Only: Batch processing is only available when authenticating with a private API key (X-API-Key: sk_live_...). Public keys are restricted to single-URL synchronous requests.

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. The entire batch count is pre-checked against your remaining monthly conversion quota 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:

curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_live_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):

{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 3,
    "output_format": "individual"
}
Note: When passing multiple URLs, async_mode is automatically set to true regardless of whether you explicitly include it in the request.

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:

curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
  -H "X-API-Key: sk_live_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):

{
    "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_live_your_private_key

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

Processing State

While conversions are still running:

{
    "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:

{
    "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:

{
    "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:

{
    "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:

{
    "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:

{
    "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"}
    ]
}
Notification behavior difference: 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.

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 Free Starter Pro 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

import requests
import time

# Submit batch
response = requests.post(
    "https://api.enconvert.com/v1/convert/url-to-pdf",
    headers={"X-API-Key": "sk_live_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_live_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

import requests

response = requests.post(
    "https://api.enconvert.com/v1/convert/url-to-pdf",
    headers={"X-API-Key": "sk_live_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

const response = await fetch("https://api.enconvert.com/v1/convert/url-to-screenshot", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-API-Key": "sk_live_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 conversion quota
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 (Free: 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