Migrate from PDFShift in one line
Change two values, keep the body: aim POST /v3/convert/pdf (or png, jpeg, webp) at this API and swap the API key. Three contract details differ, one sentence each: on the pdf path the body's format names the paper size (A4, A3, Letter, Legal, A5), never the output format; the response is raw bytes, not a JSON envelope carrying a download URL; and timeout clamps to a 60,000ms ceiling where their paid plans wait up to 100 seconds.
The whole migration is two values
Same POST, same JSON body, same X-API-Key header. Their call:
curl -X POST \
https://api.pdfshift.io/v3/convert/pdf \
-H "X-API-Key: sk_your_pdfshift_key" \
-H "Content-Type: application/json" \
-d '{"source": "https://example.com", "format": "A4", "margin": "10mm", "quality": 90, "timeout": 30}'
This API's:
curl -X POST \
https://webcapture.io/v3/convert/pdf \
-H "X-API-Key: wc_your_webcapture_key" \
-H "Content-Type: application/json" \
-d '{"source": "https://example.com", "format": "A4", "margin": "10mm", "quality": 90, "timeout": 30}'
The two bodies are byte-identical; only the host and the key move. Run the second call with -o page.pdf, because there is no URL to fetch afterward: the response body is the document itself, with its content type, and x-credits-cost, x-credits-used, and x-credits-remaining headers beside it.
Already holding an HTML string? Keep it in source: {"source": "<html><body><h1>Invoice 42</h1></body></html>"} renders directly. Raw HTML cannot carry http_headers, cookies or auth on this shim; use the native /v1 html and baseUrl fields when relative assets or origin-pinned fetch controls are needed.
The 40 parameters, mapped
Three columns: their parameter, what it becomes on this endpoint, and the status. The rows are generated from the shim's own source, the code that actually decides, so the table cannot promise a mapping that is not performed.
What maps (31)
| pdfshift parameter | what it becomes here | status |
|---|---|---|
source | source, required: the page URL or a pasted HTML document. Raw HTML renders directly; combining it with http_headers, cookies or auth answers 422 because the shim has no base-url parameter to pin them safely | mapped, url or raw html |
format | on the pdf path, the page size: A4, A3, Letter, Legal, A5. On image paths the path segment already carries the output format, so it is ignored | supported as the PDF page size |
landscape | landscape: true prints the page rotated | mapped |
margin | margin: a number with an optional mm, cm, in or px unit, 100 or less; present on an image path it answers 400 instead of applying | mapped, pdf only |
quality | quality: an integer from 1 to 100 | mapped |
delay | settle: milliseconds to wait after load before capture, ceiling 10,000ms | supported as settle |
timeout | the request's render budget in milliseconds, converted from seconds and clamped to a 60,000ms ceiling | supported as the render budget |
watermark | the pdfshift object's text becomes our watermark string: a centered text stamp on the rendered pdf (Helvetica-Bold 48pt, 0.15 opacity, 45deg); other watermark options refuse with a 422 | mapped, pdf only |
metadata | metadata: an object with title, author, subject and keywords, riding the same shape and caps as /v1/convert | mapped, pdf only |
protection | protection: the user and owner passwords become encryption's userPassword and ownerPassword; permission flags refuse with a 422 | mapped as encryption |
http_headers | custom request headers with the source fetch: an object of header name and value strings, at most 20 headers, riding /v1's admission rules (a cookie header refuses, and so do host and content-length) | mapped |
cookies | cookies: an array of name and value entries pinned to the source URL's hostname (an entry whose domain differs answers 400, and attributes beyond domain are ignored), at most 20, and the cookie jar never survives the render | mapped, host-pinned |
auth | basic auth credentials (username and password) sent with the source fetch over the browser's own credential channel | mapped |
is_hipaa | phi: the zero-residue render mode. The source url is never stored, the artifact is deleted on first download, and the render is audit-noted without the url; requires an executed BAA on the account, and a request from an account without one answers 403 | mapped as phi, BAA-gated |
css | css: an inline stylesheet string of 1 to 20,000 characters injected into the page after load, before the capture; url and object forms answer 400 instead of fetching a caller url | mapped |
javascript | javascript: an inline script string of 1 to 20,000 characters run in the page after load, before the capture; url and object forms answer 400 the same way. A disable_javascript body alongside it answers 422 | mapped |
use_print | usePrint: emulate print media before the capture, so print-keyed media queries apply the way the printed page sees them | mapped as usePrint |
disable_javascript | disableJavaScript: render with the page's JavaScript off at the browser context; it cannot combine with javascript (the pairing answers 422) | mapped as disableJavaScript |
disable_backgrounds | disableBackgrounds: pdf output prints without page backgrounds when true; unset keeps them | mapped as disableBackgrounds |
wait_for | waitFor: a CSS selector of 1 to 500 characters the render waits to see before the capture, capped by the same settle ceiling as delay. pdfshift's function-name form is NOT replicated: a string naming a function never matches a selector, and the render fails like any page error when the wait budget ends | mapped as waitFor |
zoom | scale: a number from 0.1 to 2 applied as the render's scale | mapped as scale |
filename | filename: stored with the render and used as the download's file name, 1 to 100 characters of letters, digits, dot, dash or underscore | mapped |
wait_for_network | waitForNetwork: hold navigation until the network has been quiet (a networkidle window) instead of capturing at the load event, so pages that keep fetching settle before the capture | mapped as waitForNetwork |
ignore_long_polling | accepted for parity: it maps onto the same waitForNetwork wait, because that wait's quiet window already ignores long polls; there is no second switch to set | mapped onto waitForNetwork |
lazy_load_images | lazyLoadImages: keep waiting after load until images finish (loaded or failed), before any capture on every format, on a min(settle, 10 seconds) budget; a budget that runs out fails the render instead of shipping a half-loaded capture | mapped as lazyLoadImages |
raise_for_status | raiseForStatus: a source that answers an http 400+ status fails the render with that status instead of printing the error page, and the failure is terminal, never retried | mapped as raiseForStatus |
remove_blank | removeBlank: pdf pages with no marking operators (no text, path, shading or image marks) are dropped after render, before stamping; an all-blank document keeps its original pages and the count removed is never echoed | mapped, pdf only |
clip | clip: an object with x, y, width and height (non-negative integers, width and height of at least 1) cropping the screenshot to that page region; requires fullPage, the default, and it cannot combine with css_selector | mapped, image paths only |
css_selector | cssSelector: a css selector of 1 to 500 characters whose matched element is captured instead of the page; it cannot combine with clip, and the capture waits for the element within the render's own wait budget | mapped as cssSelector, image paths only |
dpi | dpi: output density from 1 to 4 applied as the browser's device scale factor, so page and element captures come out at the requested density; pdf points are dpi-independent, so the pairing refuses | mapped, image paths only |
transparent | transparent: true prints png and webp screenshots without the default white background; jpeg has no alpha channel and the pairing refuses | mapped, png and webp only |
What refuses (9)
Present in a body, these answer a 422 whose error names every offending parameter at once; nothing is dropped silently. Anything outside both lists is ignored silently, matching pdfshift's own tolerance for unknown fields, so a drop-in caller never hard-stops on a harmless extra.
| pdfshift parameter | what it would take here | status |
|---|---|---|
sandbox | no test mode; the free tier is the dry run | not supported |
encode | no base64 mode; the bytes arrive raw | not supported |
s3_destination | use a presigned PUT URL as destination instead | not supported |
webhook | renders are synchronous; the bytes are the response | not supported |
is_gdpr | no GDPR-only processing mode | not supported |
log_request | no per-request logs here; usage lives in the dashboard | not supported |
header | no running headers; margins are the only page furniture here | not supported |
footer | no running footers; margins are the only page furniture here | not supported |
encryption | use protection with a userPassword instead | not supported |
Timeout, said plainly
pdfshift's timeout counts seconds: their spec allows up to 300, and paid plans wait up to 100 seconds (re-checked 2026-10-03). This endpoint clamps the converted ask to 60,000ms, and the value behaves as a ceiling on the job budget, not a timer you set: a caller asking for timeout: 5 waits up to this endpoint's budget, not five seconds. The ceiling caps the wait; the job budget decides the actual bound.
Clamped values are not surfaced in the response: raw bytes leave no channel for notes, so send timeout at or under 60 and delay at or under 10000 rather than relying on the clamp.
Everything else in the envelope
Errors keep their shape: {"success": false, "error": "...", "code": ...}. A malformed value is a 400; a capability refusal, an unsupported parameter or raw HTML combined with fetch controls, is a 422 naming itself; out of credits is 403 with the quota numbers in an errors object, and a phi request (is_hipaa) from an account without an executed BAA is 403 too; a render past the budget is 408; a failed render is 502. Authentication takes their X-API-Key header, and a Bearer header works too, so one credential can serve both surfaces.
Where this fits
The money side, one render per document against their credit-per-5Mb model, is on webcapture vs PDFShift. The native API this shim fronts, with its own contract and Markdown output, is in the docs; the product page keeps engine, prices, and quick start together.
Method
Checked 2026-10-05. The mapping table is generated from this API's shim source: the exported refusal list and the mapping function beside it, so the columns describe the code that runs, not a spec retype. The PDFShift side of the table comes from pdfshift.io's OpenAPI spec and live docs. Questions or corrections: hello@webcapture.io.
Render your first page in seconds
Mint a key, POST a URL, and the finished document comes back in the same response; pass ?wait=false for a job ticket with poll and download URLs. The first 50 renders a month are free, and there is no credit card. Not ready for a key? The free URL to PDF tool renders public pages without one.