POST /api/docs/generate-bulk
Generates N documents from a single template in one request. The server resolves the template by key, fetches the binary once, merges each document in a loop, uploads all results to Salesforce Files, and returns an array of ContentDocumentId values. No document bytes are returned to the caller.
Designed for Apex batch classes — see KS_BulkGenerateDocument for the Apex utility that wraps this endpoint.
Base URL: https://app.keelstone.dev
Authentication: x-org-api-key + x-sf-user-id (Apex-originated; set via KS_LicenseService.setAuthHeaders)
Request
POST /api/docs/generate-bulk
Content-Type: application/json
x-org-api-key: <orgApiKey>
x-sf-user-id: <sfUserId>
{
templateKey: string; // Template_Key__c on a Keelstone_Template__c record
documents: Document[]; // Array of per-document merge specs
outputFormat?: string; // 'pdf' to convert all outputs (optional)
}
type Document = {
data: object; // Merge context — keys become template tokens
filename?: string; // Output filename, e.g. 'Ada Welcome.docx'
linkedEntityId?: string; // Salesforce record ID to attach the file to
externalLink?: boolean; // true to return a public download URL
sharePointPath?: string; // SharePoint folder to upload this document to
}
| Field | Type | Required | Description |
|---|---|---|---|
templateKey | string | Yes | Template_Key__c on the Keelstone_Template__c record |
documents | Document[] | Yes | Non-empty array — one entry per document to generate |
outputFormat | string | No | 'pdf' converts all outputs regardless of template extension |
Response
200 OK
{
"results": [
{ "success": true, "contentDocumentId": "069...", "filename": "Ada Welcome.docx" },
{ "success": true, "contentDocumentId": "069...", "filename": "Bob Welcome.docx", "documentUrl": "https://...", "sharePointFileUrl": "https://contoso.sharepoint.com/..." },
{ "success": false, "error": "Render failed: unknown token {BadField}", "filename": "Eve Welcome.docx" },
{ "success": true, "contentDocumentId": "069...", "filename": "Hal Welcome.docx", "sharePointError": "SharePoint integration requires the Keelstone Pro plan or higher." }
]
}
Results are returned in the same order as the input documents array. A per-document failure does not abort the rest — the endpoint always returns 200 with a results array. Inspect each success flag to detect partial failures.
| Field | Type | Description |
|---|---|---|
success | boolean | true if the document was generated and uploaded to Salesforce Files |
contentDocumentId | string | Salesforce ContentDocumentId of the generated file |
documentUrl | string | Public download URL (only when externalLink: true was set) |
sharePointFileUrl | string | SharePoint URL of the uploaded file (only when sharePointPath was supplied and the upload succeeded) |
sharePointError | string | Per-document SharePoint error — set when the SP upload failed or the org is not on Pro. Does not affect success. |
error | string | Per-document generation error message when success is false |
filename | string | The filename used for this document |
400 Bad Request — missing or invalid inputs
{ "error": "templateKey is required" }
{ "error": "documents must be a non-empty array" }
{ "error": "Unsupported template type: .pptx. Supported: .docx, .xlsx" }
404 Not Found — template key does not resolve
{ "error": "No active template found with key: welcome-packet" }
{ "error": "No file attached to template: welcome-packet" }
How it works
- Resolves Salesforce credentials from
connected_orgsusing the authenticated org ID - Queries the
Keelstone_Template__crecord bytemplateKey→ContentDocumentId→ latestContentVersion - Downloads the template binary once
- For each document in the array: creates a fresh in-memory ZIP from the template buffer, merges via docxtemplater (
.docx) or XlsxTemplate (.xlsx), injects_ks_template_keyas a custom document property, optionally converts to PDF - Uploads each merged file to Salesforce as a new
ContentVersion; creates aContentDocumentLinkiflinkedEntityIdwas provided - Returns the results array — per-document errors are caught and reported without aborting remaining documents
The template buffer is parsed once and reused across all iterations — no redundant Salesforce downloads.
Using from Apex
The idiomatic way to call this endpoint is via KS_BulkGenerateDocument.generateAll(), which handles serialisation, auth headers, and result mapping:
global class GenerateWelcomePackets
implements Database.Batchable<SObject>, Database.AllowsCallouts {
global Database.QueryLocator start(Database.BatchableContext ctx) {
return Database.getQueryLocator(
'SELECT Id, Name, Level__c FROM Contact WHERE Send_Packet__c = true'
);
}
global void execute(Database.BatchableContext ctx, List<SObject> scope) {
List<KS_BulkGenerateDocument.Request> reqs = new List<KS_BulkGenerateDocument.Request>();
for (SObject rec : scope) {
Contact c = (Contact) rec;
KS_BulkGenerateDocument.Request req = new KS_BulkGenerateDocument.Request();
req.templateKey = 'welcome-packet';
req.contextJson = JSON.serialize(new Map<String, Object>{
'Name' => c.Name,
'Level__c' => c.Level__c
});
req.filename = c.Name + ' Welcome Packet.docx';
req.linkedEntityId = c.Id;
reqs.add(req);
}
KS_BulkGenerateDocument.generateAll(reqs);
}
global void finish(Database.BatchableContext ctx) {}
}
Database.executeBatch(new GenerateWelcomePackets(), 20);
See Bulk Document Generation in the Developer Guide for the full KS_BulkGenerateDocument API reference and batch size guidance.
Governor limit profile (N documents)
| Resource | KS_GenerateDocument (serial) | KS_BulkGenerateDocument (bulk) |
|---|---|---|
| SOQL queries | 4N | 1 (license check) |
| HTTP callouts | N | 1 |
| DML statements | 2N | 0 |
| Apex heap | O(N × template size) | O(N × context size) |
Related
POST /api/docs/generate— single document, caller providestemplateBase64POST /api/docs/generate-from-key— single document, server resolves template by key (LWC/session-based)