Automate Dynamic PDF Generation with the Foxit DocGen API: Word Templates, JSON Data, and Real API Calls

Foxit DocGen API workflow showing a Word template with data tags being converted into a PDF document using JSON data.

Skip the HTML-to-PDF headaches. Use Foxit’s DocGen API to turn Word templates and JSON data into clean, formatted PDFs with one API call.

If you’ve tried to generate a contract or invoice from HTML, you’ve probably burned hours on page-break-inside: avoid declarations that Chrome renders one way and a headless browser renders another. Headers and footers require separate print-media queries, and by the time you’ve got a repeating table header working correctly across pages, you’ve invested a full day of engineering into CSS that exists solely to trick a browser into behaving like a printer.

HTML documents reflow content into a viewport while PDF documents have fixed page geometry. Forcing one model into the other produces predictable failure modes: footnotes that collide with page footers, tables that split at the worst possible row, custom fonts that substitute silently, and signature blocks that drift off-page on longer documents.

There’s a larger practical cost too. For most teams, the authoritative source for enterprise document templates is already a Word file. Your legal team owns the NDA in .docx format. Finance owns the invoice in .docx format. Every structural change flows through Word because that’s where the tracked changes, formatting history, and review process live. Maintaining a parallel HTML version of each template doubles your maintenance surface from day one.

Foxit’s DocGen API eliminates that parallel entirely. You keep your templates as .docx files, embed data tags directly in Word, POST the base64-encoded template and a JSON payload to a single REST endpoint, and receive the rendered PDF (or DOCX) in the response body. You eliminate the browser rendering engine, the print-media CSS layer, and the overhead of a second template format.

How the Foxit DocGen API Works

The core model is a single synchronous POST to the GenerateDocumentBase64 endpoint at developer-api.foxit.com. Your request body carries three fields:

  • base64FileString: your .docx template, base64-encoded
  • documentValues: a JSON object containing your merge data
  • outputFormat: either "pdf" or "docx"

The API processes the template, resolves every tag against your data, and returns a JSON response containing base64FileString (the rendered document) and a message field confirming success or describing a failure. The exchange is fully synchronous, so you receive the finished document in the same HTTP response with no job ID to poll and no webhook to configure.

Authentication uses two HTTP headers: client_id and client_secret. Both come from the Foxit Developer Portal when you create an account. The free Developer plan provides 500 credits per year with no credit card required, and each GenerateDocumentBase64 call consumes exactly one credit. The Startup plan ($1,750/year) provides 3,500 credits. The Business plan ($4,500/year) covers 150,000 credits for production workloads. For context, Nutrient’s API starts at $75 for 1,000 credits, and Apryse requires a sales conversation before you can access pricing at all.

The complete call flow runs from template file to PDF on disk.

Sequence diagram showing the Foxit DocGen API workflow from reading a Word template and encoding it to base64, sending the POST request, and receiving the rendered PDF response.

You can explore every endpoint in the live API playground at developer-api.foxit.com, and the portal includes a Postman collection you can import to run authenticated requests without writing a line of code first.

Build a Word Template with DocGen Tags

Open any .docx file in Microsoft Word and type your tags as plain text directly in the document. The DocGen API uses double-brace syntax: {{field_name}}. Tags go anywhere Word accepts text: headings, body paragraphs, table cells, headers, footers, or text boxes.

Scalar field tags resolve directly to the matching key from your documentValues JSON. A document header with {{customer_name}}{{invoice_number}}, and {{invoice_date}} pulls those three values straight from the top-level keys of your payload.

For arrays, you wrap a single table row (the data row, not the header row) with {{TableStart:array_name}} and {{TableEnd:array_name}} markers. The wrapped row acts as a template row, and the API renders one output row per item in the JSON array. An invoice line-items table in Word looks like this:

DescriptionQtyUnit PriceTotal
{{TableStart:line_items}}{{description}}{{qty}}{{unit_price}}{{total}}{{TableEnd:line_items}}

Within the array row, ROW_NUMBER auto-increments with each rendered row. A SUM(ABOVE) field placed in the row directly below the {{TableEnd:line_items}} marker calculates a column total across all rendered data rows.

For nested JSON objects, use dot-notation in your tags. A shipping address block references {{shipping.street}}{{shipping.city}}, and {{shipping.postal_code}}, mapping to properties nested inside a shipping object in your payload. The nesting can go multiple levels deep, so {{customer.address.city}} resolves against documentValues.customer.address.city.

For a working starting point, grab the downloadable invoice template from the foxit-demo-templates repo. The file is well under the 4 MB upload limit and demonstrates every pattern this article uses: scalar tags, {{TableStart:line_items}} / {{TableEnd:line_items}} with {{ROW_NUMBER}}, currency and date format switches, and subtotal / tax / total fields below the line-items table.

One sizing constraint applies while you build your own template. DocGen rejects uploads larger than 4 MB, so if you embed product photos, scanned letterhead, or full font subsets, compress the images before saving, drop embedded fonts where you can rely on system fonts, or split a large template into smaller per-section templates that you generate and merge separately.

Make Your First API Call: Generate a PDF from JSON

Run a quick pre-flight check before the first call to catch the issues that derail most clean-account run-throughs:

  • Account created and client_id / client_secret copied from the Developer Portal API Keys section
  • Sample template saved locally as invoice_template.docx in the directory you’ll run the script from
  • Template file size confirmed under 4 MB (ls -lh invoice_template.docx on macOS or Linux, right-click → Properties on Windows)

With those in place, confirm your credentials work with a cURL call. The Foxit Developer Portal includes a Postman collection for this, but a quick cURL request against the API catches auth issues before any code runs:

curl -X POST "https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64" \
  -H "client_id: YOUR_CLIENT_ID" \
  -H "client_secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"base64FileString":"","documentValues":{},"outputFormat":"pdf"}'

A 401 here means invalid credentials. A 400 with a message about the template confirms your headers are accepted and you can proceed to the full call.

Save your .docx template as invoice_template.docx in the same directory as this script, then run the complete generation:

import requests
import base64

CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
API_URL = "https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64"

# Read and encode the template
with open("invoice_template.docx", "rb") as f:
    template_b64 = base64.b64encode(f.read()).decode("utf-8")

# Build the data payload
document_values = {
    "customer_name": "Acme Corporation",
    "invoice_number": "INV-2025-0042",
    "invoice_date": "07/15/2025",
    "due_date": "08/14/2025",
    "line_items": [
        {
            "description": "API Integration Consulting",
            "qty": 8,
            "unit_price": 195.00,
            "total": 1560.00
        },
        {
            "description": "Document Automation Setup",
            "qty": 1,
            "unit_price": 750.00,
            "total": 750.00
        }
    ],
    "subtotal": 2310.00,
    "tax_rate": 0.08,
    "tax_amount": 184.80,
    "total_due": 2494.80
}

# Construct the request body
payload = {
    "base64FileString": template_b64,
    "documentValues": document_values,
    "outputFormat": "pdf"
}

headers = {
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET,
    "Content-Type": "application/json"
}

response = requests.post(API_URL, json=payload, headers=headers)

if response.status_code == 200:
    result = response.json()
    pdf_bytes = base64.b64decode(result["base64FileString"])
    if pdf_bytes[:5] != b"%PDF-":
        raise ValueError("Response did not contain a valid PDF")
    with open("invoice_output.pdf", "wb") as out:
        out.write(pdf_bytes)
    print("PDF written to invoice_output.pdf")
else:
    print(f"Error {response.status_code}: {response.json().get('message')}")

The success response is a JSON object with three keys: base64FileString (the rendered PDF, base64-encoded), fileExtension ("pdf"), and message ("PDF Document Generated Successfully"). Decoding and writing the bytes to disk gives you a complete, formatted PDF with every tag replaced by its corresponding data value. If you omit a key from documentValues, the API renders the corresponding tag as an empty string, producing a blank field in the output.

Advanced Data Scenarios: Arrays, Nested Objects, and Built-In Functions

The two-row invoice above works, but most production documents have more complex data shapes. Three patterns cover the majority of real-world cases.

For multi-row tables, the line_items array in the Python snippet above already shows the basic structure. To generate five rows, pass five objects in the array. The Word template row tagged with {{TableStart:line_items}} and {{TableEnd:line_items}} repeats exactly once per array item:

