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:

terminal
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:

FieldTypeRequiredDescription
htmlStringYesA complete HTML document to render as a PDF
optionsObjectNoPDF 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.

OptionTypeDefaultDescription
formatString"Letter"Paper size. One of: Letter, Legal, A4, A3, Tabloid
marginObject0 all sidesPage margins with top, right, bottom, left keys. Values are CSS units (e.g., "1in", "20mm")
printBackgroundBooleantrueWhether to render CSS backgrounds and colors
landscapeBooleanfalsePaper orientation
displayHeaderFooterBooleanfalseShow 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

StatusMeaningResponse 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

LimitValueNotes
Request body4 MBA typical HTML document with inlined CSS is 50–100 KB
Response body6 MBMost PDFs are well under 500 KB
Render timeout30 secondsMost renders complete in under 4 seconds

JavaScript Example

generate-pdf.js
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

generate_pdf.py
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

terminal
# 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.

invoice-template.html
<!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

FieldTypeRequiredDescription
fileNameStringYesOriginal file name including its extension, e.g. resume.docx (max 255 characters). The extension selects the parser.
fileBase64StringYesThe file's contents, base64-encoded. Up to 4 MB before encoding.
allowOcrBooleanNoDefault 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

FormatExtensionsNotes
PDF.pdfText PDFs, plus scanned or image-only pages via OCR (up to 5 scanned pages)
Word.doc, .docxTables come back tab-separated, one row per line
Rich text.rtfWord, Google Docs, and TextEdit exports
Plain text.txtUTF-8, UTF-16, or Windows-1252
Transcripts and captions.vtt, .srtMicrosoft Teams, Zoom, and other exports, returned as-is
Anything elseanyBest effort: you get the text or a reason it couldn't be extracted

Extract: Response

Success (200)

response
{
  "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.

StatusreasonMeaning
400invalid_requestBad JSON, missing fileName or fileBase64, invalid base64, or no file extension
401unauthorizedInvalid or missing API key
413file_too_largeFile is over 4 MB
415unsupported_formatDocForge can't read this file type
422unreadable_fileThe file is corrupt or password-protected
422no_extractable_textThe file contains no text
422too_many_scanned_pagesMore than 5 pages need OCR
422ocr_not_allowedThe PDF has pages that need OCR, and the request set allowOcr to false
422extracted_text_too_largeThe extracted text is too large to return
503ocr_unavailableOCR is temporarily unavailable; retry later
500processing_failedSomething 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

LimitValueNotes
File size4 MBMeasured before base64 encoding
Scanned pages5Pages that need OCR; pages with a text layer don't count
Response size6,000,000 bytesSerialized response; larger results return extracted_text_too_large
Timeout20 secondsMost documents extract in well under a second

JavaScript Example

extract-text.js
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

extract.py
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

terminal
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