PDF generation API
HTML or a zipped web app to PDF or PNG, PDFs merged, a PDF to JPEG images. The rendering service that Numsolar's own PDF offers use.
| Endpoint | What it does |
|---|---|
GET /api/pdf_generation
|
Health check: the render tools that are installed and the busy render slots. |
POST /api/pdf_generation/html-to-pdf
|
One HTML page to a PDF. |
POST /api/pdf_generation/html-to-pngs
|
One HTML page to PNG images (one per page, or one full-page image). |
POST /api/pdf_generation/zip-to-pdf
|
A zipped web app (for example a PDF template) to a PDF. |
POST /api/pdf_generation/zip-to-pngs
|
A zipped web app to PNG images. |
POST /api/pdf_generation/merge-pdfs
|
Several PDFs to one PDF, in the given order. |
POST /api/pdf_generation/pdf-to-images/convert
|
A PDF to one JPEG per page, plus small previews. |
Authentication
Send the key in the x-api-key header on every call. The key is checked before the
body is read. A missing or wrong key gets 403 {"message": "Forbidden"}. After 10 wrong keys or bad bodies in a minute from one IP, every call of that IP gets 429 until the minute ends. Bodies are
JSON, at most 20 MB. There is no CORS: call it from your server.
Files in and out
- Input files (
url,pdfUrl,urls[]) are publichttpslinks (port 443) that the server downloads. A link to a private, local or cloud-metadata address is refused; each redirect is checked again (at most 3); a file can be at most 100 MB. A refused or failed download gets 400. - Results always go to a new folder
pdf-generation-api/<id>/. OfresultPathonly the file name is kept;resultDirandoutputS3Dirare replaced by the new folder. A call never writes to or over an existing file. The answer gives the stored path (s3_path,imagePaths) and a signedurlthat works for 1 hour. - A page may load its own files,
data:URLs and Google Fonts. Other requests from the page are blocked.
Endpoints
GET
/api/pdf_generation
Health check: the render tools that are installed and the busy render slots.
{
"ok": true,
"checks": {"node": true, "render_script": true, "chromium": true, "pdftoppm": true, "pdftk": true},
"in_use": {"chromium": 0, "tools": 0}
}
POST
/api/pdf_generation/html-to-pdf
One HTML page to a PDF.
| Field | Type | Description |
|---|---|---|
htmlrequired
|
string | The whole page. |
resultPathrequired
|
string | Where to store the PDF. |
awaitForSelector
|
string | A CSS selector to wait for before printing (at most 10 s), e.g. html.offer-ready. |
data
|
object | JSON the page can fetch from http://request.data/. |
settings
|
object | Render settings passed to the page. |
The page size comes from the page's CSS (@page); without it the PDF is A4. An element
#pageformat ({"width", "height"}) and #documentmetadata (title,
author, subject, keywords, creator) set the size and the PDF metadata.
{
"html": "<!doctype html><html><body><h1>Offer 2026-101</h1></body></html>",
"resultPath": "partner-acme/offers/2026-101.pdf"
}
curl -s -X POST 'https://numsolar.com/api/pdf_generation/html-to-pdf' -H 'content-type: application/json' -H 'x-api-key: YOUR_KEY' \
-d @pdf_html_to_pdf.request.json
{
"s3_path": "partner-acme/offers/2026-101.pdf",
"url": "https://numsolar.com/storage/s/<signed token>/2026-101.pdf"
}
POST
/api/pdf_generation/html-to-pngs
One HTML page to PNG images (one per page, or one full-page image).
As html-to-pdf, with resultDir instead of resultPath and page (true: one PNG per page, screenshot-page-1.png, …; false: one screenshot-page-full.png). The answer is a list of {"s3_path", "url"}.
POST
/api/pdf_generation/zip-to-pdf
A zipped web app (for example a PDF template) to a PDF.
url (the zip), archivePath (the page in the zip, e.g. dist/index.html) and resultPath; plus the optional fields of html-to-pdf. A PDF template zip renders this way.
POST
/api/pdf_generation/zip-to-pngs
A zipped web app to PNG images.
As zip-to-pdf, with resultDir and page as in html-to-pngs.
POST
/api/pdf_generation/merge-pdfs
Several PDFs to one PDF, in the given order.
urls (at least one PDF link, in order) and resultPath. The answer is {"s3_path", "url"}.
POST
/api/pdf_generation/pdf-to-images/convert
A PDF to one JPEG per page, plus small previews.
pdfUrl and outputS3Dir. The answer is {"fileId", "imagePaths", "previewPaths",
"pageCount", "ts"}: one JPEG per page (2000 px on the long side) and a preview of each
(within 300 × 300 px). Pages count from 0.
Status codes
Errors are {"error": "…"} (403: {"message": "Forbidden"}).
| Status | Retry-After | When |
|---|---|---|
| 403 | – | The x-api-key header is missing or wrong (body {"message": "Forbidden"}). |
| 429 | seconds until the minute ends | 10 failed calls (a wrong key, a body that is not valid JSON or too large) from your IP this minute. Every call of that IP gets 429 until the minute ends. |
| 400 | – | A required field is missing or wrong, the JSON is not valid, or the content type is not JSON. Also: an input link that is not a public https link, a download that failed, or a zip that is not valid or has unsafe paths (Could not download an input file. Use a public https link.). |
| 413 | – | The body is larger than 20 MB. |
| 503 | 30 | No render slot came free in time. Try again later. |
| 504 | – | The download, the wait and the render took longer than 30 s (120 s for pdf-to-images). |
| 500 | – | The render failed. error tells why; a page failure also has log (the page's console). |
The server renders 2 pages at a time and runs 4 PDF tools at a time; a call waits for a free slot until its deadline.