Documentation
Everything you need to convert HTML to pixel-perfect PDFs, and to extract text from documents, with a single API call.
Quickstart
DocForge has two endpoints: /render turns HTML into a PDF, and /extract turns a document into text. Here's the fastest way to see a render work:
curl -X POST https://api.docforge.io/render \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{"html": "<h1>Hello, PDF.</h1>"}' \ | jq -r .pdf | base64 -d > hello.pdf
Open hello.pdf — you should see a single-page document with "Hello, PDF." as the heading. That's the entire integration.
Authentication
Every request requires an API key passed in the x-api-key header. Request your key by signing up.
{
"Content-Type": "application/json",
"x-api-key": "YOUR_API_KEY"
}
Keep your API key secret. Never expose it in client-side code or public repositories. Use environment variables or a secrets manager.
Endpoint
POST https://api.docforge.io/render
All render configuration is passed in the request body. To get text out of a document, see Extract API.
Request Body
Send a JSON object with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
html | String | Yes | A complete HTML document to render as a PDF |
options | Object | No | PDF generation options (see below) |
The html field should be a complete HTML document — DOCTYPE, styles (inline <style> tags or external <link> references), and body content. DocForge renders it in headless Chromium, so anything that works in Chrome works here.
Options
Pass an options object to control the PDF output. All fields are optional.
| Option | Type | Default | Description |
|---|---|---|---|
format | String | "Letter" | Paper size. One of: Letter, Legal, A4, A3, Tabloid |
margin | Object | 0 all sides | Page margins with top, right, bottom, left keys. Values are CSS units (e.g., "1in", "20mm") |
printBackground | Boolean | true | Whether to render CSS backgrounds and colors |
landscape | Boolean | false | Paper orientation |
displayHeaderFooter | Boolean | false | Show page headers and footers with page numbers |
Margin Example
{
"options": {
"margin": {
"top": "1in",
"bottom": "1in",
"left": "0.75in",
"right": "0.75in"
}
}
}
Response
Success (200)
{
"pdf": "JVBERi0xLjQK...", // base64-encoded PDF binary
"pages": 2, // total page count
"sizeBytes": 24512, // PDF file size in bytes
"renderTimeMs": 1230 // server-side render time
}
The pdf field is a standard base64-encoded string. Decode it to get the raw PDF binary. The metadata fields (pages, sizeBytes, renderTimeMs) are informational — use them for logging, billing, or display.
Error Handling
| Status | Meaning | Response Body |
|---|---|---|
400 |
Missing or invalid html field |
{ "error": "Missing required field: html" } |
401 |
Invalid or missing API key | { "error": "Invalid or missing API key" } |
500 |
Rendering failed | { "error": "PDF rendering failed", "detail": "..." } |
A 500 error usually means the HTML caused the renderer to fail — malformed markup, an infinite-loading external resource, or content that exceeds the render timeout. Check the detail field for specifics.
Limits
| Limit | Value | Notes |
|---|---|---|
| Request body | 4 MB | A typical HTML document with inlined CSS is 50–100 KB |
| Response body | 6 MB | Most PDFs are well under 500 KB |
| Render timeout | 30 seconds | Most renders complete in under 4 seconds |
JavaScript Example
const html = ` <!DOCTYPE html> <html> <head> <style> body { font-family: 'Helvetica', sans-serif; padding: 40px; } h1 { color: #1e293b; } .page-break { break-before: page; } </style> </head> <body> <h1>Invoice #1042</h1> <p>Amount due: $2,400.00</p> <div class="page-break"> <h1>Terms & Conditions</h1> <p>Payment due within 30 days.</p> </div> </body> </html>` const response = await fetch('https://api.docforge.io/render', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.DOCFORGE_API_KEY }, body: JSON.stringify({ html, options: { format: 'Letter', margin: { top: '1in', bottom: '1in', left: '0.75in', right: '0.75in' } } }) }) const { pdf, pages, sizeBytes, renderTimeMs } = await response.json() // Save the PDF const buffer = Buffer.from(pdf, 'base64') fs.writeFileSync('invoice.pdf', buffer) console.log(`Generated ${pages} page PDF (${sizeBytes} bytes) in ${renderTimeMs}ms`)
Python Example
import requests, base64, os response = requests.post( "https://api.docforge.io/render", headers={ "Content-Type": "application/json", "x-api-key": os.environ["DOCFORGE_API_KEY"] }, json={ "html": "<h1>Hello from Python</h1>", "options": { "format": "A4", "margin": {"top": "20mm", "bottom": "20mm"} } } ) response.raise_for_status() data = response.json() with open("output.pdf", "wb") as f: f.write(base64.b64decode(data["pdf"])) print(f"Generated {data['pages']} page PDF in {data['renderTimeMs']}ms")
cURL Example
# Minimal — send HTML, save PDF curl -X POST https://api.docforge.io/render \ -H "Content-Type: application/json" \ -H "x-api-key: $DOCFORGE_API_KEY" \ -d '{"html": "<h1>Hello, PDF.</h1>"}' \ | jq -r .pdf | base64 -d > output.pdf # With options — A4, landscape, 1-inch margins curl -X POST https://api.docforge.io/render \ -H "Content-Type: application/json" \ -H "x-api-key: $DOCFORGE_API_KEY" \ -d '{ "html": "<h1>Landscape Report</h1><p>Wide content goes here.</p>", "options": { "format": "A4", "landscape": true, "margin": {"top": "1in", "bottom": "1in", "left": "1in", "right": "1in"} } }' \ | jq -r .pdf | base64 -d > report.pdf
Invoice Template
A more realistic example — a styled invoice with line items, totals, and a page break for terms and conditions.
<!DOCTYPE html> <html> <head> <link href="https://fonts.googleapis.com/css2? family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> <style> body { font-family: 'Inter', sans-serif; padding: 0; color: #1e293b; } .header { display: flex; justify-content: space-between; margin-bottom: 40px; } .company { font-size: 24px; font-weight: 700; } table { width: 100%; border-collapse: collapse; } th { text-align: left; border-bottom: 2px solid #e2e8f0; padding: 8px 0; } td { padding: 8px 0; border-bottom: 1px solid #f1f5f9; } .total { font-weight: 700; font-size: 18px; text-align: right; margin-top: 20px; } .terms { break-before: page; } .terms h2 { margin-bottom: 12px; } .terms p { color: #64748b; line-height: 1.6; } </style> </head> <body> <div class="header"> <div class="company">Acme Corp</div> <div>Invoice #1042<br>August 31, 2026</div> </div> <table> <tr><th>Description</th><th>Qty</th><th>Price</th></tr> <tr><td>API Integration</td><td>1</td><td>$1,200</td></tr> <tr><td>Monthly Hosting</td><td>1</td><td>$200</td></tr> </table> <div class="total">Total: $1,400.00</div> <div class="terms"> <h2>Terms & Conditions</h2> <p>Payment due within 30 days of invoice date.</p> </div> </body></html>
Page Breaks
DocForge respects standard CSS page-break properties. Use them to control where content splits across pages.
/* Force a page break before an element */ .new-page { break-before: page; } /* Prevent an element from being split across pages */ .keep-together { break-inside: avoid; } /* Always break after an element */ .section-end { break-after: page; } /* Set page margins via @page rule */ @page { margin: 1in; } @page :first { margin-top: 2in; /* extra space on first page */ }
If you set margins in both the options.margin request field and a CSS @page rule, the options.margin value takes precedence.
Web Fonts
DocForge can load external fonts during rendering. Google Fonts and other CDN-hosted font files are fully supported.
<link href="https://fonts.googleapis.com/css2? family=Inter:wght@400;700&display=swap" rel="stylesheet"> <style> body { font-family: 'Inter', sans-serif; } </style>
For maximum reliability, you can also base64-encode font files and embed them directly with @font-face and a data: URL. This eliminates any dependency on external font servers during rendering.
Headers & Footers
Set displayHeaderFooter: true in options to enable built-in page headers and footers. These use Chromium's default template which includes the page title, date, URL, and page number.
{
"options": {
"displayHeaderFooter": true,
"margin": {
"top": "1in",
"bottom": "1in"
}
}
}
Headers and footers render inside the margin area. You need sufficient margin (at least 0.5 inches) for them to be visible.
Extract: Endpoint
POST https://api.docforge.io/extract
Send a document and get back all of its text. Extraction uses the same API key and headers as render (see Authentication). It's synchronous: the text comes back in the response, and DocForge keeps nothing but a usage record.
Extract: Request Body
| Field | Type | Required | Description |
|---|---|---|---|
fileName | String | Yes | Original file name including its extension, e.g. resume.docx (max 255 characters). The extension selects the parser. |
fileBase64 | String | Yes | The file's contents, base64-encoded. Up to 4 MB before encoding. |
allowOcr | Boolean | No | Default true. Set false to refuse any PDF with pages that need OCR, with ocr_not_allowed, instead of reading them. |
DocForge returns the text in the file as-is: it doesn't summarize, clean up, or reformat. Scanned and image-only PDF pages are read with OCR automatically, unless you set allowOcr to false.
Extract: Supported Formats
| Format | Extensions | Notes |
|---|---|---|
.pdf | Text PDFs, plus scanned or image-only pages via OCR (up to 5 scanned pages) | |
| Word | .doc, .docx | Tables come back tab-separated, one row per line |
| Rich text | .rtf | Word, Google Docs, and TextEdit exports |
| Plain text | .txt | UTF-8, UTF-16, or Windows-1252 |
| Transcripts and captions | .vtt, .srt | Microsoft Teams, Zoom, and other exports, returned as-is |
| Anything else | any | Best effort: you get the text or a reason it couldn't be extracted |
Extract: Response
Success (200)
{
"text": "Zoë Ångström-Nuñez\nSenior Recruiter..."
}
Extract: Error Handling
Every error body is { "error": "...", "reason": "..." }. reason is a stable code you can branch on; error is a human-readable message.
| Status | reason | Meaning |
|---|---|---|
400 | invalid_request | Bad JSON, missing fileName or fileBase64, invalid base64, or no file extension |
401 | unauthorized | Invalid or missing API key |
413 | file_too_large | File is over 4 MB |
415 | unsupported_format | DocForge can't read this file type |
422 | unreadable_file | The file is corrupt or password-protected |
422 | no_extractable_text | The file contains no text |
422 | too_many_scanned_pages | More than 5 pages need OCR |
422 | ocr_not_allowed | The PDF has pages that need OCR, and the request set allowOcr to false |
422 | extracted_text_too_large | The extracted text is too large to return |
503 | ocr_unavailable | OCR is temporarily unavailable; retry later |
500 | processing_failed | Something else went wrong |
An extraction uses one credit (the same credits as render) only when it returns text. Failed extractions, including every error above, are free.
Extract: Limits
| Limit | Value | Notes |
|---|---|---|
| File size | 4 MB | Measured before base64 encoding |
| Scanned pages | 5 | Pages that need OCR; pages with a text layer don't count |
| Response size | 6,000,000 bytes | Serialized response; larger results return extracted_text_too_large |
| Timeout | 20 seconds | Most documents extract in well under a second |
JavaScript Example
import { readFile } from 'node:fs/promises' const file = await readFile('resume.docx') const response = await fetch('https://api.docforge.io/extract', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.DOCFORGE_API_KEY }, body: JSON.stringify({ fileName: 'resume.docx', fileBase64: file.toString('base64') }) }) const data = await response.json() if (response.ok) { console.log(data.text) } else { // branch on the stable reason code, e.g. 'unsupported_format' console.error(response.status, data.reason, data.error) }
Python Example
import requests, base64 with open("resume.pdf", "rb") as f: file_b64 = base64.b64encode(f.read()).decode() response = requests.post( "https://api.docforge.io/extract", headers={"x-api-key": "YOUR_API_KEY"}, json={"fileName": "resume.pdf", "fileBase64": file_b64} ) data = response.json() if response.ok: print(data["text"]) else: print(response.status_code, data["reason"])
cURL Example
curl -X POST https://api.docforge.io/extract \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d "{\"fileName\": \"resume.docx\", \"fileBase64\": \"$(base64 -i resume.docx)\"}" \ | jq -r .text