html2pdf.app is an HTML to PDF conversion API. Send a public web page URL or raw HTML markup, and receive a ready-to-use PDF document in response. Call it from your own code, or from automation tools such as n8n: see Generate and Send PDF Invoice with n8n.
POST /v1/generate.callBackUrl.The page is rendered in a real browser, so it prints the way it looks in Chrome. These decide the result:
media to print to use your @media print rules instead. See Parameters.printBackground to false to leave them out.waitFor to wait up to 10 seconds before the PDF is generated, and lazyLoad for images that load on scroll. See Lazy-loaded images.A standard conversion is one authenticated POST request to https://api.html2pdf.app/v1/generate. Send a public web page URL or raw HTML in the required html field, and the API returns the generated PDF as binary data.
html to a publicly reachable URL or a raw HTML string..pdf file.Replace <your-api-key>, run the command, and cURL will write the generated PDF to document.pdf in the current directory.
curl --fail --show-error \
--request POST https://api.html2pdf.app/v1/generate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <your-api-key>' \
--data '{
"html": "https://www.example.com"
}' --output document.pdf--fail option makes the cURL example fail on HTTP 4xx and 5xx responses.callBackUrl and follow the callback workflow instead.Authenticate every request by passing the X-API-Key header with your API key. You will receive your API key by email after registration.
Send it in this header:
X-API-Key: <your-api-key>apiKey parameter. The key is also accepted as an apiKey query string parameter or as an apiKey field in the JSON body, so older integrations keep working. Use the header in new code: a key in a URL ends up in server and proxy logs, and in the body it sits among the document options. If your key has been sent in URLs, replace it with Rotate Key on the dashboard's API Keys page.| Parameter | Type | Description | Default |
|---|---|---|---|
| html | string | Raw HTML markup or a publicly reachable URL to convert. | required |
| callBackUrl | string | Generates the document asynchronously and sends the result to this URL. The callback payload contains the base64-encoded PDF as {"document": "..."}. See Asynchronous requests & callbacks. | null |
| state | string | Optional value returned unchanged in the callback payload. Use it to associate the result with the original request. | null |
| landscape | boolean | Uses landscape instead of portrait page orientation. | false |
| format | string | Page format. Supported values:Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6 | A4 |
| width | integer | Custom page width in pixels. Use together with the height parameter. | null |
| height | integer | Custom page height in pixels. Use together with the width parameter. | null |
| singlePage | boolean | Puts the whole document on one page, with no page breaks. The page is as wide as width, or as the format (its long side with landscape), and as tall as the content plus marginTop and marginBottom; height is ignored. Content up to 1.5 times wider than the page is scaled down to fit, and headerTemplate and footerTemplate appear once. For a URL whose images load on scroll, also set lazyLoad. See Single-page PDFs. | false |
| marginTop marginRight marginBottom marginLeft | integer | Whitespace, in pixels, between the page edge and its content. | 0 |
| filename | string | Sets the filename returned with the generated PDF. | null |
| waitFor | integer | Number of seconds to wait before generating the PDF. Use this when the page needs time to finish JavaScript or load asynchronous resources. Supported range: 0 to 10. | 0 |
| lazyLoad | boolean | Scrolls through the page before generating the PDF so that lazy-loaded images and content are loaded. Adds time on long pages, and effects the page runs while scrolling (such as a sticky header) can appear in the PDF. See Lazy-loaded images. | false |
| media | string | CSS media mode used while rendering. Supported values: print and screen. | screen |
| printBackground | boolean | Prints background colours and images. Set it to false to leave them out. | true |
| scale | number | Scale applied to the document content. Supported range: 0.1 to 2. | 1 |
| headerTemplate footerTemplate | string | HTML markup rendered in the page header or footer. Use sufficient marginTop or marginBottom to make the template visible. | null |
| userPassword | string | Password required to open the encrypted PDF. Setting this value enables encryption. | null |
| ownerPassword | string | Owner password that grants full access to the encrypted PDF and its permissions. | null |
| permissions | array | Actions available to a user who opens the encrypted PDF. See the Encryption section for supported values. | ["print"] |
Check the HTTP status before treating a response as a PDF. A non-2xx response is an error and must not be saved or returned with an application/pdf content type.
The body of an error response is JSON, with a numeric errorCode and a message that says what went wrong:
{
"errorCode": 12,
"message": "net::ERR_CONNECTION_RESET at https://example.com/"
}| Status | Meaning | Recommended action |
|---|---|---|
400 | The page could not be rendered, the source URL is not accessible, or a request parameter is invalid. | Read the message, then correct the URL or the request values. For errorCode 12, see below. |
401 | The API key is missing or invalid. | Check the X-API-Key header and replace an invalid key. |
403 | The account has reached a limit included in its current plan. | Review the plan limits and the notification email sent to the account before retrying. |
500 | An unhandled server error occurred. | Retry after a short delay. Use increasing delays between repeated attempts and contact support if the error persists. |
A 400 with errorCode 12 means the request was valid, but the page could not be converted. The message says why:
| Message | Meaning | Recommended action |
|---|---|---|
The URL points to a PDF file… or …a file that browsers download… | The URL serves a file, not a web page. | Use the URL of a web page. Retrying will not help. |
net::ERR_…, such as net::ERR_CONNECTION_RESET or net::ERR_HTTP2_PROTOCOL_ERROR | The site refused or dropped the connection, or did not answer in time. | This can be temporary, so retry a few times with increasing delays. If it keeps failing, the site may be blocking automated browsers or requests from cloud providers. |
The page did not finish rendering within the time limit | The page is too slow or too heavy to render in time. | Retry later. If it keeps failing, reduce what the page loads or split it into smaller documents. |
400, 401, or 403 responses without first correcting the request, credentials, or account limit. The exception is a 400 with errorCode 12 from a site that was unreachable or slow, as described above.Each plan sets a monthly number of credits and how many conversions can run at the same time. Every option is available on every plan.
| Plan | Credits per month | Parallel conversions | PDF size |
|---|---|---|---|
| Free | 100 | 1 | Up to 1 MB |
| Startup | 1,000 | 3 | No cap |
| Standard | 5,000 | 10 | No cap |
| Scale | 10,000 | 20 | No cap |
| Scale 25 | 25,000 | 25 | No cap |
| Scale 50 | 50,000 | 50 | No cap |
| Scale 75 | 75,000 | 75 | No cap |
| Scale 100 | 100,000 | 100 | No cap |
html instead of the markup.waitFor can delay rendering by up to 10 seconds.403 response. See Errors.See Pricing for the price of each plan.
By default, the API keeps the request open while it generates the PDF and then returns the document as binary data. Provide a callBackUrl when the document should be generated in the background instead.
Send the request to the normal generate endpoint and authenticate with the same X-API-Key header used for synchronous conversions. When authentication succeeds and the request is accepted for processing, the API responds with 202 Accepted. A 202 response is confirmation that the job was queued; its body is not the generated PDF.
curl --fail --show-error \
--request POST https://api.html2pdf.app/v1/generate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <your-api-key>' \
--data '{
"html": "https://www.example.com",
"callBackUrl": "https://your-app.com/webhooks/pdf",
"state": "order-123"
}'When processing finishes, html2pdf.app sends a POST request to callBackUrl. The callback JSON contains the generated PDF as a base64-encoded document value. Decode that value before saving or serving the PDF.
{
"document": "JVBERi0xLjQ...==",
"state": "order-123"
}The optional state value is returned unchanged, which lets you associate the callback with the original order, report, or job.
POST requests.Set singlePage to true to put the whole document on one page, with no page breaks. The page is as tall as the content, so nothing is split between pages and no blank space is left at the end.
It suits receipts and order confirmations whose length varies, long web pages kept as one document, and reports or dashboards that are read on screen rather than printed.
curl --fail --show-error --output page.pdf --request POST \
--url https://api.html2pdf.app/v1/generate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <your-api-key>' \
--data '{
"html": "https://example.com",
"singlePage": true,
"width": 1280
}'width in pixels, or the width of format (its long side with landscape). height is ignored.marginTop and marginBottom.break-after: page are ignored.headerTemplate and footerTemplate appear once, at the top and the bottom of the page.lazyLoad, so that they are loaded before the page is measured. See Lazy-loaded images.Many pages load images only when they scroll into view, with loading="lazy" or a JavaScript loader. A PDF is generated without scrolling, so by default images further down such a page can be missing or show a placeholder.
Set lazyLoad to true to scroll through the page before the PDF is generated, the way a visitor would. Images and content that load on scroll are loaded, and the page is scrolled back to the top before it is rendered.
curl --fail --show-error --output page.pdf --request POST \
--url https://api.html2pdf.app/v1/generate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <your-api-key>' \
--data '{
"html": "https://example.com",
"lazyLoad": true
}'loading="lazy" are loaded straight away, and the page is scrolled from top to bottom so that JavaScript loaders see every element.waitFor: the wait starts after the scroll.lazyLoad too, so that every image is on the page.Set userPassword to encrypt the PDF and require a password when it is opened. Use permissions to control which actions are available after opening it. An optional ownerPassword grants full access to the document and its permission settings.
Encryption is enabled when userPassword is set. By default, permissions is ["print"], allowing a user who opens the document to print it.
| Parameter | Type | Description | Default |
|---|---|---|---|
| userPassword | string | The password that you want users to use for opening the PDF. | null |
| ownerPassword | string | Grants full access to the PDF, including actions restricted by permissions and the ability to modify those permissions. | null |
| permissions | array | Field controlling what actions a user entering userPassword can perform. print - allow printing.modify - allow document modification, signing, and form-field changes.copy - allow content copying and copying for accessibility.edit - allow commenting.fillform - allow filling of form fields.extract - allow content copying for accessibility.assemble - allow document assembly operations such as rotating, inserting, or deleting pages and managing bookmarks and thumbnails.printbest - allow high-resolution printing when print is also allowed. | ["print"] |
curl --fail --show-error --output example.pdf --request POST \
--url https://api.html2pdf.app/v1/generate \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <your-api-key>' \
--data '{
"html": "https://example.com",
"userPassword": "user",
"ownerPassword": "owner",
"permissions": ["print", "modify", "copy"]
}'Use CSS break rules to start each selected element on a new PDF page. Include the legacy page-break-* properties as a fallback.
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Paginated HTML</title>
<style>
.page {
break-after: page;
break-inside: avoid;
page-break-after: always;
page-break-inside: avoid;
}
.page:last-child {
break-after: auto;
page-break-after: auto;
}
</style>
</head>
<body>
<section class="page">
<h1>This is page 1</h1>
</section>
<section class="page">
<h1>This is page 2</h1>
</section>
<section class="page">
<h1>This is page 3</h1>
</section>
</body>
</html>To put the whole document on one page with no breaks instead, see Single-page PDFs.
The fonts listed below are available in the rendering environment.
@font-face. Font stylesheets and files must be publicly reachable without authentication. If they load asynchronously, use waitFor to give the page time to finish rendering. No emoji font is installed, so load one as a web font if your document uses emoji.| Andale Mono | Comic Sans MS | Tahoma |
| Arial | Courier New | Times New Roman |
| Arial Black | Georgia | Trebuchet MS |
| Arial Narrow | Impact | Verdana |
| Arial Unicode MS | Microsoft Sans Serif | Webdings |
Generated PDFs are deleted from our servers immediately after they are delivered, in the API response or to your callback URL, and are never stored permanently. Raw HTML or text submitted in the html parameter is not stored in conversion logs. Selected request metadata and a source URL supplied in html may be retained in those logs.
See the Privacy Policy and Data Processing Agreement for processing, retention, and security details.