Skip to main content
All posts
bulk HTML Markdown PDF API automation

Bulk HTML and Markdown to PDF: Convert a Whole Folder in One Job

Turn a ZIP of HTML or Markdown files into a ZIP of PDFs, with relative CSS, images and fonts still working. How it works, what it costs, and how to use it.

PodPDF Team September 13, 2026 7 min read

Converting one document to PDF is easy. Converting two hundred is where things usually fall apart: you write a loop, fire off requests one at a time, handle the failures, rename the outputs, and zip them back up yourself. And if those documents share a stylesheet or reference images by relative path, a one-file-at-a-time API can’t see any of it.

PodPDF bulk conversion handles the whole batch as a single job. Send a folder of HTML or Markdown files, get back one ZIP with a PDF per document and a manifest explaining what happened to each file.

What Bulk Conversion Does

You send a bundle of files. PodPDF:

  1. Opens the bundle and sorts files into documents and assets
  2. Renders each document to PDF with a headless browser, serving the other files in the bundle as its assets
  3. Packages the results into a ZIP that mirrors your folder structure, plus a manifest.json
  4. Charges only for PDFs that were produced — failed and skipped files cost nothing

The job runs asynchronously. You get a job_id back immediately and either poll for status or receive a webhook when it finishes.

Relative Assets Keep Working

This is the part a single-document API can’t do. Suppose your folder looks like this:

reports/
├── january.html
├── february.html
├── css/
│   └── site.css
└── img/
    └── logo.png

and january.html contains:

<link rel="stylesheet" href="css/site.css">
<img src="img/logo.png" alt="Logo">

When you convert january.html on its own, those relative paths point at nothing. In a bulk job they resolve against the bundle, so the CSS and the logo load exactly as they would when opening the file from disk. Root-absolute paths like /css/site.css resolve against the top of the bundle too.

Which files become PDFs depends on bundle_type:

bundle_typeConverted to PDFEverything else
html (default).html, .htmServed as assets
markdown.md, .markdownServed as assets

So a Markdown bundle can reference images in the same ZIP, and an HTML bundle can include Markdown files without them becoming PDFs.

Limits

LimitValue
Pages per job200, across all files
Files per job200
ZIP upload50 MB
Inline upload (multipart or base64)4 MB
Pages per individual PDF100
Bulk jobs running at once2 per account
Output kept30 days

The 200-page budget is shared across the job. Files are rendered in order, and once the next file would push the total past 200 pages, it and the files after it are skipped rather than failing the job — you get every PDF that fit, and the manifest tells you which ones didn’t.

Using the API

Every request authenticates with your API key in the X-API-Key header. There are two ways to send files.

Small batches: multipart upload

Up to 4 MB of files, sent directly. Multipart drops folder names, so pass a paths array in the same order as the files to keep the structure that relative links depend on:

curl -X POST https://api.podpdf.com/bulkjob \
  -H "X-API-Key: YOUR_API_KEY" \
  -F bundle_type=html \
  -F 'paths=["reports/january.html","reports/february.html","reports/css/site.css","reports/img/logo.png"]' \
  -F files=@reports/january.html \
  -F files=@reports/february.html \
  -F files=@reports/css/site.css \
  -F files=@reports/img/logo.png

Larger batches: upload a ZIP

For anything up to 50 MB, upload the ZIP straight to storage with a presigned URL, then submit the job:

# 1. Ask for an upload URL for a ZIP of this exact size
UPLOAD=$(curl -s -X POST https://api.podpdf.com/bulkjob/upload-url \
  -H "X-API-Key: $PODPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"content_length_bytes\": $(wc -c < reports.zip)}")

# 2. Upload the ZIP (no API key on this request)
curl -X PUT "$(echo "$UPLOAD" | jq -r .upload_url)" \
  -H "Content-Type: application/zip" \
  --data-binary @reports.zip

