Website to PDF API#

The POST /v1/convert/website-to-pdf endpoint crawls an entire website (via sitemap parsing or a full breadth-first crawl), converts each discovered page to a high-fidelity PDF, and bundles the results into a single ZIP archive. Jobs always run asynchronously: the API returns HTTP 202 with a batch_id immediately, completion is signalled via batch status polling, a webhook callback, or an email notification, and the batch status response includes a presigned download URL for the finished ZIP. Requires a paid plan and a private API key.


Endpoint#

POST /v1/convert/website-to-pdf

Content-Type: application/json

Output format: ZIP archive containing one PDF per discovered page.

Mode: Always asynchronous. Returns HTTP 202 immediately.


Authentication#

This endpoint requires a private API key. Public keys are not supported for website capture.

X-API-Key: sk_your_private_key

Request Parameters#

Website Discovery Parameters#

Parameter Type Required Default Description Plan Gating
url string Yes -- The website base URL (e.g. https://example.com). Used as the root for page discovery. --
crawl_mode string No "auto" URL discovery method. One of "auto", "sitemap", or "full". See Crawl Modes below. Sitemap requires Indie+, Full requires Studio+
include_patterns string[] No null Regex patterns to whitelist discovered URLs. Only used in full crawl mode. --
exclude_patterns string[] No System defaults Regex patterns to blacklist URLs. Only used in full crawl mode. When omitted, uses built-in defaults that exclude static assets, login/admin/cart pages, and deep pagination. --

Notification Parameters#

Parameter Type Required Default Description Plan Gating
output_filename string No Auto-generated Custom base name for the output ZIP file. Timestamp is appended automatically. --
notification_email string No Project owner email Email address to notify when the job completes. --
callback_url string No -- Webhook URL to receive a POST request on completion. Requires webhook access

Browser & Rendering Parameters#

These settings apply to the conversion of each individual page within the website.

Parameter Type Required Default Description Plan Gating
viewport_width integer No 1920 Browser viewport width in pixels. --
viewport_height integer No 1080 Browser viewport height in pixels. --
single_page boolean No true true renders each page as one continuous PDF page. false produces paginated output using pdf_options page size. --
load_media boolean No true Wait for all images and videos to fully load before conversion. --
enable_scroll boolean No true Scroll each page to trigger lazy-loading content. --
handle_sticky_header boolean No true Detect sticky/fixed headers and handle them before capture. --
handle_cookies boolean No true Auto-dismiss cookie consent banners. --
wait_for_images boolean No true Wait for all <img> elements to finish loading. --
wait_for_selector string No null CSS selector to wait for before capture, applied to each page. Returns 422 if it never appears within wait_for_selector_timeout. Useful for SPAs that hydrate content after load. --
wait_for_selector_timeout integer No 10000 Milliseconds to wait for wait_for_selector (maximum 60000). --
block_ads boolean No false Abort requests to known ad/tracker domains so they never load, render, or slow the capture. --
block_media boolean No false Abort image and audio/video requests entirely for a faster, lighter render. Unlike load_media (which only controls waiting), this prevents media from downloading at all. --

Authentication & Custom Requests#

Parameter Type Required Default Description Plan Gating
auth object No null HTTP Basic Auth credentials applied to every page. Format: {"username": "...", "password": "..."}. Requires basic auth access
cookies array No null Array of cookie objects injected before each page load. Maximum 50 cookies. Requires basic auth access
headers object No null Custom HTTP headers sent with every request. Maximum 20 headers. Requires basic auth access

PDF Options#

Pass these inside a pdf_options object. They apply to every page in the website.

Parameter Type Default Description
page_size string "A4" Named page size. Ignored when both page_width and page_height are set.
page_width float null Custom page width in millimeters. Both page_width and page_height must be set together.
page_height float null Custom page height in millimeters.
orientation string "portrait" "portrait" or "landscape".
margins object {"top": 10, "bottom": 10, "left": 10, "right": 10} Page margins in millimeters.
scale float 1.0 Content scale factor. Range: 0.1 to 2.0. Paginated mode only.
grayscale boolean false Convert each PDF page to grayscale.
header object null Page header for paginated mode. Format: {"content": "<html>", "height": 15}. Supports template variables: {{page}}, {{total_pages}}, {{date}}, {{title}}, {{url}}.
footer object null Page footer for paginated mode. Same format as header.

Supported page sizes: A0, A1, A2, A3, A4, A5, A6, B0, B1, B2, B3, B4, B5, Letter, Legal, Tabloid, Ledger


Crawl Modes#

"auto" (default)#

Uses the highest crawl mode your plan allows. If your plan supports full crawling, it runs a full crawl. If your plan supports sitemap-only, it runs sitemap discovery.

"sitemap"#

Discovers pages by parsing the website's sitemap.xml:

  1. Fetches {base_url}/sitemap.xml (30-second timeout)
  2. If the root element is <sitemapindex>, recursively fetches each child sitemap
  3. Extracts all <url><loc> entries from <urlset> elements
  4. Returns the full list of discovered URLs

Returns an error if the sitemap is missing, returns a non-200 status, contains invalid XML, or has no URLs.

"full"#

Performs a comprehensive two-phase crawl:

Phase 1 -- Seed discovery:

  1. Parses robots.txt for sitemap directives and crawl rules
  2. Checks standard sitemap paths (/sitemap.xml, /wp-sitemap.xml, /sitemap_index.xml, etc.)
  3. Discovers RSS/Atom feeds from <link> tags and common feed paths
  4. Extracts seed URLs from all discovered sources

Phase 2 -- Breadth-first link crawl:

  1. Starts from the base URL plus all seed URLs
  2. Visits each page and enqueues same-domain links
  3. Applies include_patterns and exclude_patterns to filter links
  4. Respects robots.txt rules
  5. Detects and avoids infinite URL traps (calendar pages, faceted filters, etc.)
  6. Deduplicates URLs by normalizing scheme, host, query parameters, and stripping tracking parameters (utm_*, fbclid, gclid, etc.)

Default exclude patterns (when exclude_patterns is not provided):

  • Static assets: *.pdf, *.zip, *.jpg, *.png, *.gif, *.svg, *.css, *.js, *.xml, *.json, *.mp4, *.webm, *.woff, *.woff2
  • Protected paths: /login, /admin, /cart, /checkout
  • Deep pagination: URLs with page= parameters exceeding 3 digits

Response#

202 Accepted (immediate)#

{
    "status": "processing",
    "batch_id": "550e8400-e29b-41d4-a716-446655440000",
    "url_count": 42,
    "total_discovered": 42,
    "discovery_method": "sitemap",
    "output_format": "zip"
}
Field Description
batch_id UUID for tracking the job via batch status polling or webhook.
url_count Number of pages that will be converted.
total_discovered Total pages discovered by the crawl.
discovery_method "sitemap" or "full_crawl" depending on the effective crawl mode.

Batch Status Polling#

Poll with the batch_id from the 202 response:

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

Returns aggregate status, per-URL statuses, and a presigned download URL for the ZIP when complete. See Batch Status Polling for the full response schema.

Webhook Callback Payload#

When callback_url is provided, EnConvert sends a POST request on completion:

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

Email Notification#

A completion email is sent to notification_email (or the project owner's email by default) when the job finishes, regardless of success or failure.


Subscription Plan Gating#

Feature Founding Indie Studio Enterprise
Website capture No Yes Yes Yes
Sitemap crawl mode No Yes Yes Yes
Full crawl mode No No Yes Yes
Webhook callbacks No No Yes Yes
HTTP Basic Auth No Yes Yes Yes
Cookie injection No Yes Yes Yes
Custom headers No Yes Yes Yes
Batch size limit 0 Plan-based Plan-based Unlimited
Monthly conversions 100 Plan-based Plan-based Unlimited
Founding plan: Website capture is not available on the free plan. Attempting to use this endpoint returns 403 Forbidden.

Code Examples#

Python (Private Key)#

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 (Private Key)#

$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 (Private Key)#

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 (Private Key)#

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

With Webhook Callback#

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

With Authentication (Password-Protected Site)#

{
    "url": "https://staging.example.com",
    "crawl_mode": "sitemap",
    "auth": {
        "username": "admin",
        "password": "staging-password"
    },
    "cookies": [
        {"name": "session_token", "value": "abc123", "domain": "staging.example.com"}
    ]
}

Error Responses#

Status Condition
400 Bad Request Missing or empty url parameter
400 Bad Request No URLs found in sitemap
400 Bad Request Timeout fetching sitemap (30-second limit)
400 Bad Request Non-200 response from sitemap URL
400 Bad Request Invalid XML in sitemap
400 Bad Request Unrecognized sitemap format
400 Bad Request No pages discovered (full crawl found zero URLs)
400 Bad Request Invalid auth, cookies, or headers structure
402 Payment Required Monthly ops allowance exceeded by discovered page count
402 Payment Required Storage limit reached
403 Forbidden Website crawling not available on current plan (Founding plan)
403 Forbidden Full crawl mode requires Studio plan or higher
403 Forbidden Discovered page count exceeds batch size limit
403 Forbidden Feature not available on plan (webhook, basic auth)
500 Internal Server Error Crawl or conversion failure

Limits#

Limit Value
Sitemap fetch timeout 30 seconds
Global crawl timeout (full mode) 10 minutes
Max crawl depth (full mode) 10 levels
Per-page crawl timeout (full mode) 30 seconds
Crawler memory limit 512 MB
Infinite trap threshold 20 URLs per URL pattern
Robots.txt fetch timeout 10 seconds
Max pages per crawl Plan's batch size limit
Maximum cookies per request 50
Maximum custom headers per request 20
Webhook delivery timeout 30 seconds
Monthly conversions Plan-dependent
File retention Plan-dependent

Frequently asked questions#

How do I convert an entire website to PDF with an API?#

Send a POST request to /v1/convert/website-to-pdf with the site's base url and your private key in the X-API-Key header. The API discovers every page (sitemap or full crawl), converts each one to PDF, bundles them into a ZIP, and returns HTTP 202 with a batch_id you can poll for the download link.

What is the difference between sitemap and full crawl mode?#

crawl_mode: "sitemap" parses the site's sitemap.xml (including nested sitemap indexes) and is available on Indie plans and above. crawl_mode: "full" runs a two-phase crawl (seed discovery from robots.txt, sitemaps, and RSS/Atom feeds, then a breadth-first same-domain link crawl with include_patterns/exclude_patterns filtering) and requires Studio or higher. The default "auto" uses the highest mode your plan allows.

How do I know when my website to PDF job is finished?#

Poll GET /v1/convert/batch/{batch_id} with your private key to get aggregate status, per-URL statuses, and a presigned ZIP download URL, or pass a callback_url to receive a webhook POST on completion. A completion email is also sent to notification_email (or the project owner by default) regardless of success or failure.

Can I archive a password-protected or staging site as PDF?#

Yes, on plans with basic auth access: pass auth with username and password for HTTP Basic Auth applied to every page, inject up to 50 session cookies, or send up to 20 custom headers.

Why does the website to PDF endpoint return 403 Forbidden?#

The most common causes: website capture is not available on the Founding plan, crawl_mode: "full" requires Studio or higher, the discovered page count exceeds your plan's batch size limit, or a requested feature (webhook, basic auth) is not on your plan.