{
  "line_items": [
    {
      "description": "UX Design Review",
      "qty": 4,
      "unit_price": 150.0,
      "total": 600.0
    },
    {
      "description": "Backend API Development",
      "qty": 12,
      "unit_price": 185.0,
      "total": 2220.0
    },
    {
      "description": "Database Schema Migration",
      "qty": 3,
      "unit_price": 200.0,
      "total": 600.0
    },
    {
      "description": "QA Testing",
      "qty": 6,
      "unit_price": 95.0,
      "total": 570.0
    },
    {
      "description": "Deployment and Documentation",
      "qty": 2,
      "unit_price": 175.0,
      "total": 350.0
    }
  ]
}

The API generates exactly five table rows. Swap in 50 items and you get 50 rows, with page breaks handled by Word’s native pagination logic.

For nested objects, the DocGen API resolves dot-notation paths against the full depth of your JSON structure. A shipping confirmation template referencing {{customer.address.city}} works against this payload without any flattening on your end:

{
  "customer": {
    "name": "Sarah Chen",
    "email": "[email protected]",
    "address": {
      "street": "742 Evergreen Terrace",
      "city": "Portland",
      "state": "OR",
      "postal_code": "97201"
    }
  }
}

In the Word template, {{customer.name}}{{customer.address.city}}, and {{customer.address.postal_code}} each resolve to the correct nested value. You can reference the same nested object from multiple locations in the template, and the API populates each instance independently.

For numeric and date formatting, the DocGen API respects Word’s native field switch syntax. Adding \# Currency to a tag formats a numeric value as a currency string, so {{unit_price \# Currency}} renders 195.00 as \$195.00. Date fields accept \@ "MM/dd/yyyy" to control output format, so {{invoice_date \@ "MM/dd/yyyy"}} formats an ISO date string to 07/15/2025. To auto-calculate a column total, place a SUM(ABOVE) field in the Word table row immediately below {{TableEnd:line_items}} and the API evaluates it against the rendered data rows.

Error Handling and Production Readiness

The DocGen API returns a focused set of HTTP status codes. A 200 confirms successful generation. A 401 means your client_id or client_secret headers are invalid, and the fix is to re-copy the credentials from the Developer Portal. A 400 covers three cases. The first is a malformed request body, for example a missing base64FileString or outputFormat. The second is structural issues with the template itself, such as a {{TableStart}} marker placed outside its table row. The third is an oversize template; DocGen rejects .docx uploads larger than 4 MB, and the fix is to compress embedded images, drop embedded fonts, or split the template before re-encoding. The message field in every non-200 response body gives you the specific reason, so log it rather than discarding the response object.

A production wrapper handles all three cases and adds exponential backoff for transient server errors:

import requests
import base64
import time

def generate_document(client_id, client_secret, template_path,
                      document_values, output_format="pdf"):
    API_URL = "https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64"

    with open(template_path, "rb") as f:
        template_b64 = base64.b64encode(f.read()).decode("utf-8")

    payload = {
        "base64FileString": template_b64,
        "documentValues": document_values,
        "outputFormat": output_format
    }
    headers = {
        "client_id": client_id,
        "client_secret": client_secret,
        "Content-Type": "application/json"
    }

    max_retries = 3
    for attempt in range(max_retries):
        try:
            response = requests.post(API_URL, json=payload,
                                     headers=headers, timeout=30)

            if response.status_code == 200:
                return base64.b64decode(response.json()["base64FileString"])

            if response.status_code == 401:
                raise ValueError("Authentication failed: re-check client_id and client_secret")

            if response.status_code == 400:
                msg = response.json().get("message", "Bad request")
                raise ValueError(f"Request error: {msg}")

            if response.status_code >= 500:
                if attempt < max_retries - 1:
                    wait = 2 ** attempt
                    print(f"Server error ({response.status_code}), retrying in {wait}s...")
                    time.sleep(wait)
                    continue
                raise RuntimeError(f"Server error after {max_retries} attempts")

        except requests.exceptions.Timeout:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
                continue
            raise

    raise RuntimeError("Max retries exceeded")

The wrapper raises immediately on 4xx responses because retrying a credential error or a malformed request produces the same result. Exponential backoff applies only to 5xx responses and timeouts, where the issue is transient.

Once generate_document() returns raw PDF bytes, routing them downstream takes three lines:

import boto3

s3 = boto3.client("s3")
pdf_bytes = generate_document(CLIENT_ID, CLIENT_SECRET, "invoice_template.docx", document_values)
s3.put_object(Bucket="my-documents-bucket", Key="invoices/INV-2025-0042.pdf", Body=pdf_bytes)

To attach the output to an email, pass pdf_bytes directly as the smtplib attachment payload. To collect a signature on the generated document, base64-encode the bytes and POST them to Foxit’s eSign API with the signer’s email address in the request body. The full eSign API reference is at docs.developer-api.foxit.com.

Common Mistakes

