PDFShift and Api2Pdf both turn URLs and HTML into PDFs with headless Chromium, so their output is close. Where they differ is how you pay and what you get back.
- PDFShift sells monthly plans with credits. One credit is one document per started 5 MB of output. The response body is the PDF.
- Api2Pdf is pay-per-use: a $1 monthly fee plus a charge per MB of bandwidth and per second of compute. The response is JSON with a link to the file, which is deleted after 24 hours.
For small, fast documents Api2Pdf costs a fraction of PDFShift at every volume we modelled. For large, slow documents at high volume, PDFShift's flat credits win. Run the cost model below with your own document size and render time before you pick.
Disclosure: I build RenderScreenshot, a hosted screenshot and PDF API. PDFShift and Api2Pdf are not ours, and we have no paid account with either. This page has no latency numbers for them. Prices, parameters and limits come from their sites, checked on 2026-10-15, and the request code was run against a local mock that checks each documented request shape.
This page sits in our comparison hub. If you are also weighing a print engine, read DocRaptor vs PDFShift, which tests Prince against Chromium.
Verdict
| You care most about... | Pick | Why |
|---|---|---|
| Lowest bill for small invoices and receipts | Api2Pdf | About $7 vs $99 a month at 10,000 small PDFs in our model |
| A fixed, predictable monthly bill | PDFShift | Plans with a credit allowance |
| Large or slow documents at high volume | PDFShift | Credits do not grow with render seconds |
| PDF bytes in the response, no second download | PDFShift | Api2Pdf returns a link |
| Office files, merging, passwords, barcodes | Api2Pdf | LibreOffice, PDF tools and ZXing endpoints |
| Choosing the Chromium version | PDFShift | X-Processor-Version: 116 or 142 |
| HIPAA or GDPR switches on the request | PDFShift | is_hipaa, is_gdpr |
| Very heavy pages | Api2Pdf | A separate XL host with 4 CPUs, 8 GB and a 5 minute timeout, at 10x the price |
The cost model
Prices checked 2026-10-15. PDFShift: the monthly plans returned by its public pricing endpoint (Startup $9 for 500 credits, Boost $24 for 2,500, Business $99 for 25,000, 100k plan $249, 250k plan $499), with one credit per started 5 MB. Api2Pdf: $1 a month, plus $0.001 per MB of bandwidth and $0.00019551 per second of compute, from its pricing page. Free allowances and annual discounts are left out.
# Monthly cost of PDFShift vs Api2Pdf at a given volume, from list prices checked 2026-10-15. # PDFShift: cheapest monthly plan whose credits cover the volume (1 credit per started 5 MB). # Api2Pdf: $1 per month + $0.001 per MB + $0.00019551 per second of compute. import math import sys PDFSHIFT_PLANS = [(500, 9), (2_500, 24), (5_000, 39), (25_000, 99), (50_000, 149), (100_000, 249), (250_000, 499), (500_000, 799), (1_000_000, 999)] def pdfshift(docs, mb): credits = docs * math.ceil(mb / 5) if credits <= 50: return 0.0 return next((price for limit, price in PDFSHIFT_PLANS if credits <= limit), float("nan")) def api2pdf(docs, mb, seconds): return 1 + docs * mb * 0.001 + docs * seconds * 0.00019551 mb = float(sys.argv[1]) if len(sys.argv) > 1 else 0.2 seconds = float(sys.argv[2]) if len(sys.argv) > 2 else 2.0 print(f"assumes {mb} MB and {seconds} s of compute per PDF") print(f"{'PDFs/month':>10} {'PDFShift':>10} {'Api2Pdf':>10}") for docs in (1_000, 10_000, 100_000): print(f"{docs:>10,} {pdfshift(docs, mb):>10.2f} {api2pdf(docs, mb, seconds):>10.2f}")
Run it as python cost_model.py 0.2 2 (MB per PDF, seconds per PDF). Here are four runs:
| Profile | PDFs/month | PDFShift | Api2Pdf |
|---|---|---|---|
| Small and fast: 0.2 MB, 2 s | 1,000 | $24 | $1.59 |
| 10,000 | $99 | $6.91 | |
| 100,000 | $249 | $60.10 | |
| Small but slow: 0.2 MB, 10 s | 1,000 | $24 | $3.16 |
| 10,000 | $99 | $22.55 | |
| 100,000 | $249 | $216.51 | |
| Large: 6 MB, 2 s | 1,000 | $24 | $7.39 |
| 10,000 | $99 | $64.91 | |
| 100,000 | $499 | $640.10 |
As a check on the model, Api2Pdf's own pricing page gives $5.91 for 10,000 PDFs of 0.1 MB at 2 seconds each. Our script returns the same figure for those inputs.
What the table means:
- Render seconds drive Api2Pdf's bill. A page that waits for fonts, maps or a slow API adds compute time to every document. Measure your real render time before you trust the "small and fast" row.
- Output size drives both bills. PDFShift charges an extra credit for each 5 MB. Api2Pdf charges per MB. Compress images in your HTML before you print it.
- PDFShift's plan steps are coarse. 1,000 PDFs a month needs the 2,500-credit plan, because the 500-credit plan is too small. If your volume sits just above a step, you pay for headroom you do not use.
Response, auth and limits
| PDFShift | Api2Pdf | |
|---|---|---|
| Endpoint | POST https://api.pdfshift.io/v3/convert/pdf |
POST https://v2.api2pdf.com/chrome/pdf/url (or /html, /markdown) |
| Auth | X-API-Key header |
Authorization: <key> header (raw key, no "Bearer") |
| Input field | source (URL or HTML, detected) |
url or html, by endpoint |
| Success response | PDF bytes (or JSON with an S3 link if you set filename) |
JSON with FileUrl |
| File lifetime on their side | filename links: 2 days |
24 hours |
| Engines | Chromium 116 or 142 | Chrome (130 per its docs), wkhtmltopdf, LibreOffice |
| Waiting for the page | wait_for (global function), delay up to 10 s, wait_for_network (on by default), lazy_load_images |
options.delay, puppeteerWaitForMethod and puppeteerWaitForValue |
| Rate limits | 250 conversions per minute when authenticated | None, per its pricing page |
| Timeouts and size | Free plan: 30 s, 15 MB; paid plans list 100 s and 250 MB in the pricing endpoint | Standard host per request; XL host: 5 minutes |
| Images as well as PDFs | /v3/convert/png, /jpeg, /webp |
/chrome/image/url, /chrome/image/html |
| Free testing | 50 credits a month, sandbox: true |
One month of free credit |
The response difference matters in code. With PDFShift you write the body to a file and you are done. With Api2Pdf you make a second request to fetch FileUrl, and if your job retries after more than a day, that link is gone.
Python code for both
Both scripts use requests and stop on any HTTP error. We ran them against a local mock that checks each documented auth header and required field. They have not run against the live APIs.
PDFShift:
# PDFShift: URL to PDF. The response body is the PDF itself. import os import requests resp = requests.post( "https://api.pdfshift.io/v3/convert/pdf", headers={"X-API-Key": os.environ["PDFSHIFT_API_KEY"]}, json={ "source": "https://example.com", "sandbox": True, # watermarked, not counted against credits "format": "A4", "wait_for_network": True, }, timeout=60, ) resp.raise_for_status() with open("example-pdfshift.pdf", "wb") as f: f.write(resp.content) print("saved example-pdfshift.pdf")
Api2Pdf:
# Api2Pdf: URL to PDF with headless Chrome. The response is JSON with a link to the file, # which Api2Pdf deletes after 24 hours, so download it straight away. import os import requests resp = requests.post( "https://v2.api2pdf.com/chrome/pdf/url", headers={"Authorization": os.environ["API2PDF_API_KEY"]}, # raw key, no "Bearer" json={ "url": "https://example.com", "inline": False, "fileName": "example.pdf", "options": {"printBackground": True, "landscape": False}, }, timeout=60, ) resp.raise_for_status() body = resp.json() if not body.get("FileUrl"): raise RuntimeError(f"Api2Pdf error: {body}") pdf = requests.get(body["FileUrl"], timeout=60) pdf.raise_for_status() with open("example-api2pdf.pdf", "wb") as f: f.write(pdf.content) print("saved example-api2pdf.pdf")
Note the option style. PDFShift takes flat snakecase fields (`useprint,waitfornetwork). Api2Pdf nests Chrome print options underoptionsin camelCase (printBackground,marginTop,headerTemplate), close to Puppeteer'spage.pdf()` names.
Option map for a typical invoice
| Task | PDFShift | Api2Pdf (Chrome) |
|---|---|---|
| Paper size | format: "A4" or "210mmx297mm" |
options.width, options.height (default A4 in inches) |
| Margins | margin object |
options.marginTop, marginBottom, marginLeft, marginRight |
| Landscape | landscape: true |
options.landscape: true |
| Backgrounds | On; disable_backgrounds: true turns them off |
options.printBackground: true (default false) |
| Header and footer | Header and footer templates (see its docs) | options.headerTemplate, options.footerTemplate, displayHeaderFooter |
| Use CSS page size | Set format |
options.preferCSSPageSize: true |
| Page range | pages: "1-3" |
options.pageRanges: "1-3" |
| Print or screen CSS | use_print: true |
Screen by default |
Watch the background default when you switch. Api2Pdf's printBackground defaults to false, so a colored header that shows in PDFShift output disappears until you set it.
How to benchmark them on your documents
We could not run this without accounts, so here is the method we would use:
- Pick 10 real documents: your smallest receipt, your largest report, one with web fonts, one with a chart drawn by JavaScript.
- Send each to both services three times, with sandbox mode on for PDFShift. Record total time, including Api2Pdf's second download.
- Note the bandwidth and compute seconds that Api2Pdf bills for each document (its portal keeps request logs), and feed them into the cost model above.
- Open every PDF. Check fonts, page breaks, backgrounds and that late JavaScript content appears.
- Repeat from the region where your servers run. Latency from a laptop is not latency from your production cloud.
When PDFShift wins
- You want one predictable monthly invoice and PDF bytes in the response.
- You need compliance switches (
is_hipaa,is_gdpr) or export to your own S3, Google Cloud Storage or R2 bucket. - Your documents are large or slow and your volume is high, where credits beat metered seconds.
When Api2Pdf wins
- Your documents are small and quick, and cost is the deciding factor.
- Volume swings a lot from month to month and you do not want to pick a plan.
- You also need Office-to-PDF, merges, passwords or barcodes from the same key.
- You build AI agents: Api2Pdf has an MCP server and accepts pay-per-request x402 payments.
Where RenderScreenshot fits
RenderScreenshot renders PDFs in Chromium too, and returns the bytes in the response like PDFShift. Every render is cached with a CDN link in the X-Cache-URL header, so a repeat request for the same document does not render again. The PDF options are in the PDF parameters reference, and URL to PDF walks through the common cases.
# RenderScreenshot Python SDK: URL to A4 PDF. The PDF bytes come back in the response. import os from renderscreenshot import Client, TakeOptions client = Client(os.environ['RS_API_KEY']) pdf = client.take( TakeOptions.url("https://example.com") .format("pdf") .pdf_paper_size("a4") .pdf_print_background() ) with open("example-rs.pdf", "wb") as f: f.write(pdf) print("saved example-rs.pdf")
The same client makes screenshots and social cards when you drop .format('pdf'), which is the part neither PDF service focuses on. We tested this snippet against a local mock of our API, not the live service. The Python SDK installs from GitHub (pip install git+https://github.com/Render-Screenshot/rs-python.git) until it is published on PyPI. We do not convert Office files, so Api2Pdf keeps that job. Get a free API key and add a third column to your benchmark.
Last checked 2026-10-15: PDFShift docs and public pricing endpoint, Api2Pdf docs and pricing page. Python 3.14.8 with requests 2.34.2; vendor calls run against a local mock only.