DEV Community

PDF4me
PDF4me

Posted on

Single and Multiple Document Generation Aren't the Same Endpoint With an Array Bolted On

A team that has already wired up Generate Document (Single) will reasonably assume batch generation is the same call with an array bolted onto the data field. It is not. Generate Documents (Multiple) is a separate endpoint, with a narrower set of accepted inputs, one output format Single cannot touch at all, and a response envelope shaped nothing like the one Single returns. This post covers the exact differences, with live-verified request and response shapes for both.

The endpoint path trap

Single lives at POST /api/v2/GenerateDocumentSingle. The batch version, despite its own docs page being titled "Generate Documents (Multiple)," lives at POST /api/v2/GenerateDocumentMultiple, singular Document, no trailing s. The Generate Documents (Multiple) docs page calls this out directly as a common mistake: the plural path, GenerateDocumentsMultiple, 404s. Guessing at the plural because Single's own name pattern suggests it is the fastest way to fail a request before it reaches the template engine.

# Correct
curl -X POST https://api.pdf4me.com/api/v2/GenerateDocumentMultiple \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic YOUR_API_KEY" \
  -d @payload.json# 404s, easy to type by habit from the Single endpoint's name
curl -X POST https://api.pdf4me.com/api/v2/GenerateDocumentsMultiple ...
Enter fullscreen mode Exit fullscreen mode

Template and data types are not the same set

Single accepts five templateFileType values: Docx, MailMerge, GoogleDocs, HTML, and PDF. Docx, MailMerge, and GoogleDocs templates can render to PDF or Docx. HTML templates render to HTML only. PDF templates render to PDF only, and casing matters throughout the API.

Multiple cuts that list to three: Docx, HTML, and PDF. MailMerge and GoogleDocs, both fully supported on Single, are not options on Multiple at all. A mail-merge Word template that renders fine one record at a time through Single gets rejected outright the moment it is pointed at Multiple for a batch run.

The data side narrows the same way. Single accepts documentDataType values of Json, XML, or Csv, with exactly one of documentDataText (inline text) or documentDataFile (Base64 or a URL) set, never both. Multiple accepts only Json or XML. Csv, the format a spreadsheet-driven workflow reaches for first, does not exist on the batch endpoint.

Generate Document (Single) Generate Documents (Multiple)
Endpoint POST /api/v2/GenerateDocumentSingle POST /api/v2/GenerateDocumentMultiple
templateFileType Docx, MailMerge, GoogleDocs, HTML, PDF Docx, HTML, PDF
documentDataType Json, XML, Csv Json, XML
outputType extras none beyond PDF/Docx/HTML xlsx (Docx or PDF templates only)
Response raw file binary (200) or poll URL (202) JSON outputDocuments[] array

Where Multiple pulls ahead

There is exactly one place the trade runs the other way. Multiple's outputType field accepts xlsx for Docx or PDF templates, alongside the usual PDF and Docx choices. Single has no equivalent option under any template type. A workflow that needs to turn a batch of records into a formatted Excel workbook, rather than a folder full of individual PDFs or Word files, has to go through Multiple to get there, even if every other part of the job looks like a single-document task.

The response shape stops resembling anything

Single's synchronous response, on a 200, is the rendered file itself, delivered as binary content with the appropriate content type. On a 202, a Location header points at a poll URL that eventually resolves to that same binary file. There is no wrapper JSON to parse on the happy path.

import requests

payload = {
    "templateFileType": "Docx",
    "templateFileName": "invoice-template.docx",
    "templateFileData": "<base64 template>",
    "documentDataType": "Json",
    "documentDataText": '{"customerName": "Acme Corp", "invoiceNumber": "INV-1042"}',
    "outputType": "PDF"
}

response = requests.post(
    "https://api.pdf4me.com/api/v2/GenerateDocumentSingle",
    headers={"Authorization": "Basic YOUR_API_KEY"},
    json=payload
)

if response.status_code == 200:
    with open("output.pdf", "wb") as f:
        f.write(response.content)
elif response.status_code == 202:
    poll_url = response.headers["Location"]
Enter fullscreen mode Exit fullscreen mode

Multiple's response is a JSON object built around an outputDocuments array, one entry per generated file. Here the docs page adds a detail worth building defensively around from the start: each entry is expected to carry a fileName and a Base64 streamFile, but the page itself notes the field may also appear as fileContent, content, or data instead. That is PDF4me's own written acknowledgment that the per-file field name in this response is not fixed across every call.

import base64
import requests

payload = {
    "templateFileType": "Docx",
    "templateFileName": "invoice-template.docx",
    "templateFileData": "<base64 template>",
    "documentDataType": "Json",
    "documentDataText": '[{"customerName": "Acme Corp", "invoiceNumber": "INV-1042"}, {"customerName": "Globex", "invoiceNumber": "INV-1043"}]',
    "outputType": "PDF"
}

response = requests.post(
    "https://api.pdf4me.com/api/v2/GenerateDocumentMultiple",
    headers={"Authorization": "Basic YOUR_API_KEY"},
    json=payload
)

result = response.json()
for doc in result["outputDocuments"]:
    file_bytes = base64.b64decode(
        doc.get("streamFile") or doc.get("fileContent") or doc.get("content") or doc.get("data")
    )
    with open(doc["fileName"], "wb") as f:
        f.write(file_bytes)
Enter fullscreen mode Exit fullscreen mode

Code that decodes streamFile and assumes that key will always be present is one field-name change away from silently failing to extract a single document out of the batch, even though the request itself succeeded and the response came back with a 200.

Both endpoints share the same asynchronous pattern underneath these differences. Sending IsAsync: true can return a 202 with a Location header instead of the finished result, and the caller polls that URL with the same Authorization header until it resolves. That part of the contract is consistent. What is not consistent is everything about what gets accepted going in and what comes back going out.

Template authoring is common ground either way

Regardless of which endpoint a workflow ends up using, both render against the same mustache-style placeholder engine. The Word Template Syntax Overview covers that engine, and the Variables in Templates guide documents the {{fieldName}} binding syntax that maps a JSON key onto a spot in the Word document. For anything beyond flat fields, the Tables in Templates guide explains the row-repeat syntax that turns a JSON array into a dynamic table, which matters most on Multiple, where the whole point of the call is rendering once per array element.

Every integration platform keeps the split intact

None of PDF4me's no-code integrations collapse Single and Multiple into one action. Power Automate offers Single and Multiple as distinct actions. Make does the same with its own Single and Multiple modules, as does Zapier with Single and Multiple, and n8n with its Single and Multiple nodes. Choosing the wrong one in any of these builders means hitting the same template-type and data-type restrictions described above, just surfaced through a no-code form instead of a raw request body.

Try it before wiring it in

PDF4me's interactive API Tester covers Single, and a separate API Tester page covers Multiple, letting a real request run against both endpoints with the same template and comparable data. Running that comparison once, before either endpoint gets wired into production code, is a cheaper way to learn the contract than discovering it from a parser that expected a file and got a JSON object holding a field it did not know to look for.

Website: pdf4me.com
Documentation: docs.pdf4me.com
Developer portal: dev.pdf4me.com

Top comments (0)