A short list of the issues that account for almost every failed first run.

  • Smart-quote autocorrect on braces. Word’s AutoCorrect can convert the second { of {{ into a curly-quote glyph, which breaks tag parsing silently. Disable “Straight quotes with smart quotes” under AutoCorrect Options, or paste tags as plain text.
  • Token case sensitivity. {{Customer_Name}} and {{customer_name}} are different keys. Match the casing in your JSON exactly.
  • TableStart and TableEnd must sit in the same Word table row. Splitting them across two rows, or placing either marker outside the table, leaves the loop unrendered with no error.
  • Template over 4 MB. The API rejects oversize uploads with a 400. Compress embedded images, drop embedded fonts where system fonts will do, or split the template into smaller pieces.
  • Missing payload key. The API renders an unmatched tag as an empty string rather than failing, so a 200 response does not guarantee every field is populated. Spot-check the rendered PDF as part of any pipeline test.
  • Auth header typos. Headers are client_id and client_secret in snake_case. Client-IdClientId, or X-Client-Id all return 401.

Run the Full Invoice Example End-to-End Right Now

Create a free account directly at account.foxit.com/site/sign-up. This skips the pricing-page redirect you hit from the marketing site and drops you straight into the account form.

  1. Open account.foxit.com/site/sign-up and complete the form (no credit card required).
  2. After verification, sign in to the Developer Portal and the Developer plan (500 credits per year) is active by default.
  3. Open the API Keys section and copy your client_id and client_secret.

With credentials in hand, run the example end-to-end:

  1. Download invoice_full.docx from the foxit-demo-templates repo and save it locally as invoice_template.docx in your working directory. The file is well under the 4 MB upload limit and exercises every tag pattern this article covers.
  2. Paste your credentials into the CLIENT_ID and CLIENT_SECRET variables in the Python script from the previous section.
  3. Edit the document_values dictionary with your own customer name, invoice number, and line items.
  4. Run the script and open invoice_output.pdf.

The free Developer plan’s 500 annual credits cover this tutorial dozens of times over before you spend anything. The full API reference at docs.developer-api.foxit.com covers every endpoint parameter, the complete tag specification, all supported output formats, and the full GenerateDocumentBase64 request and response schema.

Get started with a free account (no credit card required) and generate your first dynamic PDF in under 10 minutes.

Foxit DocGen API Quickstart: Word Template to Pixel-Perfect PDF in Under 10 Minutes

Foxit DocGen API tutorial converting a Word template into a pixel-perfect PDF.

Go from a Word template to a pixel-perfect PDF in under 10 minutes with the Foxit DocGen API. This guide covers template authoring, JSON payload structure, the GenerateDocumentBase64 call, and the most common errors that trip people up.

Most document generation quickstarts hand you a template with one text field, a trivial JSON payload, and no explanation of what breaks when you add a repeating table, a date format string, or a missing key. You end up in the docs trying to work backwards from a 400 error. This tutorial covers the complete Foxit DocGen API flow end-to-end: authoring a Word template with scalar fields, formatted dates, and repeating line-item rows; building the matching JSON payload; and POSTing everything to GenerateDocumentBase64 to retrieve a production-ready PDF. Working Python and cURL throughout.

Before starting, you’ll need a free Foxit developer account at developer-api.foxit.com (no credit card required; the free tier includes 500 credits/year), your client_id and client_secret from the developer dashboard, Python 3.x with requests installed (pip install requests), and Microsoft Word for template authoring.

How the Foxit DocGen API Works: One Endpoint, One Call

The GenerateDocumentBase64 endpoint accepts a single POST request and returns the rendered document in the same response body. You pass three things: your .docx template (base64-encoded), your structured JSON data, and the desired output format. The API merges template with data and returns the rendered file as a base64-encoded string.

Your .docx file defines layout, branding, and placeholder tokens. Your JSON payload carries the runtime values that populate those tokens. The API resolves every token in the template against the corresponding key in documentValues and renders the result as a PDF or DOCX.

Foxit DocGen API flow showing Word template and JSON data merged into a PDF response. The call is synchronous, returning the rendered file in the HTTP 200 response body with no job ID, polling loop, or webhook callback required. The request body always carries three keys: base64FileString (your .docx template, base64-encoded), documentValues (the JSON object whose keys map to template tokens), and outputFormat ("pdf" or "docx").

Authentication passes client_id and client_secret as custom HTTP headers on every request, with no OAuth 2.0 flow and no token exchange step.

Author Your Word Template with Dynamic Tags

Open Word and create a standard .docx. Place your dynamic content using double-curly-brace tokens typed directly in the document body.

For scalar string and number fields, the syntax is {{field_name}}. For a date with a specific display pattern, use {{ field_name \@ MM/dd/yyyy }}. The \@ format string controls how the API renders date values from your JSON payload.

An invoice template header section looks like this:

Invoice #: {{invoice_number}}
Date: {{ invoice_date \@ MM/dd/yyyy }}
Bill To: {{client_name}}
Address: {{billing_address}}

Repeating table rows use a pair of range markers. Place {{TableStart:line_items}} in the first cell of the row you want to repeat, and {{TableEnd:line_items}} in the last cell of that same row. The array name in both markers (line_items here) must exactly match the key name in your JSON payload. Cells within the repeating row take individual field tokens:

| {{TableStart:line_items}}{{ROW_NUMBER}} | {{description}} | {{qty}} | {{unit_price}}{{TableEnd:line_items}} |

{{ROW_NUMBER}} auto-increments across all rendered rows. Word’s built-in SUM(ABOVE) formula in a totals row below the table still works for column totals.

Two authoring mistakes account for the majority of template parsing failures. Placing tokens inside merged table cells causes a parser error because the API can’t determine which logical cell owns the token. Using Word’s smart (curly) quotes instead of straight ASCII double-braces causes an encoding mismatch that returns a 400. Before uploading your template, check Word’s autocorrect settings and run a Find & Replace search for any {{ or }} pairs that got converted to curly equivalents.

Authenticate and Prepare the Template

Your client_id and client_secret from the developer dashboard at developer-api.foxit.com pass as custom headers on every request. The base64 module ships with Python’s standard library, so the encoding step adds no new dependencies to your project:

import base64
import requests

# Load and encode the .docx template
with open("invoice_template.docx", "rb") as f:
    template_b64 = base64.b64encode(f.read()).decode("utf-8")

# Authentication and content-type headers
headers = {
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "Content-Type": "application/json"
}

# Scalar fields (the line_items array is added in the next section)
document_values = {
    "invoice_number": "INV-2025-0042",
    "invoice_date": "2025-07-15",
    "client_name": "Meridian Software Inc."
}

payload = {
    "base64FileString": template_b64,
    "documentValues": document_values,
    "outputFormat": "pdf"
}

The cURL equivalent encodes the file on the fly and passes the same headers:

# Encode the template
TEMPLATE_B64=$(base64 < invoice_template.docx)

curl -s -X POST "https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64" \
  -H "client_id: YOUR_CLIENT_ID" \
  -H "client_secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d "{\"base64FileString\": \"${TEMPLATE_B64}\", \"documentValues\": {\"invoice_number\": \"INV-2025-0042\", \"invoice_date\": \"2025-07-15\", \"client_name\": \"Meridian Software Inc.\"}, \"outputFormat\": \"pdf\"}"

Build the JSON Data Payload for DocGen

The full documentValues payload maps scalar fields at the top level and places the repeating line-item array under the key that matches your {{TableStart:}} / {{TableEnd:}} marker name exactly.

{
  "invoice_number": "INV-2025-0042",
  "invoice_date": "2025-07-15",
  "client_name": "Meridian Software Inc.",
  "billing_address": "400 Pine Street, Suite 12, Seattle, WA 98101",
  "line_items": [
    {
      "description": "API Integration Consulting",
      "qty": "8",
      "unit_price": "225.00"
    },
    {
      "description": "DocGen Template Authoring",
      "qty": "4",
      "unit_price": "175.00"
    },
    {
      "description": "QA and Deployment Support",
      "qty": "2",
      "unit_price": "150.00"
    }
  ]
}

A few type behaviors worth tracking. The API formats date values using the \@ format string in the template tag, so pass dates as ISO 8601 strings ("2025-07-15") and let the tag control the display format. Numeric quantities and prices work as either strings or integers. When a template token has no matching key in documentValues, the API leaves that placeholder blank in the output rather than returning an error, so missing keys produce silent blanks in your document.

Call the Generation Endpoint and Retrieve the PDF

This complete Python function adds the line_items array, posts to the endpoint, validates the response, and writes the output to disk:

import base64
import requests

with open("invoice_template.docx", "rb") as f:
    template_b64 = base64.b64encode(f.read()).decode("utf-8")

headers = {
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "Content-Type": "application/json"
}

document_values = {
    "invoice_number": "INV-2025-0042",
    "invoice_date": "2025-07-15",
    "client_name": "Meridian Software Inc.",
    "billing_address": "400 Pine Street, Suite 12, Seattle, WA 98101",
    "line_items": [
        {"description": "API Integration Consulting", "qty": "8", "unit_price": "225.00"},
        {"description": "DocGen Template Authoring", "qty": "4", "unit_price": "175.00"},
        {"description": "QA and Deployment Support", "qty": "2", "unit_price": "150.00"}
    ]
}

payload = {
    "base64FileString": template_b64,
    "documentValues": document_values,
    "outputFormat": "pdf"
}

response = requests.post(
    "https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64",
    headers=headers,
    json=payload
)

if response.status_code == 200:
    result = response.json()
    pdf_bytes = base64.b64decode(result["base64FileString"])

    # Confirm the response is a valid PDF before writing to disk
    if pdf_bytes[:5] != b"%PDF-":
        raise ValueError("Response did not contain a valid PDF")

    with open("invoice_output.pdf", "wb") as f:
        f.write(pdf_bytes)
    print("PDF written: invoice_output.pdf")
else:
    error = response.json()
    print(f"Error {response.status_code}: {error.get('message', 'Unknown error')}")

A successful response returns HTTP 200 with a JSON body containing three fields: base64FileString (the rendered PDF, base64-encoded), fileExtension ("pdf"), and message ("PDF Document Generated Successfully"). A failed call returns a 4xx status with a JSON body containing a message field describing the error. The API is synchronous: no job IDs, no polling, no webhooks.

The %PDF- magic bytes check catches cases where the API returned a non-PDF payload: a malformed template, an incorrect outputFormat value, or an error body that got decoded as if it were the file. Run this validation before writing to disk so failures surface immediately rather than producing a corrupt file.

The cURL equivalent uses jq to extract the base64 field and writes the decoded PDF directly to a file:

curl -s -X POST "https://na1.fusion.foxit.com/document-generation/api/GenerateDocumentBase64" \
  -H "client_id: YOUR_CLIENT_ID" \
  -H "client_secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d @request_body.json \
  | jq -r '.base64FileString' \
  | base64 --decode > invoice_output.pdf

Store the full JSON payload from the previous section in request_body.json. The jq -r flag strips JSON string escaping from the base64 field before it reaches base64 --decode. Omitting -r produces a corrupt output file.

Debugging Common Failures

Unmatched tags render as blank strings in the output PDF. The API produces an HTTP 200 and a valid PDF with the missing data absent. If your output has blank fields, compare your template token names character-by-character against your JSON keys. Token matching is case-sensitive: {{client_name}} and {{Client_Name}} are treated as different fields.

Authentication failures return a 401 with a JSON body:

{
  "message": "Unauthorized. Invalid client credentials."
}

The header names are lowercase (client_idclient_secret), and values must appear without surrounding quotes or whitespace. If you recently rotated keys, regenerate credentials from the developer dashboard and verify you’re reading the correct environment’s values.

Template parse errors return a 400:

{
  "message": "Template parsing error: invalid token format detected."
}

Two root causes produce this error. A .docx re-saved through LibreOffice alters the underlying XML structure in ways that break the parser, so re-author the template in Word. Curly (smart) quote characters in token braces cause an encoding mismatch, so use Word’s Find & Replace to swap any curly {{ and }} back to straight ASCII equivalents.

Payload size limits apply to the base64-encoded template. Check docs.developer-api.foxit.com for the current threshold. Templates exceeding the limit should have embedded images compressed through Word’s Picture Format settings before export. For templates that remain too large after compression, split the document into two .docx files and merge the generated PDFs using Foxit’s PDF Services API.

Wire the Foxit DocGen API Into Your Stack: Next Steps

The integration pattern for a CRM-triggered flow covers four steps: receive the webhook event, pull the record data from your CRM’s API, POST to GenerateDocumentBase64, then upload the decoded PDF to Amazon S3 or return it as a download URL.

def handle_crm_webhook(event):
    record = crm_client.get_record(event["record_id"])
    pdf_bytes = generate_document(record)           # wraps the API call above
    s3_url = upload_to_s3(pdf_bytes, record["id"])  # store in your delivery layer
    crm_client.attach_document(record["id"], s3_url)

Once DocGen is generating your contracts, invoices, and compliance reports, adding signatures is the natural next step. Foxit’s eSign API accepts the same PDF output and adds a fully auditable, legally binding signing workflow via REST. For low-code integration with SalesforceHubSpot, or SAP, Foxit’s 40+ pre-built connectors let you trigger document generation from a workflow automation tool without writing the HTTP call yourself. Full documentation and connector references are at docs.developer-api.foxit.com.

Create your free account at developer-api.foxit.com, grab your client_id and client_secret, import the Postman collection from the developer dashboard, and run your first generation call against your own .docx. The round trip from account creation to a rendered PDF takes under five minutes.

Frequently Asked Questions

What does the Foxit DocGen API GenerateDocumentBase64 endpoint return?

It returns a synchronous HTTP 200 response whose JSON body contains a base64FileString key holding the fully rendered PDF (or DOCX) encoded in base64. There’s no job ID, polling loop, or webhook. The rendered file arrives in the same response.

What happens when a template token has no matching key in the JSON payload?

The API silently leaves that placeholder blank in the rendered output and still returns HTTP 200. Missing keys don’t trigger a 400 error, so always validate rendered output programmatically. The %PDF- magic bytes validation shown above is a reliable first check.

Why does my Foxit DocGen template return a 400 parsing error?

The two most common causes are token braces containing Word’s smart (curly) quotes instead of straight ASCII double-braces, and a .docx re-saved through LibreOffice, which alters the underlying OOXML structure in ways the parser rejects. Re-author the template in Microsoft Word and replace any curly quote characters using Find & Replace.

Can I generate DOCX output instead of PDF with the DocGen API?

Yes. Set "outputFormat": "docx" in the request body. The same template syntax and documentValues structure apply regardless of output format.

How does authentication work with the Foxit DocGen API?

Pass your client_id and client_secret as custom HTTP request headers on every call. There’s no OAuth token exchange or session management. Credentials are validated per-request and can be generated or rotated from the Foxit developer dashboard.

DocGen QuickStart FAQs

The Foxit DocGen API is used to generate documents from structured data. Developers can populate templates with data from systems like CRMs, databases, forms, or internal applications, then output branded PDFs or DOCX files for contracts, invoices, reports, disclosures, and other document workflows.

The GenerateDocumentBase64 endpoint accepts a base64-encoded document template, a JSON object containing document values, and an output format such as PDF or DOCX. The API merges the template with the supplied data and returns the generated file as a base64-encoded response.

To generate a PDF, you need a document template, structured JSON data that matches the template fields, Foxit API credentials, and an output format value. In the blog example, the template is a Word .docx file and the output format is set to PDF.

Yes. The blog states that developers can set the output format to docx instead of pdf when calling the DocGen API. The same general template and data-mapping approach applies, but this specific behavior should still be validated against the current API documentation before publication.

In the blog example, repeating table rows use TableStart and TableEnd markers around a row in the Word template. The marker name must match the array name in the JSON payload, and each object in the array supplies values for the repeated row fields.

The blog states that if a template token does not have a matching key in documentValues, the generated document leaves that placeholder blank instead of returning an error. Because this is a specific API behavior, it should be confirmed against the current DocGen API documentation.

The blog states that developers authenticate by passing client_id and client_secret as HTTP request headers. Before publication, Foxit should confirm that this is the current recommended authentication method and add guidance to store credentials securely in environment variables or a secrets manager.

According to the blog, common causes include invalid template token formatting, smart quote characters in template tags, or .docx files saved in a way that changes the underlying document structure. This is useful troubleshooting guidance, but it should be validated by the product or documentation team.

Yes. Foxit’s Document Generation API materials position the API for generating documents from structured data in systems such as CRMs, databases, web forms, ERP, or HR systems. Common use cases include quotes, contracts, disclosures, invoices, onboarding documents, and customer communications.

Foxit DocGen can generate a document from structured data, then the generated PDF can move into downstream workflows such as signing, storage, delivery, or archiving. Foxit’s broader API materials position Document Generation, eSign, PDF Services, and PDF Embed APIs as part of a full-stack document automation ecosystem.

Generate Dynamic PDFs from JSON using Foxit APIs

Generate Dynamic PDFs from JSON using Foxit APIs

See how easy it is to generate PDFs from JSON using Foxit’s Document Generation API. With Word as your template engine, you can dynamically build invoices, offer letters, and agreements—no complex setup required. This tutorial walks through the full process in Python and highlights the flexibility of token-based document creation.

Generate Dynamic PDFs from JSON using Foxit APIs

One of the more fascinating APIs in our library is the Document Generation API. This document generation API lets you create dynamic PDFs or Word documents using your own data as templates. That may sound simple – and the code you’re about to see is indeed simple – but the real power lies in how flexible Word can be as a template engine. This API could be used for:

All of this is made available via a simple API and a “token language” you’ll use within Word to create your templates. Whether you’re feeding in data from a database, a form submission, or a JSON API response, the process looks the same from your Python script. Let’s take a look at how this is done.

Credentials

Before we go any further, head over to our developer portal and grab a set of free credentials. This will include a client ID and secret values – you’ll need both to make use of the API.

Don’t want to read all of this? You can also follow along by video:

Using the API

The Document Generation API flow is a bit different from our PDF Services APIs in that the execution is synchronous. You don’t need to upload your document beforehand or download a result. You simply call the API (passing your data and template) and the result has your new PDF (or Word document). With it being this simple, let’s get into the code.

Loading Credentials

My script begins by loading in the credentials and API root host via the environment:

CLIENT_ID = os.environ.get('CLIENT_ID')
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
HOST = os.environ.get('HOST')

As always, try to avoid hard coding credentials directly into your code.

Calling the API

The endpoint only requires you to pass the output format, your data, and a base64 version of your file. “Your data” can be almost anything you like—though it should start as an object (i.e., a dictionary in Python with key/value pairs). Beneath that, anything goes: strings, numbers, arrays of objects, and so on.

Here’s a Python wrapper showing this in action:

def docGen(doc, data, id, secret):
    
    headers = {
        "client_id":id,
        "client_secret":secret
    }

    body = {
        "outputFormat":"pdf",
        "documentValues": data,  
        "base64FileString":doc
    }

    request = requests.post(f"{HOST}/document-generation/api/GenerateDocumentBase64", json=body, headers=headers)
    return request.json()

And here’s an example calling it:

with open('../../inputfiles/docgen_sample.docx', 'rb') as file:
    bd = file.read()
    b64 = base64.b64encode(bd).decode('utf-8')

data = {
    "name":"Raymond Camden", 
    "food": "sushi",
    "favoriteMovie": "Star Wars",
    "cats": [
        {"name":"Elise", "gender":"female", "age":14 },
        {"name":"Luna", "gender":"female", "age":13 },
        {"name":"Crackers", "gender":"male", "age":13 },
        {"name":"Gracie", "gender":"female", "age":12 },
        {"name":"Pig", "gender":"female", "age":10 },
        {"name":"Zelda", "gender":"female", "age":2 },
        {"name":"Wednesday", "gender":"female", "age":1 },
    ],
}

result = docGen(b64, data, CLIENT_ID, CLIENT_SECRET)

You’ll note here that my data is hard-coded. In a real application, this would typically be dynamic—read from the file system, queried from a database, or sourced from any other location.

The result object contains a message representing the success or failure of the operation, the file extension for the result, and the base64 representation of the result. To turn that base64 string back into a file, decode it first:

b64_bytes = result["base64FileString"].encode('ascii')
binary_data = base64.b64decode(b64_bytes)

Most likely you’ll always be outputting PDFs, so here’s a simple bit of code that stores the result:

with open('../../output/docgen_sample.pdf', 'wb') as file:
    file.write(binary_data)
    print('Done and stored to ../../output/docgen_sample.pdf')

There’s a bit more to the API than I’ve shown here so be sure to check the docs, but now it’s time for the real star of this API, Word.

Using Word as a Template

I’ve probably used Microsoft Word for longer than you’ve been alive and I’ve never really thought much about it. But when you begin to think of a simple Word document as a template, all of a sudden the possibilities begin to excite you. In our Document Generation API, the template system works via simple “tokens” in your document marked by opening and closing double brackets.

Consider this block of text:

See how name is surrounded by double brackets? And food and favoriteMovie? When this template is sent to the API along with the corresponding values, those tokens are replaced dynamically. In the screenshot, notice how favoriteMovie is bolded. That’s fine. You can use any formatting, styling, or layout options you wish.

That’s one example, but you also get some built-in values as well. For example, including today as a token will insert the current date, and can be paired with date formatting to specify how the date looks:

Remember the array of cats from earlier? You can use that to create a table in Word like this:

Notice that I’ve used two new tags here, TableStart and TableEnd, both of which reference the array, cats. Then in my table cells, I refer to the values from that array. Again, the color you see here is completely arbitrary and was me making use of the entirety of my Word design skills.

Here’s the template as a whole to show you everything in context:

The Result

Given the code shown above with those values, and given the Word template just shared, once passed to the API, the following PDF is created:

What About Converting PDF to JSON?

So far we’ve been going one direction: JSON data in, PDF out. But what if you need to go the other way—extract structured content from a PDF and work with it in your application?

Foxit’s PDF Services API includes an Extract endpoint that handles exactly this. You upload a PDF, specify whether you want TEXT, IMAGE, or PAGE-level data, and the API returns the extracted content. The text output is particularly useful if you want to feed the result into a data pipeline, search index, or AI workflow.

Here’s a quick look at how extraction works in Python. First, upload your PDF:

def uploadDoc(path, id, secret):
    headers = {
        "client_id":id,
        "client_secret":secret
    }
    with open(path, 'rb') as f:
        files = {'file': (path, f)}
        request = requests.post(f"{HOST}/pdf-services/api/documents/upload", files=files, headers=headers)
    return request.json()

doc = uploadDoc("../../inputfiles/input.pdf", CLIENT_ID, CLIENT_SECRET)

Then call the Extract endpoint with the document ID and the type of content you want. The result comes back in a structured format you can parse, store, or pass along to other tools—including an LLM if you’re building an AI document pipeline.

You can read a full walkthrough in our PDF text extraction guide.

Ready to Try?

If this looks cool, be sure to check the docs for more information about the template language and API. Sign up for some free developer credentials and reach out on our developer forums with any questions.

If you’re building AI agents or LLM-powered workflows, Foxit also offers an MCP server that lets you connect your agents directly to Foxit PDF Services—so your AI tools can generate, extract, and process documents without any custom glue code.

Want the code? Get it on GitHub (Python).

If you are more of a Node person, check out that version. Get it on GitHub (Node.js).

Document Generation API: Automate Personalized Document Creation at Scale

Document Generation API: Automate Personalized Document Creation at Scale

Automate document creation at scale with a document generation API. Learn how templates and JSON data replace manual workflows to produce PDFs instantly.

Somewhere in your company right now, someone is copying client data from a CRM into a Word document, updating the logo, adjusting the date, and exporting it to PDF. If you’re lucky, it’s an intern doing this for 50 documents a month. If you’re not, it’s a developer who hard-coded the layout in iText or PDFKit, and Marketing is about to ask them to change the font size.

Neither of those is a document generation strategy. They’re workarounds, and like legacy Mail Merge tools that choke on anything beyond a few hundred records, they don’t scale to 50,000 invoices overnight.

The good news is that the problem is well-solved. A document generation API lets you treat documents exactly like you treat any other data pipeline. A template defines the structure, a JSON payload carries the content, and the API outputs a polished, production-ready PDF (or DOCX) in milliseconds. The copy-paste step disappears entirely.

This guide walks through how that works, where it fits in real-world systems, and how tools like Foxit’s DocGen API make the implementation straightforward, even for a team that’s never touched a document automation system before.

What Is a Document Generation API?

document generation API is a cloud service that merges a template with structured data to produce a final document, typically a PDF or DOCX. The template defines layout, fonts, branding, and placeholder tokens. A JSON payload supplies the dynamic values. The API engine renders the finished document in milliseconds, with no manual intervention and no layout code required.

The equation looks like this:

Template (Structure) + JSON Data (Content) + API Engine = Final Document

Using a document generation API rather than building a local generator offers two key advantages, scalability and separation of concerns.

On the scalability side, the same POST request that generates one invoice generates 100,000 invoices. You don’t need to provision more rendering capacity, manage memory pressure from large PDFs, or debug pagination edge cases. The API handles all of that. On the separation-of-concerns side, your legal team can update a liability clause in the Word template without touching your codebase. Your marketing team can swap the logo without a redeploy. The document’s design is fully decoupled from the application logic that drives it.

That said, not all document generation tools take the same approach. Older solutions like PDFKit or Apache PDFBox require you to code the visual layout programmatically by drawing lines, positioning text boxes, and calculating column widths manually. That works for simple, static documents. It breaks down fast when tables grow dynamically, when conditional sections appear based on customer data, or when stakeholders want to iterate on the design. The API approach flips that model. The design stays in the template while the logic stays in the API.

The Architecture of Document Generation Automation

Understanding the three-layer architecture makes every implementation decision easier. Here’s how the pieces fit together.

The Architecture of Document Generation Automation.

1. Template Creation

With a modern document generation API like Foxit’s, you don’t write rendering code. You open Microsoft Word, design the document exactly as it should look, and insert double-bracket tokens wherever dynamic data belongs.

To skip ahead and inspect a working artifact, two ready-made templates accompany this tutorial. The scalar-only version (invoice_simple.docx) is geared toward your first end-to-end run, and the full version (invoice_table.docx) adds the line-items table loop and a SUM(ABOVE) subtotal. Both live in the companion Foxit demo templates repo. Open them in Word to see the placeholder syntax in context, then copy the patterns into your own template.

A simple invoice template might include:

  • {{ companyName }} is replaced with the client’s company name
  • {{ invoiceNumber }} is replaced with a string like INV-00471
  • {{ invoiceDate \@ MM/dd/yyyy }} is replaced with a date formatted as 01/15/2024
  • {{ totalDue \# "$#,##0.00" }} is replaced with a currency-formatted number like $2,500.00

That’s it. Business users can open this .docx file, update the header font, move the logo, or reword a clause, and none of those changes require a developer.

2. Data Binding

Your application pulls data from wherever it lives (a Salesforce CRM, an SAP ERP, a PostgreSQL database) and structures it as a JSON payload. The JSON keys map directly to the template token names. No transformation layer, no intermediate format.

A payload for the invoice above looks like:

{
  "companyName": "Meridian Financial Group",
  "invoiceDate": "2024-01-15",
  "invoiceNumber": "INV-00471",
  "lineItems": [
    {
      "description": "API Integration Consulting",
      "qty": 10,
      "unitPrice": 150.0,
      "lineTotal": 1500.0
    },
    {
      "description": "Compliance Review",
      "qty": 5,
      "unitPrice": 200.0,
      "lineTotal": 1000.0
    }
  ],
  "totalDue": 2500.0
}

The keys companyNameinvoiceDate, and totalDue match the tokens in the template, a direct one-to-one bind. Note that lineTotal is precomputed in the payload. Foxit DocGen renders fields it receives, so any per-row arithmetic (price × quantity, tax calculations, conversions) happens in your application before the request goes out.

3. Dynamic Template Logic: Loops and Formatting

This is where document generation APIs separate themselves from simple find-and-replace tools. Real-world documents (invoices, statements, policy schedules) need rows that grow with the data, not fixed scalar fields.

Repeating tables are handled with loop delimiters. In Foxit’s syntax, you wrap the repeating row of your Word table with {{TableStart:lineItems}} in the first cell and {{TableEnd:lineItems}} in the last cell of the same row. The API iterates over the lineItems array in your JSON and renders one row per item, whether the array has 2 entries or 200.

Here’s how the Word table for the invoice payload above would look. The first row is the static header. The second row carries the loop tokens and is the only row you author. The third row uses SUM(ABOVE) to compute a subtotal across whatever number of rows the loop produces:

#DescriptionQtyUnit PriceLine Total
{{TableStart:lineItems}}{{ROW_NUMBER}}{{description}}{{qty}}{{unitPrice \# "$#,##0.00"}}{{lineTotal \# "$#,##0.00"}}{{TableEnd:lineItems}}
   Subtotal:{{=SUM(ABOVE) \# "$#,##0.00"}}

When merged with the JSON payload (two lineItems entries), the API renders a two-row table plus the computed subtotal:

#DescriptionQtyUnit PriceLine Total
1API Integration Consulting10$150.00$1,500.00
2Compliance Review5$200.00$1,000.00
   Subtotal:$2,500.00

{{ROW_NUMBER}} handles automatic line numbering inside the loop, and {{=SUM(ABOVE) \# "$#,##0.00"}} in a footer row sums the numeric column directly above it. Per-row arithmetic between fields (multiplying qty by unitPrice, for example) is not done by the API, so the payload sends lineTotal as a precomputed value.

Formatting specifiers are built into the token syntax and use Word’s MERGEFIELD picture strings. Currency formatting (\# "$#,##0.00") converts 2500.00 to $2,500.00, and you can adjust the symbol or decimal places by editing the picture string (\# "€#,##0.00"\# "0"). Date formatting (\@ MM/dd/yyyy) handles locale-specific date presentation without extra preprocessing in your application code.

The result is dynamic document templates that handle variable-length tables, formatted numbers, and conditional logic entirely in Word, not in your codebase . The Python example in Step 2 below sends this exact payload against the live DocGen endpoint and produces the rendered table shown above.

Top Document Generation Use Cases by Industry

Document generation sits in the critical path for several industries. These are the scenarios where teams see the most immediate return.

Financial Services: Client Reports and Investment Summaries

A wealth management firm generates quarterly performance summaries for thousands of clients. The template (header, chart placeholders, disclaimer text, signature block) stays constant. The data changes per client and includes portfolio value, allocation breakdown, benchmark comparison, and YTD return. The team pushes a nightly batch job that pulls data from their portfolio management system, constructs JSON payloads per client, and fires POST requests to the generation API, generating invoices and reports programmatically at scale. By morning, 8,000 personalized PDFs are sitting in an Amazon S3 bucket, ready to be emailed.

Insurance: The Policy Packet

An insurance carrier issuing a homeowner’s policy needs to assemble a multi-section document covering the cover letter (personalized with the policyholder’s name and address), the policy declarations page (premium, coverage limits, deductibles), the endorsements, and the liability disclaimer. Each section might be its own template. The API merges them into a single PDF. Underwriters stop manually assembling packets; the system generates them at bind time.

HR and Operations: Employee Onboarding

When a new hire accepts an offer, the HRIS triggers a webhook. The document generation service receives the employee’s data (name, role, start date, salary, benefits elections) and generates the full onboarding packet, including the offer letter, benefits summary, I-9 instructions, and handbook acknowledgment. The employee receives a complete, personalized PDF bundle within seconds of accepting. No one in HR touched it.

Sales: Branded Quotes and Contracts

Sales teams often live in the “Export to PDF” nightmare. They fill in a spreadsheet, copy it into Word, format it manually, and hope the branding looks right. A document generation API replaces that with a contract automation workflow tied directly to the CRM. When a rep marks a deal as “Proposal Sent,” Salesforce triggers a POST request with the deal data, and the API returns a PDF with the correct pricing, the client’s logo pulled from the CRM, and the correct contract terms based on the deal tier.

Foxit DocGen: The Developer Experience

Foxit brings 20+ years of PDF engine experience to a Word-template-to-PDF API that’s straightforward to integrate. The fastest happy path is to download a sample, configure your credentials, and run it. The implementation follows three steps:

Step 1: Get a Word template

The quickest start is to download invoice_simple.docx from the companion templates repo and use it as-is for your first end-to-end test. It carries the exact tokens (companyNameinvoiceNumberinvoiceDatetotalDue) that the Python sample below populates. Once you’ve confirmed the round-trip works, swap in invoice_table.docx to add the line-items loop and the SUM(ABOVE) subtotal. When you’re ready to build your own, open Microsoft Word and add {{ token }} placeholders using Foxit’s double-bracket syntax. Format specifiers (\# "$#,##0.00"\@ MM/dd/yyyy) and loop delimiters ({{TableStart:items}} / {{TableEnd:items}}) go directly into the Word document. No special editor required.

Step 2: Configure credentials and send a POST request

After signing up for a Foxit developer account, grab your BASE_URLCLIENT_ID, and CLIENT_SECRET from the developer console and set them as environment variables. The endpoint is /document-generation/api/GenerateDocumentBase64. You authenticate with your client_id and client_secret in the request headers, then send the base64-encoded template and your JSON data in the body. Here’s what that looks like in Python:

import os
import base64
import requests

# Credentials and base URL from your Foxit developer console
HOST = os.environ["BASE_URL"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]

# Load and encode the Word template (download invoice_table.docx from the demos repo
# linked above for a quick first run)
with open("invoice_table.docx", "rb") as f:
    template_b64 = base64.b64encode(f.read()).decode("utf-8")

# JSON data payload from your CRM or database. lineTotal is precomputed in
# the application because the API renders fields rather than evaluating
# arithmetic between them.
document_values = {
    "companyName": "Meridian Financial Group",
    "invoiceDate": "2024-01-15",
    "invoiceNumber": "INV-00471",
    "lineItems": [
        {"description": "API Integration Consulting", "qty": 10, "unitPrice": 150.00, "lineTotal": 1500.00},
        {"description": "Compliance Review", "qty": 5, "unitPrice": 200.00, "lineTotal": 1000.00}
    ],
    "totalDue": 2500.00
}

# POST to Foxit DocGen API
response = requests.post(
    f"{HOST}/document-generation/api/GenerateDocumentBase64",
    headers={
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "Content-Type": "application/json"
    },
    json={
        "base64FileString": template_b64,
        "documentValues": document_values,
        "outputFormat": "pdf"
    }
)

# Decode and save
result = response.json()
pdf_bytes = base64.b64decode(result["base64FileString"])
with open("invoice_00471.pdf", "wb") as f:
    f.write(pdf_bytes)

print("Invoice generated successfully.")

In this code, you read the .docx template from disk and base64-encode it because Foxit’s API expects the file as a UTF-8 string in base64FileString rather than raw bytes. You then build a document_values payload whose keys match the template’s tokens, including a precomputed lineTotal for every row since Foxit DocGen does not evaluate inline arithmetic like {{=qty*unitPrice}}. The POST request sends that payload to /document-generation/api/GenerateDocumentBase64, authenticates with client_id and client_secret headers pulled from environment variables, and asks for a PDF via outputFormat: "pdf". Finally, you decode the base64 response and write the bytes to invoice_00471.pdf on disk.

Step 3: Decode and store the response

The API returns a base64-encoded document. Decode it, write it to disk or pass it to an Amazon S3 bucket, and you’re done.

Foxit also supports DOCX output in addition to PDF. Most document generation APIs only produce PDFs, which means once the document is generated, it’s immutable. DOCX output enables a “Draft Mode” workflow where a generated document can be sent to a reviewer for light edits before it’s finalized. That’s genuinely useful in legal and HR workflows where a human needs to add a handwritten note or approve a clause. Sample code and SDKs are available for Python, JavaScript, Java, C#, and PHP, with additional language support on the developer portal. A Postman workspace is available for quick testing without writing any code first.

Common Template Mistakes

A few issues account for most failed first runs. Check these before assuming the API is the problem:

  • Placeholder syntax. Tokens use double curly braces with a single space inside, like {{ companyName }}, not { companyName } or {{{ companyName }}}. Word’s autocorrect occasionally swaps straight braces for smart braces; if a token renders as literal text in the output, retype the braces.
  • Case sensitivity. {{ CustomerName }} and {{ customerName }} are different tokens. The placeholder is matched verbatim against the JSON key, so an unmatched token silently renders as blank rather than throwing an error.
  • Wrong format-spec syntax. Foxit DocGen uses Word’s MERGEFIELD picture strings, not friendly keywords. {{ totalDue \# "$#,##0.00" }} works; {{ totalDue \# Currency }} renders as blank because Currency is not a recognized picture string. Same rule applies to date formatting (\@ MM/dd/yyyy).
  • Missing fields in the payload. A token without a corresponding JSON key produces an empty string in the output. Validate your payload against the template’s token list before sending the request, ideally with a JSON schema in your CI.
  • Expecting inline arithmetic. {{=qty*unitPrice}} does not evaluate inside a row. Compute derived fields (line totals, tax, conversion) in your application and send them in the payload. Aggregate functions like {{=SUM(ABOVE)}} do work in a footer row beneath the loop.
  • Loop tokens placed outside a table row. {{TableStart:items}} and {{TableEnd:items}} must sit inside cells of the same Word table row. Putting them in a regular paragraph or splitting them across rows produces unpredictable output.
  • Forgetting base64 encoding. The template must be base64-encoded as a UTF-8 string before being placed in base64FileString. Sending the raw bytes returns a 400-level error from the API.

Why the API Approach Wins Over Build-It-Yourself

If you automate PDF creation with the API approach, you gain three concrete advantages over build-it-yourself alternatives:

  • Compliance and accuracy: When a developer hard-codes a PDF layout, human error enters every time someone updates the template. A token mismatch ({{ CustomerName }} vs. {{ customerName }}) silently renders as blank. The API approach catches mismatches and enforces consistent data binding. More importantly, the data flows directly from your database to the document. There’s no manual copy-paste step and no transposition errors on a loan amount or policy limit.

  • Speed at scale: Generating PDFs with a local library like PDFKit means each document renders sequentially on your server, and rendering time grows with document complexity, especially when tables span multiple pages. At 10,000 documents, even modest per-document rendering times add up to minutes of blocking processing. A cloud document generation API parallelizes rendering across infrastructure you don’t manage. Fifty thousand invoices overnight is a scheduling problem, not a compute problem.

  • Maintenance belongs to the right person: When Marketing wants to update the invoice footer, they open the Word template and make the change. When Legal updates the liability clause, same thing. The developer does nothing, with no code change, no redeploy, and no regression testing on a layout that changed by three pixels. That’s the real ROI of the template-first approach, and it compounds over time as the team iterates on document designs without creating developer tickets.

Final Thoughts

Document generation shouldn’t be a manual task or a maintenance burden. If your team is still copy-pasting data into Word or maintaining a custom PDF renderer that breaks whenever a table gets too long, the template-plus-API model is a straightforward fix, not a major architectural change.

The next step worth watching is document generation paired with generative AI. The pattern is emerging in a few places, where teams use an LLM to draft a personalized summary paragraph based on a client’s portfolio data, then pass the result as a JSON field to the document generation API. The LLM handles the prose; the API handles the formatting and branding. You get dynamic content without losing layout control.

Ready to automate your document workflow? Create a free developer account at Foxit and access the DocGen API today. Download invoice_simple.docx for a one-shot scalar test, or invoice_table.docx for the full line-items flow, set your credentials, and run the example above to generate a PDF from JSON in minutes. See for yourself why the template-plus-API approach is replacing hand-coded generators.

Building Auditable, AI-Driven Document Workflows with Foxit APIs

Building Auditable, AI-Driven Document Workflows with Foxit APIs

We had an incredible time at API World 2025 connecting with developers, sharing ideas, and seeing how Foxit APIs power everything from AI-driven resume builders to interactive doodle apps. In this post, we’ll walk through the same hands-on workflow Jorge Euceda demoed live on stage—showing how to build an auditable, AI-powered document automation system using Foxit PDF Services and Document Generation APIs.

This year’s API World was packed with energy—and it was amazing meeting so many developers face-to-face at the Foxit booth. We spent three days trading ideas about document automation, AI workflows, and integration challenges.

Our team hosted a hands-on workshop and sponsored the API World Hackathon, where developers submitted 16 high-quality projects built with Foxit APIs. Submissions ranged from:

  • Automated legal-advice generators

  • Compatibility-rating apps that analyze your personality match

  • AI-powered resume optimizers that tailor your CV to dream-job descriptions

  • Collaborative doodle games that turn drawings into shareable PDFs

Each project offered a new perspective on what’s possible with Foxit APIs—and we loved seeing the creativity.

Among all the sessions, Jorge Euceda’s workshop stood out as a crowd favorite. It showed how to make AI document decisions auditable, explainable, and replayable using event sourcing and two key Foxit APIs. That’s exactly what we’ll walk through below.

Click here to grab the project overview file.

Prefer to follow along with the live session instead of reading step-by-step?
Watch Jorge’s complete “AI-Powered Resume to Report” presentation from API World 2025.
It includes every step shown below—plus real-time API responses.

What You’ll Build

A complete, auditable workflow:

Resume Upload → Extract Resume Data → AI Candidate Scoring → Generate HR Report → Event Store

This workshop is designed for technical professionals and managers who want to learn how to use application programming interfaces (APIs) and explore how AI can enhance document workflows. Attendees will get hands-on experience with Foxit’s PDF Services (extraction/OCR) and Document Generation APIs, and see how event sourcing turns AI decisions into an auditable, replayable ledger.

By the end, you’ll have a Python-based demo that extracts data from a PDF resume, analyzes it against a policy, and generates a polished HR Report PDF with a traceable event log.

Getting Set Up

To follow along, you’ll need:

  • Access to a terminal with a Python 3.9+ Environment and internet connectivity

  • Visual Studio Code or your preferred IDE

  • Basic familiarity with REST/JSON (helpful but not required)

 

  1. Install Dependencies
python -V
# virtual environment setup, requests installation
python3 -m venv myenv
source myenv/bin/activate
pip3 install requests
  1. Download the project’s zip file below

Project Source Code

Now extract the files somewhere in your computer, open in Visual Studio Code or your preferred IDE.

You may use any sample resume PDF for inputs/input_resume.pdf. A sample one is provided, but you may leverage any resume PDF you wish to generate a report on.

  1. Create a Foxit Account for credentials

Create a Free Developer Account now or navigate to our getting started guide, which will go over how to create a free trial.

Hands-On Walkthrough

Step 1 – Open the Project

Now that you’ve downloaded the workshop source code, navigate to the resume_to_report.py file, which will serve as our main entry point.

Once dependencies are installed and the ZIP file extracted, open your workspace and run:

python3 resume_to_report.py

You should see console logs showing:

  • An AI Report printed as JSON

  • A generated PDF (outputs/HR_Report.pdf)

  • An event ledger (outputs/events.json) with traceable actions

Step 2 — Inspect the outputs

Open the generated HR report to review:

  • Candidate name and phone

  • Overall fit score

  • Matching skills & gaps

  • Summary and policy reference in the footer

Then open events.json to see your audit trail—each entry captures the AI’s decision context.

{
  "eventType": "DecisionProposed",
  "traceId": "8d1e4df6-8ac9-4f31-9b3a-841d715c2b1c",
  "payload": {
    "fitScore": 82,
    "policyRef": "EvaluationPolicy#v1.0"
  }
}

This is your audit trail.

Step 3 — Replay & Explain a Policy Change

Replay demonstrates why event-sourcing matters:

  1. Edit inputs/evaluation_policy.json: add a hard requirement (e.g., "kubernetes") or adjust the job_description emphasis.

  2. Re-run the script with the same resume.

  3. Compare:

    • New decision and updated PDF content

    • Event log now reflects the updated rationale (PolicyLoaded snapshot → new DecisionProposed with the same traceId lineage)

  4. Emphasize: The input resume hasn’t changed; only policy did — the event ledger explains the difference.

Policy: Drive Auditable & Replayable Decisions

The AI assistant uses a JSON policy file to control how it scores, caps, and summarizes results. Every policy snapshot is logged as its own event, creating a replayable audit trail for governance and compliance.

 

{
  "policyId": "EvaluationPolicy#v1.0",
  "job_description": "Looking for a software engineer with expertise in C++, Python, and AWS cloud services. Experience building scalable applications in agile teams; familiarity with DevOps and CI/CD.",
  "overall_summary": "Make the summary as short as possible",
  "hard_requirements": ["C++", "python", "aws"]
}

Notes:

  • policyId appears in both the report and event log.

  • job_description defines what the AI is looking for.

  • Changing these values creates a new traceable event.

Generate a Polished Report

Next, use the Foxit Document Generation API to fill your Word template and create a formatted PDF report.

Open inputs/hr_report_template.docx, you will find the following HR reporting template with placeholders for the fields we will be entering:

Tips:

  • Include lightweight branding (logo/header) to make the generated PDF presentation-ready.

  • Include a footer with traceable Policy ID and Trace ID Events

Results and Audit Trail

Here’s what the final HR Report PDF looks like:

Every decision has a Trace ID and Policy Ref, so you can recreate the report at any time and verify how the AI arrived there.

Why Event-Sourced AI Matters

This pattern does more than score resumes—it proves that AI decisions can be transparent, deterministic, and trustworthy.
By using Foxit APIs to extract, analyze, and generate documents, developers can bring auditability to any workflow that relies on machine logic.

Key Takeaways

  • Auditability – Every AI step emits a verifiable event.

  • Replayability – Change a policy and regenerate for deterministic results.

  • Explainability – Decisions carry policy and trace references for clear “why.”

  • Automation – PDF Services and Document Generation handle the document lifecycle end-to-end.

Try It Yourself

Ready to build your own auditable AI workflow?

Closing Thought

At API World, we set out to show how Foxit APIs can power real, transparent AI workflows—and the community response was incredible. Whether you’re building for HR, legal, finance, or creative industries, the same pattern applies:

Make your AI explain itself.

Start with the Foxit APIs, experiment with policies, and turn every AI decision into a traceable event that builds trust.

Create Custom Invoices with Word Templates and Foxit Document Generation

Create Custom Invoices with Word Templates and Foxit Document Generation

Invoicing is a critical part of any business. This tutorial shows how to automate the process by creating dynamic, custom PDF invoices with the Foxit Document Generation API. Learn how to design a Microsoft Word template with special tokens, prepare your data in JSON, and then use a simple Python script to generate your final invoices.

Create Custom Invoices with Word Templates and Foxit Document Generation

Invoicing is a critical part of any business, often involving multiple steps—gathering customer data, calculating amounts owed, and sending out invoices so your company can get paid. Foxit’s Document Generation API streamlines this process by making it easy to create well-formatted, dynamic PDF invoices. Let’s walk through an example.

Before You Start

If you want to follow along with this blog post, be sure to get your free credentials over on our developer portal. Also, read our introductory blog post, which covers the basics of working with our API.

As a reminder, the API makes use of Microsoft Word templates. These templates are essentials tokens wrapped in double brackets. When you call the API, you’ll pass the template and your data. Our API then dynamically replaces those tokens with your data and returns you a nice PDF (you can also get a Word file back as well).

Creating Your Custom Invoice with Word Templates

Let’s begin by designing the template in Word. An invoice typically includes things like:

  • The customer receiving the invoice
  • The invoice number and issue date
  • The payment due date
  • A detailed list of items, including name, quantity, and price for each line item, with a total at the end

The Document Generation API makes no requirements in terms of how you design your templates. Size, alignment, and so forth, can match your corporate styles and be as fancy, or simple, as you like. Let’s consider the template below (I’ll link to where you can download this file at the end of the article):

MS Word template

Let's break it down from the top.

  • The first token, {{ invoiceNum }}, represents the invoice number for the customer.
  • The next token is special. {{ today \@ MM/dd/yyyy }} represents two different features of the Document Generation API. First, today is a special value representing the present time, or more accurately, when you call the API. The next portion represents a date mask for representing a date value. Our docs have a list of available masks.
  • {{ accountName }} is another regular token.
  • The payment date, {{ paymentDueDate \@ MM/dd/yyyy }}, shows how the date mask feature can be used on dates in your own data as well.
  • Now let's look at the table. You can format tables however you like, but a common setup includes one row for the header and one row for the dynamic data. (In this example, there’s also a third row, which I'll explain shortly.) To start, you’ll use a marker tag: {{TableStart:lineItems}}, where lineItems represents an array in your data. The row ends with the matching {{TableEnd:lineItems}} tag. Between these two tags, you'll place additional tags for each value in the array. For example, we have a product, qty, price, and totalPrice for each item. You'll also see the special ROW_NUMBER value, which automatically counts each row starting at 1. Finally, the \# Currency format is applied to the totalPrice value to display it as a currency.
  • The last row in the table uses two special features together, namely SUM(ABOVE), which maps to creating a total of the last column from the table. This can be paired with currency formatting as shown.

Alright, now that you've seen the template, let's talk data!

The Data for Your Custom Invoices

Usually the data for an operation like this would come from a database, or perhaps an API with an ecommerce system. For this demo, the data will come from a simple JSON file. Let's take a look at it:

[
{
	"invoiceNum":100, 
	"accountName":"Customer Alpha", 
	"accountNumber":1,
	"paymentDueDate":"August 15, 2025",
	"lineItems":[
		{"product":"Product 1", "qty":5, "price":2, "totalPrice":10},
		{"product":"Product 5", "qty":3, "price":9, "totalPrice":18},
		{"product":"Product 4", "qty":1, "price":50, "totalPrice":50},
		{"product":"Product X", "qty":2, "price":15, "totalPrice":30}
	]
},
{
	"invoiceNum":25, 
	"accountName":"Customer Beta", 
	"accountNumber":2,
	"paymentDueDate":"August 15, 2025",
	"lineItems":[
		{"product":"Product 2", "qty":9, "price":2, "totalPrice":18},
		{"product":"Product 4", "qty":1, "price":8, "totalPrice":8},
		{"product":"Product 3", "qty":10, "price":25, "totalPrice":250},
		{"product":"Product YY", "qty":3, "price":15, "totalPrice":45},
		{"product":"Product AA", "qty":2, "price":100, "totalPrice":200}
	]
},
{
	"invoiceNum":51, 
	"accountName":"Customer Gamma", 
	"accountNumber":3,
	"paymentDueDate":"August 15, 2025",
	"lineItems":[
		{"product":"Product 9", "qty":1, "price":2, "totalPrice":2},
		{"product":"Product 23", "qty":30, "price":9, "totalPrice":270},
		{"product":"Product ZZ", "qty":6, "price":15, "totalPrice":90}
	]
}
]

The data consists of an array of 3 sets of invoice data. Each set follows the same pattern and matches what you saw above in the Word template. The only exception being the accountNumber value which wasn't used in the template. That's fine – sometimes your data will include things not necessary for the final PDF. In this case, though, we're actually going to make use of it (you'll see in a moment). Onward to code!

Calling the Foxit API with Our Data

Now for my favorite part – actually calling the API. The Generate Document API is incredibly simple; needing just your credentials, a base64 version of the template, and your data. The entire demo is slightly over 50 lines of Python code, so let's look at the template and then break it down.

import os
import requests
import sys 
from time import sleep 
import base64 
import json 
from datetime import datetime

CLIENT_ID = os.environ.get('CLIENT_ID')
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
HOST = os.environ.get('HOST')

def docGen(doc, data, id, secret):
	
	headers = {
		"client_id":id,
		"client_secret":secret
	}

	body = {
		"outputFormat":"pdf",
		"documentValues": data,  
		"base64FileString":doc
	}

	request = requests.post(f"{HOST}/document-generation/api/GenerateDocumentBase64", json=body, headers=headers)

	return request.json()

with open('invoice.docx', 'rb') as file:
	bd = file.read()
	b64 = base64.b64encode(bd).decode('utf-8')

with open('invoicedata.json', 'r') as file:
	data = json.load(file)

for invoiceData in data:
	result = docGen(b64, invoiceData, CLIENT_ID, CLIENT_SECRET)

	if result["base64FileString"] == None:
		print("Something went wrong.")
		print(result)
		sys.exit()

	b64_bytes = result["base64FileString"].encode('ascii')
	binary_data = base64.b64decode(b64_bytes)

	filename = f"invoice_account_{invoiceData["accountNumber"]}.pdf"

	with open(filename, 'wb') as file:
		file.write(binary_data)
		print(f"Done and stored to {filename}")

After importing the necessary modules and loading credentials from the environment, we define a simple docGen method. This method takes the template, data, and credentials, then calls the API endpoint. The API responds with the rendered PDF in Base64 format, which the method returns.

The main code of the template breaks down to:

  • Reading in the template and converting it to base64.
  • Reading in the JSON file
  • Iterating over each block of invoice data and calling the API
  • Remember how I said accountNumber wasn't used in the template? We actually use it here to generate a unique filename. Technically, you don't need to store the results at all. You could take the raw binary data and email it. But having a copy of the results does mean you can re-use it later, such as if the customer is late to pay.

Here's an example of one of the results:

Example PDF result

Next Steps

If you want to try this demo yourself, first grab yourself a shiny free set of credentials and then head over to our GitHub to grab the template, Python, and sample output values yourself.