# 3. Submit the job
curl -X POST https://api.podpdf.com/bulkjob \
  -H "X-API-Key: $PODPDF_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"s3_key\": \"$(echo "$UPLOAD" | jq -r .s3_key)\", \"bundle_type\": \"html\", \"options\": {\"format\": \"A4\"}}"

Either way, the response is 202 Accepted:

{
  "job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
  "status": "queued",
  "job_type": "bulk",
  "bundle_type": "html",
  "message": "Bulk job queued for processing"
}

The options object takes the same PDF settings as single conversions — format, margin, landscape, printBackground, scale, header and footer templates — and applies them to every document in the job.

Tracking the job

Poll the job until it reaches a final status:

curl https://api.podpdf.com/jobs/3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c \
  -H "X-API-Key: $PODPDF_API_KEY"
{
  "job_id": "3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c",
  "status": "partial_failed",
  "job_type": "bulk",
  "file_count": 42,
  "processed_count": 42,
  "success_count": 40,
  "failure_count": 2,
  "pages_total": 118
}

status moves from queued to processing, then ends as one of:

  • completed — every document converted
  • partial_failed — some converted, some failed or were skipped
  • failed — nothing converted

Rather than polling, you can register a webhook in the dashboard and subscribe to bulk.job.completed, bulk.job.partial and bulk.job.failed.

Downloading the results

curl https://api.podpdf.com/jobs/3f5c2a7e-8b1d-4c9e-a2f4-6d7e8f9a0b1c/download \
  -H "X-API-Key: $PODPDF_API_KEY"

This returns a download_url for the output ZIP, valid for one hour. The ZIP itself is kept for 30 days, so call the endpoint again whenever you need a fresh link.

Partial Failures, Handled

In a batch of 200 files, something will go wrong eventually — a file too large, a document that renders to too many pages, the page budget running out. Bulk jobs don’t throw away 199 good PDFs because of one bad file.

Every PDF that converted is in the output ZIP. manifest.json records the outcome for every input:

{
  "input": "reports/huge.html",
  "output": null,
  "status": "failed",
  "error_code": "FILE_TOO_LARGE",
  "missing_assets": 0
}

missing_assets is worth watching: it counts references the document made that weren’t in the bundle — a quick way to catch a stylesheet you forgot to include before a customer notices a PDF with no styling.

For a per-file breakdown without downloading anything, call GET /jobs/{job_id}/files.

What It Costs

Bulk conversion uses the same pricing as everything else: $0.01 per PDF produced. A job that converts 40 of 42 files costs $0.40. Skipped and failed files are free.

Before rendering starts, PodPDF checks your credit balance can cover the documents in the archive, so a job never gets halfway through and stops for lack of credits.

Using the Web App

You don’t need the API for any of this. In the PodPDF web app, open the Bulk tab and pick a folder or a ZIP. The app:

  • Lists the files it found and which will become PDFs
  • Ignores macOS metadata like .DS_Store and ._ files
  • Shows an estimate before you submit
  • Lets you switch between HTML and Markdown when a folder contains both

Submit, and the job appears in your job history with live progress. Download the ZIP when it’s done.

When to Use Bulk vs Single Conversions

You haveUse
One document, need the PDF back immediatelyPOST /quickjob (up to 25 pages)
One large documentPOST /longjob (up to 100 pages)
Many documents, or documents that share assetsPOST /bulkjob

Bulk is the right choice as soon as your documents reference each other’s files, or you’d otherwise be writing a loop.

Common Use Cases

  • Documentation exports — Turn a folder of Markdown docs into a PDF per page for offline distribution
  • Statement runs — Render a month of customer statements from HTML templates that share one stylesheet
  • Static site archives — Convert an exported site, with its CSS and images, into PDFs for records retention
  • Report packs — Produce a set of related reports in one job and hand over a single ZIP

Getting Started

Create a PodPDF account, buy credits, and generate an API key under Settings → API Keys. Credits never expire. The bulk conversion guide and API reference cover every option and error code.

Start generating PDFs today

$0.01 per PDF — no subscriptions, no monthly minimums. Credits never expire.