Render HTML and CSS exactly as a browser does, then take it away as a PDF or a full-page PNG. Grid, flexbox, gradients and web fonts all work, with optional JavaScript execution and full control over page size, margins and scale.
Invoices, reports, certificates, tickets and dashboards — rendered by the same engine that draws them on screen, so the output matches the design instead of approximating it.
Grid, flexbox, custom properties, gradients and transforms, rendered by a real browser engine.
A warm engine renders a typical document in about a quarter of a second.
Named or custom page sizes, orientation, margins and scale.
Run scripts before capture so charts and client-rendered content appear in the output.
Web fonts and local families load and are waited for before the page is captured.
Take a paginated PDF, or a full-height screenshot of the entire page.
Start free and move up when you need more. Every plan renders with the same engine — higher tiers unlock larger documents, more output formats and full styling control.
Everything you need to integrate the HTML to PDF API
Every request needs your API key in the x-api-key header. Keys are created in the dashboard and belong to your account rather than to a single API.
x-api-key: your_api_keyhttps://api.corenexis.com/html-to-pdf/v1One synchronous endpoint. Send the HTML and the finished document comes back in the same response — there is no job to poll.
Documents are rendered by a real browser engine, so what you see in a browser is what lands in the PDF. Grid, flexbox, custom properties, gradients, transforms, web fonts and SVG all work without a compatibility layer.
Stylesheets, images and web fonts referenced by public URL are fetched and waited for before capture. Reference them absolutely — a relative path has no page to resolve against.
The HTML can arrive in any of these forms. Use whichever suits your environment.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: your_api_key" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Invoice</h1><p>Thank you.</p>", "page_size": "a4"}'curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: your_api_key" \
-F "html=<h1>Hello</h1>" \
-F "page_size=letter"curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: your_api_key" \
-F "[email protected]"curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: your_api_key" \
-H "Content-Type: text/html" \
--data-binary @invoice.html| Parameter | Type | Description |
|---|---|---|
| x-api-keyRequired | String | Your API key. Send in the request header. |
| htmlRequired | String | The HTML to render. May also be sent as an uploaded file or as the raw request body. |
| formatOptional | String | pdf or png. PNG requires a paid plan.Default: pdf |
| outputOptional | String | Only url. Every result is delivered as a hosted link, on every plan, so this can be omitted. base64 and binary were withdrawn and now return a clear error.Default: url |
| page_sizeOptional | String | a3, a4, a5, letter, legal or tabloid.Default: a4 |
| orientationOptional | String | portrait or landscape.Default: portrait |
| widthOptional | Number | Custom page width in millimetres, 20 to 2000. Must be sent with height. |
| heightOptional | Number | Custom page height in millimetres, 20 to 2000. Must be sent with width. |
| margin_topOptional | Number | Top margin in millimetres, 0 to 100. Default: 20 |
| margin_rightOptional | Number | Right margin in millimetres, 0 to 100. Default: 15 |
| margin_bottomOptional | Number | Bottom margin in millimetres, 0 to 100. Default: 20 |
| margin_leftOptional | Number | Left margin in millimetres, 0 to 100. Default: 15 |
| scaleOptional | Number | Render scale, 0.1 to 2. Default: 1 |
| jsOptional | Boolean | Run scripts in the page before capture. Paid plans only. Default: false |
| wait_msOptional | Number | Extra delay after load, 0 to 5000 ms. Requires js.Default: 0 |
| header_htmlOptional | String | HTML for the running header. Paid plans only. |
| footer_htmlOptional | String | HTML for the running footer. Paid plans only. |
| filenameOptional | String | Name for the produced file, without extension. Default: document |
Set js to true to run scripts before capture, and use wait_ms to allow for work that finishes after load. Charts, templating and client-side rendering all work. Available on paid plans.
{
"html": "<div id=\"chart\"></div><script>renderChart()</script>",
"js": true,
"wait_ms": 500
}wait_ms.Send header_html and footer_html to place a running header or footer on every page. Inside them, <span class="pageNumber"></span> and <span class="totalPages"></span> are replaced with page numbers.
{
"html": "<h1>Report</h1>",
"margin_bottom": 25,
"footer_html": "<div style=\"font-size:9px;width:100%;padding:0 12mm;text-align:right;color:#666\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
}margin_top or margin_bottom or it will overlap the content.Every result is stored and handed back as a hosted link under data.url, on every plan including the free one. There is nothing to decode and nothing to stream.
| Plan | Link valid for | Why |
|---|---|---|
| Free | 2 hours | Long enough to fetch and use the file in the workflow that produced it. |
| Any paid plan | 7 days | Long enough to email, queue or hand on to another service. |
Inline delivery was withdrawn. A caller still sending the old value gets an error that says so rather than a silent change of behaviour.
| output | Status | Notes |
|---|---|---|
base64 | Withdrawn | The file inside the JSON response. It inflated every result by a third and had to be held in memory twice. |
binary | Withdrawn | The raw file as the response body. It could not carry a document that had become several images. |
url | The only mode | A hosted link under data.url. This is now the default, so the parameter can be omitted. |
Set format to png to receive images instead of a PDF. PNG output is a paid feature.
| png_mode | What you get | When to use it |
|---|---|---|
stack | The document laid out into pages, returned as an ordered sequence of images in data.images. | Default. Reading or displaying a whole document. |
first | One image of page one. | Thumbnails and previews. |
page | One image of the page named by png_page. | Pulling a single page out of a long document. |
viewport | One continuous screenshot with no page breaks, at the page's natural height. | Capturing a design rather than a document. |
image.truncated tells you whether the set is complete. viewport is still there when you want one unbroken image, and it reports clipped when it had to stop.import base64, requests
resp = requests.post(
"https://api.corenexis.com/html-to-pdf/v1",
headers={"x-api-key": "your_api_key"},
json={
"html": open("invoice.html").read(),
"page_size": "a4",
"margin_top": 12,
"margin_bottom": 12,
"js": True,
"wait_ms": 300,
},
timeout=120,
)
data = resp.json()
if not data["success"]:
raise SystemExit(data["message"])
with open("invoice.pdf", "wb") as fh:
fh.write(base64.b64decode(data["data"]["content"]))const fs = require("fs");
const res = await fetch("https://api.corenexis.com/html-to-pdf/v1", {
method: "POST",
headers: { "x-api-key": "your_api_key", "Content-Type": "application/json" },
body: JSON.stringify({
html: fs.readFileSync("invoice.html", "utf8"),
format: "png",
output: "url",
}),
});
const data = await res.json();
if (!data.success) throw new Error(data.message);
console.log(data.data.url, data.data.image);<?php
$ch = curl_init('https://api.corenexis.com/html-to-pdf/v1');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'x-api-key: your_api_key',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'html' => file_get_contents('invoice.html'),
'page_size' => 'a4',
'orientation' => 'landscape',
'output' => 'binary',
]),
]);
$pdf = curl_exec($ch);
curl_close($ch);
file_put_contents('invoice.pdf', $pdf);urllib sends a default agent string our edge network refuses. The requests library, Node, Go, Java and cURL all work unchanged.{
"success": true,
"plan": "pro",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"pages": 1,
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 305,
"url": "https://cdn.corenexis.com/f/qLm6VWHVlur.pdf",
"filename": "document.pdf",
"size_bytes": 45589,
"expires_at": "2026-08-16T06:24:41+00:00"
},
"usage": { "remaining": 2498, "rate_limit": 15, "monthly_limit": 2500 }
}Always check the success field rather than the status code alone. On failure the body carries a code and a message naming what to change.
Every option below was sent to the live API and the response shown is what came back, including the request that produced it. Copy a command, put your own key in it, and you get the same shape of answer.
Only the document is required. Grid, flexbox, gradients and web fonts all render, because a real browser engine draws the page.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document..."
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 1539,
"pages": 1,
"url": "https://cdn.corenexis.com/f/M5Cd8FtSTtV.pdf",
"filename": "document.pdf",
"size_bytes": 16304,
"expires_at": "2026-08-09T06:57:53+00:00"
},
"usage": {
"remaining": 4987,
"rate_limit": 120,
"monthly_limit": 5000
}
}One of a3, a4, a5, letter, legal or tabloid, in either orientation.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document...",
"page_size": "letter",
"orientation": "landscape"
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "letter",
"orientation": "landscape",
"scale": 1,
"js_enabled": false,
"render_time_ms": 318,
"pages": 1,
"url": "https://cdn.corenexis.com/f/J8XaCP3y547.pdf",
"filename": "document.pdf",
"size_bytes": 16276,
"expires_at": "2026-08-09T06:57:53+00:00"
},
"usage": {
"remaining": 4986,
"rate_limit": 120,
"monthly_limit": 5000
}
}Millimetres, 20 to 2000, supplied together. Overrides page_size, and the response reports page_size as custom so it is clear which applied.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document...",
"width": 150,
"height": 100
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "custom",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 453,
"pages": 1,
"width_mm": 150,
"height_mm": 100,
"url": "https://cdn.corenexis.com/f/n6LiWb0bJ0D.pdf",
"filename": "document.pdf",
"size_bytes": 16296,
"expires_at": "2026-08-09T06:57:54+00:00"
},
"usage": {
"remaining": 4985,
"rate_limit": 120,
"monthly_limit": 5000
}
}Millimetres, 0 to 100 each.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document...",
"margin_top": 25,
"margin_right": 20,
"margin_bottom": 25,
"margin_left": 20
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 311,
"pages": 1,
"url": "https://cdn.corenexis.com/f/0iaopelfqvs.pdf",
"filename": "document.pdf",
"size_bytes": 16289,
"expires_at": "2026-08-09T06:57:54+00:00"
},
"usage": {
"remaining": 4984,
"rate_limit": 120,
"monthly_limit": 5000
}
}0.1 to 2.0. Scales the rendered page before it is paginated, so 0.75 fits more onto each sheet rather than shrinking the sheet.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document...",
"scale": 0.75
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "a4",
"orientation": "portrait",
"scale": 0.75,
"js_enabled": false,
"render_time_ms": 290,
"pages": 1,
"url": "https://cdn.corenexis.com/f/in3LLDB6jqu.pdf",
"filename": "document.pdf",
"size_bytes": 16317,
"expires_at": "2026-08-09T06:57:55+00:00"
},
"usage": {
"remaining": 4983,
"rate_limit": 120,
"monthly_limit": 5000
}
}Off by default. wait_ms waits after load for scripted content to settle, and only applies when JavaScript is enabled.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<html><body><h1 id='t'>placeholder</h1><script>document.getElementById('t').textContent='written by script';</script></body></html>",
"js": true,
"wait_ms": 300
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": true,
"render_time_ms": 2507,
"pages": 1,
"url": "https://cdn.corenexis.com/f/YL7zK3syOjb.pdf",
"filename": "document.pdf",
"size_bytes": 7764,
"expires_at": "2026-08-09T06:57:57+00:00"
},
"usage": {
"remaining": 4982,
"rate_limit": 120,
"monthly_limit": 5000
}
}Full HTML, up to 2000 characters each. The browser substitutes pageNumber and totalPages.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...long document...",
"header_html": "<div style='font-size:9pt;width:100%;text-align:center'>Invoice</div>",
"footer_html": "<div style='font-size:9pt;width:100%;text-align:center'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>"
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "pdf",
"mime": "application/pdf",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 2194,
"pages": 94,
"url": "https://cdn.corenexis.com/f/TSC4XHrqoJK.pdf",
"filename": "document.pdf",
"size_bytes": 527013,
"expires_at": "2026-08-09T06:57:59+00:00"
},
"usage": {
"remaining": 4981,
"rate_limit": 120,
"monthly_limit": 5000
}
}Pages are rendered and returned as an ordered sequence of images, so a long document comes back complete.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...long document...",
"format": "png"
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "png",
"mime": "image/png",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 56288,
"pages": 94,
"url": "https://cdn.corenexis.com/f/3Vqgm5fTcVa.png",
"filename": "document-p1-17.png",
"size_bytes": 1379418,
"expires_at": "2026-08-09T06:58:56+00:00",
"image": {
"mode": "stack",
"dpi": 144,
"pages_total": 94,
"pages_rendered": 94,
"pages_per_image": 17,
"truncated": false
},
"image_count": 6,
"total_bytes": 7571082,
"images": [
{
"url": "https://cdn.corenexis.com/f/3Vqgm5fTcVa.png",
"filename": "document-p1-17.png",
"size_bytes": 1379418,
"expires_at": "2026-08-09T06:58:56+00:00",
"page_from": 1,
"page_to": 17,
"width": 1192,
"height": 28628
},
{
"url": "https://cdn.corenexis.com/f/HHMBPPVvek2.png",
"filename": "document-p18-34.png",
"size_bytes": 1381082,
"expires_at": "2026-08-09T06:58:56+00:00",
"page_from": 18,
"page_to": 34,
"width": 1192,
"height": 28628
},
"... 4 more, one per page range, in order"
]
},
"usage": {
"remaining": 4980,
"rate_limit": 120,
"monthly_limit": 5000
}
}Any page the document has.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...long document...",
"format": "png",
"png_mode": "page",
"png_page": 12
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "png",
"mime": "image/png",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 2229,
"pages": 94,
"url": "https://cdn.corenexis.com/f/H7AoJ1d5zIR.png",
"filename": "document.png",
"size_bytes": 126506,
"expires_at": "2026-08-09T06:58:58+00:00",
"image": {
"mode": "page",
"dpi": 144,
"pages_total": 94,
"pages_rendered": 1,
"pages_per_image": 17,
"truncated": false
},
"image_count": 1,
"total_bytes": 126506,
"images": [
{
"url": "https://cdn.corenexis.com/f/H7AoJ1d5zIR.png",
"filename": "document.png",
"size_bytes": 126506,
"expires_at": "2026-08-09T06:58:58+00:00",
"page_from": 12,
"page_to": 12,
"width": 1192,
"height": 1684
}
]
},
"usage": {
"remaining": 4979,
"rate_limit": 120,
"monthly_limit": 5000
}
}One continuous screenshot with no page breaks, for capturing a design rather than a document. It stops at the renderer's maximum height, so clipped tells you whether the whole page was captured.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document...",
"format": "png",
"png_mode": "viewport"
}'{
"success": true,
"plan": "basic",
"data": {
"status": "completed",
"format": "png",
"mime": "image/png",
"page_size": "a4",
"orientation": "portrait",
"scale": 1,
"js_enabled": false,
"render_time_ms": 435,
"url": "https://cdn.corenexis.com/f/OuQ6HZMAjhm.png",
"filename": "document.png",
"size_bytes": 20267,
"expires_at": "2026-08-09T06:58:59+00:00",
"image": {
"mode": "viewport",
"width": 1280,
"height": 1200,
"clipped": false
}
},
"usage": {
"remaining": 4978,
"rate_limit": 120,
"monthly_limit": 5000
}
}Inline delivery was withdrawn. Every result is a hosted link.
curl -X POST "https://api.corenexis.com/html-to-pdf/v1" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "...your document...",
"output": "binary"
}'{
"success": false,
"code": "INVALID_INPUT",
"message": "'output' no longer accepts 'binary'. Every result is delivered as a hosted link, so omit the parameter or send output=url. The response carries the link in data.url."
}Errors arrive as JSON with a stable code.
| Code | HTTP | Meaning |
|---|---|---|
MISSING_KEY | 401 | The x-api-key header was not sent. |
INVALID_KEY | 401 | The key does not exist or has been revoked. |
KEY_DISABLED | 403 | The key exists but has been switched off. |
ACCOUNT_SUSPENDED | 403 | The account is suspended. |
NO_SUBSCRIPTION | 402 | The account has no subscription to this API. |
SUBSCRIPTION_EXPIRED | 402 | The subscription lapsed and the grace period has passed. |
RATE_LIMIT_EXCEEDED | 429 | Too many requests this minute. Retry shortly. |
QUOTA_EXCEEDED | 429 | The monthly allowance is used up. |
PARAM_LIMIT_EXCEEDED | 400 | A requested feature or value is above what the plan allows. The response names the parameter. |
INVALID_INPUT | 400 | Something in the request is malformed. The message names the field. |
RENDER_TIMEOUT | 408 | The document took too long. Simplify it and retry. |
PROCESSING_FAILED | 422 | The document could not be rendered. |
RENDERER_BUSY | 503 | Every worker is busy. Retry in a moment. |
| Limit | Value |
|---|---|
| Maximum input size | 512 KB free, up to 2 MB on Max |
| Pages that means in practice | About 150 pages free, about 570 on Max, for ordinary prose |
| Page limit | There is no page limit. A 660-page document renders in about 20 seconds |
| Maximum delivered file | 15 MB |
| Render deadline | 25 seconds in the renderer. Beyond it you receive RENDER_TIMEOUT and nothing is charged |
| Hosted link lifetime | 2 hours on the free plan, 7 days on any paid plan |
| Concurrency | One document at a time, so latency stays predictable |
Documents render one at a time so latency stays predictable. If every worker is busy you receive RENDERER_BUSY and nothing is charged; retry a moment later.
The samples/ directory in the project repository holds real output across page sizes, orientations and both formats, so you can see what each option produces before calling the API.
Create your free account and turn your first HTML page into a PDF in seconds. No credit card required.