To turn HTML into a PDF with curl, POST JSON to https://api.relaypdf.com/v1/pdf. Send Authorization: Bearer pdf_live_... and Content-Type: application/json. Put the markup in html. Put a .pdf name in filename. Default response is binary: write the body with --output. That is the html to pdf api curl path. Canonical samples live at https://relaypdf.com/docs/quickstart/curl. The same JSON fields are used by the official SDKs.
Provide exactly one source: html, url, markdown, or templateId. Mixing two is invalid_request. Keys are created in the dashboard and shown once. Query-string keys are not accepted. Coding agents should run npx @relaypdf/cli setup instead of asking anyone to paste a key. The examples below use the public placeholder pdf_live_... Replace it, or export RELAYPDF_API_KEY and interpolate.
Canonical copy-paste
The live curl quickstart lists this as “Basic binary output” for POST /v1/pdf. It is the shortest request that returns a file. The docs hub repeats a slightly longer invoice body with the same headers and --output. Both are valid CreatePdfRequest JSON. Field names are camelCase.
curl https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{"html":"<h1>Hello</h1>","filename":"hello.pdf"}'
--output hello.pdf
curl defaults to POST when you pass -d, so the quickstart omits -X POST. Either form works. --output writes the raw HTTP body. On success that body is application/pdf. On failure it is JSON { "error": { "code", "message" } }. Check the first bytes or the HTTP status before you treat the file as a PDF. Use -f or -w "%{http_code}" if you want the shell to fail on 4xx/5xx.
curl -X POST https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{
"html": "<h1>Invoice #1042</h1><p>Total: $1,200.00</p>",
"filename": "invoice.pdf"
}'
--output invoice.pdf
Binary vs response:url
Every generating endpoint accepts response: "binary" | "url" | "async". Default is binary. You do not need to send the field for the copy-paste above. Binary returns file bytes plus x-relaypdf-id, x-relaypdf-size, and content-disposition. That is the right mode when the next step is a local file, an email attachment, or a stream to the client.
Set "response": "url" when the next hop should fetch the PDF later. The HTTP body is JSON, not PDF bytes. Do not use --output hello.pdf for this mode unless you intend to save JSON. OpenAPI names the object FileUrlResponse. Required fields: id, status, url, filename, sizeBytes, expiresAt. status is completed. url is GET /v1/files/:id. That download is public for 24 hours and does not need a key. After expiry, GET returns not_found.
curl https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{
"html": "<h1>Hello</h1>",
"filename": "hello.pdf",
"response": "url"
}'
A completed url-mode body looks like this shape (values are illustrative):
{
"id": "file_...",
"status": "completed",
"url": "https://api.relaypdf.com/v1/files/file_...",
"filename": "hello.pdf",
"sizeBytes": 12345,
"expiresAt": "2026-08-24T16:00:00.000Z"
}
Then GET the url with a second curl --output hello.pdf. No Authorization header. Treat the 24-hour object as a transfer, not as permanent storage. If you need a webhook or a poll instead of a blocking POST, use "response": "async". That returns 202 with a pollUrl. Poll GET /v1/jobs/:id with the same bearer key until status is completed or failed. callbackUrl, if you set it, must be https. Async jobs are a separate guide; this page stays on binary and url.
| response | HTTP | Body | When |
|---|---|---|---|
| binary (default) | 200 | PDF bytes; x-relaypdf-id, x-relaypdf-size, content-disposition | Save or stream now. Use --output. |
| url | 200 | JSON FileUrlResponse (id, status, url, filename, sizeBytes, expiresAt) | Hand a 24h GET /v1/files/:id to another system. No key on download. |
| async | 202 | Job accepted; pollUrl. GET /v1/jobs/:id | Long render or webhook (callbackUrl https). |
Auth, host, and JSON rules
Host is https://api.relaypdf.com. Local override in the SDKs is http://localhost:8787; production curl should not use that. Bearer scheme only. The docs show Authorization: Bearer pdf_live_… Keys are hashed at rest (SHA-256). A missing or unknown key is unauthorized (401). An empty wallet is payment_required (402). Rate limits return rate_limited (429) with Retry-After. Failed operations, 429s, and 402s are never billed.
The body is one JSON object. Do not send multipart. Do not send form fields. html is a string of markup, not a file path. curl -d @payload.json is fine if the file is that object. filename should end in .pdf when you set it. options is an object of Chromium print fields, not a query string.
Options you can add on the same POST
PDF options sit under options on POST /v1/pdf. Names match https://relaypdf.com/docs/options. Defaults that matter for a first curl: format is letter; printBackground is true; waitUntil is networkidle0; landscape is false; preferCSSPageSize is false; scale is 1. timeout is the Chromium budget in milliseconds, max 60000. waitForTimeout is an extra wait, max 30000. headerTemplate and footerTemplate are HTML; they enable Chromium headers and need extra margin. Placeholders documented on the options page: pageNumber, totalPages, date, title.
curl https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{
"html": "<h1>Invoice #1042</h1><p>Total: $1,200.00</p>",
"filename": "invoice.pdf",
"options": {
"format": "letter",
"printBackground": true,
"margin": {
"top": "16mm",
"right": "12mm",
"bottom": "16mm",
"left": "12mm"
}
}
}'
--output invoice.pdf
A public page uses url instead of html. Markdown uses markdown. A published Handlebars layout uses templateId plus templateData. Those are still POST /v1/pdf. Screenshots are a different route: POST /v1/images. The curl quickstart shows a URL-to-PNG sample there. Office files go to POST /v1/convert, not /v1/pdf.
curl https://api.relaypdf.com/v1/pdf
-H "Authorization: Bearer pdf_live_..."
-H "Content-Type: application/json"
-d '{"markdown":"# Hello from Markdown"}'
--output hello.pdf
Errors and price
Failures are structured: { "error": { "code", "message" } }. Branch on code. Common codes on this endpoint: invalid_request (400) for field names, types, or more than one source; unauthorized (401); url_not_allowed (400) for private, loopback, or metadata hosts, or a non-https callbackUrl; payload_too_large (413); render_failed (502) when Chromium cannot print; payment_required (402); rate_limited (429). There is no published megabyte figure. Do not invent one.
A successful HTML, URL, or Markdown PDF is $0.015 on the live rate card (https://relaypdf.com/pricing). TemplateId jobs are the same $0.015. New accounts still get $5 trial credit with no card. Credit does not expire. Only a successful generation debits. Trial rate limit is 20/min; funded or auto-reload is 60/min; burst is 5 / 10s (SDK READMEs).
What this page is not
This is not the Node, Python, or Next.js how-to. Those call the same REST fields through an SDK. This is not merge, protect, stamp, or convert. Those are other POSTs. This is not OCR, e-sign, PDF/A, or HIPAA — none of those are product claims. Use this page when you want raw HTTP and a file on disk.
Ship it
Copy the five-line request from https://relaypdf.com/docs/quickstart/curl. Swap pdf_live_... for your key. Keep --output for binary. Switch to "response": "url" when you want JSON and a 24-hour GET /v1/files/:id. Add options only when letter and printBackground are not enough. Full field list: https://relaypdf.com/openapi.json. Product page: https://relaypdf.com/html-to-pdf. Docs hub: https://relaypdf.com/docs.