# API REFERENCE

Developer Reference

Everything you need to integrate DocuForge into your stack — authentication, endpoints, webhooks, and error handling.

CONTENTS
Quick Start
Authentication
Documents API
Templates API
Webhooks
Error Codes
Pipeline Editor
↳ Step Types
HTML → PDF
DOCX Templates
XLSX Templates
PDF Form Fill
Batch & ZIP
Post-Processors
Assets

Base URLhttps://api.docuforge.io
Versionv1 (stable)

Quick Start

Generate your first document in under 3 minutes.

1
Create your API key in the dashboard

Navigate to Dashboard → API Keys → Create Key. Choose sk_test_ for staging or sk_live_ for production.

2
Generate a document via POST
terminal
curl -X POST https://api.docuforge.io/api/v1/documents/generate \
  -H "Authorization: Bearer sk_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "tmpl_abc123",
    "data": {
      "recipient": "Marie Dupont",
      "amount": "4 500,00 €",
      "date": "2026-01-15"
    }
  }'
3
Receive your document ID and download link
response.json
{
  "correlationId": "doc_9f3a7bc1d2e4",
  "status": "RENDERED",
  "templateId": "tmpl_abc123",
  "createdAt": "2026-01-15T09:42:11Z",
  "expiresAt": null,
  "downloadUrl": "/api/v1/documents/doc_9f3a7bc1d2e4/download",
  "verifyUrl": "/verify/qr_tk_xxxx",
  "contentHash": "sha256:4b2d..."
}

Authentication

DocuForge supports two authentication methods depending on context.

API Key (Bearer Token)
Recommended

Use your sk_test_* or sk_live_* key in every request header. Scoped to your tenant with configurable expiration and IP restrictions.

Authorization: Bearer sk_live_YOUR_SECRET_KEY
JWT — Dashboard Sessions

Short-lived RS256 JWT tokens issued upon sign-in for the dashboard. Not recommended for server-to-server integrations. Rotate every 1 hour automatically.

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
API Key Scopes
documents:read
List, get, download documents
documents:write
Generate, revoke, expire documents
templates:read
List and get template metadata
templates:write
Create and update templates
webhooks:manage
Create and manage webhook endpoints
audit:read
Read audit log events

Documents API

Core endpoints for document generation, retrieval, lifecycle management, and verification.

POST
/api/v1/documents/generateTrigger document generation from a template
GET
/api/v1/documentsList documents with pagination & filters
GET
/api/v1/documents/{correlationId}Get document metadata & lifecycle status
GET
/api/v1/documents/{correlationId}/downloadDownload PDF binary (or regenerated on-demand)
POST
/api/v1/documents/{correlationId}/revokeRevoke a document with an audit reason
POST
/api/v1/documents/{correlationId}/expireSet a hard expiration date on a document
GET
/api/v1/documents/{correlationId}/verifyVerify document integrity & QR signature
DELETE
/api/v1/documents/{correlationId}Permanently delete a document record
POST
/api/v1/documents/generate

Request body schema:

generate_request.json
{
  "templateId":   "string  (required) — UUID of the target template",
  "data":         "object  (required) — Variable bindings for the template",
  "expiresAt":    "string  (optional) — ISO-8601 expiry datetime",
  "webhookUrl":   "string  (optional) — Override default webhook for this request",
  "metadata":     "object  (optional) — Arbitrary key/value stored with the document"
}

Templates API

Manage your document templates — HTML, PDF-fill, or DOCX sources with versioning and checksums.

POST
/api/v1/templatesCreate a new template (HTML/PDF source)
GET
/api/v1/templatesList all templates for your organisation
GET
/api/v1/templates/{id}Get template metadata & checksum
PUT
/api/v1/templates/{id}Update template content (creates new version)
DELETE
/api/v1/templates/{id}Archive a template
Template Object
template_response.json
{
  "id":          "tmpl_abc123",
  "name":        "Bulletin de paie FR",
  "type":        "HTML_TO_PDF",
  "version":     3,
  "checksum":    "sha256:f3a2...",
  "variables":   ["recipient", "amount", "date", "company"],
  "createdAt":   "2025-11-01T08:00:00Z",
  "updatedAt":   "2026-01-10T14:22:00Z"
}

Webhooks

Receive real-time event notifications delivered to your endpoint via HTTPS POST.

document.generated
Fired when document generation completes successfully
document.failed
Fired when generation fails — includes error payload
document.revoked
Fired when a document is revoked via API or dashboard
document.expired
Fired when a document passes its expiration date
template.updated
Fired when a template is updated (version bump)
Webhook Payload Example
webhook_payload.json
{
  "event":     "document.generated",
  "timestamp": "2026-01-15T09:42:13Z",
  "tenantId":  "ten_xk99z",
  "data": {
    "correlationId": "doc_9f3a7bc1d2e4",
    "templateId":    "tmpl_abc123",
    "status":        "RENDERED",
    "downloadUrl":   "/api/v1/documents/doc_9f3a7bc1d2e4/download"
  },
  "signature": "sha256=hmac_XXXX"
}
Always verify the X-DocuForge-Signature header using HMAC-SHA256 with your webhook secret before processing the payload.

Error Codes

All errors follow a consistent JSON structure. Use request_id when contacting support.

error_response.json
{
  "status":     422,
  "error":      "UNPROCESSABLE_ENTITY",
  "message":    "Template variable 'amount' is required but was not provided",
  "request_id": "req_7f2a1b9c",
  "timestamp":  "2026-01-15T09:42:11Z"
}
400
BAD_REQUEST
Malformed JSON body or missing required fields
401
UNAUTHORIZED
Missing or invalid API key / JWT token
403
FORBIDDEN
Valid token but insufficient RBAC permissions
404
NOT_FOUND
Document, template, or resource does not exist
409
CONFLICT
Document already revoked or in a terminal state
422
UNPROCESSABLE_ENTITY
Template variable binding failed or schema mismatch
429
RATE_LIMITED
Exceeded per-tenant request rate limit
500
INTERNAL_ERROR
Unexpected server error — includes request_id for support

Pipeline Editor

The Pipeline Editor lets you define, on each template, a complete data orchestration graph that runs before rendering. It fetches data from external sources, transforms it, validates it, generates values, and assembles the final payload — all without writing backend code.

Full pipeline_config structure
pipeline_config.json
{
  "version": "2.0",
  "name": "Insurance Policy Generation",
  "description": "Fetch policy, validate, generate reference, render",

  // Execution mode
  "mode": "SINGLE",           // SINGLE | BATCH | FAN_OUT
  "output": "SINGLE_PDF",     // SINGLE_PDF | ZIP | MERGED_PDF | INDIVIDUAL

  // Global variables injected into every step context
  "variables": {
    "base_url": "https://api.internal.com",
    "tax_rate": "0.20"
  },

  // Static constants accessible as {{constants.name}}
  "constants": {
    "company_name": "Atlantis Finance",
    "footer_text": "Document generated by DocuForge"
  },

  // Execution limits
  "timeout_ms": 120000,
  "max_retries": 2,

  // Steps — executed in order unless CONDITION branches
  "steps": [ /* see Step Types below */ ],

  // Optional: overlay text/images on the final PDF
  "post_processing": [ /* see Post-Processors below */ ],

  // Error policy
  "error_handling": {
    "strategy": "FAIL_FAST",
    "notify_on_error": true,
    "error_webhook_url": "https://your-system.com/hooks/docuforge-errors"
  }
}
Step Types — all 15
GATEWAY_CALL

Call your organisation's configured gateway (insurance core, ERP…). Authentication is handled automatically by the configured gateway.

{
  "id": "fetch_policy",
  "type": "GATEWAY_CALL",
  "description": "Fetch policy details from core system",
  "gateway_config_id": 1,
  "method": "GET",
  "endpoint": "/policies/{{input.policy_number}}",
  "output_key": "policy"
}
EXTERNAL_AUTH

Authenticate against an OAuth2 or token endpoint. The extracted token is stored and automatically injected by EXTERNAL_CALL steps that reference this step.

{
  "id": "auth_step",
  "type": "EXTERNAL_AUTH",
  "url": "https://partner-api.com/oauth/token",
  "method": "POST",
  "body": {
    "grant_type": "client_credentials",
    "client_id": "{{vars.client_id}}",
    "client_secret": "{{vars.client_secret}}"
  },
  "extract": {
    "token": "$.access_token",
    "expires_in": "$.expires_in"
  },
  "output_key": "auth"
}
EXTERNAL_CALL

HTTP call to an external service. If an EXTERNAL_AUTH step ran before, its token is injected automatically in the Authorization header.

{
  "id": "fetch_client",
  "type": "EXTERNAL_CALL",
  "url": "https://partner-api.com/clients/{{input.client_id}}",
  "method": "GET",
  "headers": {
    "X-Custom-Header": "docuforge"
  },
  "output_key": "client",
  "continue_on_error": true,
  "default_on_error": { "name": "Unknown", "email": "" }
}
HTTP_CALL

Raw HTTP call without gateway or auth context. Use for public endpoints or when you manage auth manually via headers.

{
  "id": "get_rates",
  "type": "HTTP_CALL",
  "url": "https://api.ecb.europa.eu/stats/rates?currency=EUR",
  "method": "GET",
  "response_extract": "$.rates[0].value",
  "output_key": "eur_rate",
  "timeout_ms": 5000
}
CONDITION

Conditional branching. Evaluates an expression and routes execution to then_steps or else_steps. Skipped branch steps are completely excluded from context.

{
  "id": "check_vip",
  "type": "CONDITION",
  "condition": "{{client.category}} == 'VIP'",
  "then_steps": ["fetch_vip_discount", "apply_premium_template"],
  "else_steps": ["apply_standard_template"],
  "on_true": "CONTINUE",
  "on_false": "CONTINUE"
}
TRANSFORM

Data transformation step. Supports 14 operations: CONCAT, UPPERCASE, LOWERCASE, TRIM, FORMAT_DATE, TEMPLATE, CALCULATE, SUBSTRING, REPLACE, DEFAULT, LENGTH, NOW, TODAY.

{
  "id": "format_data",
  "type": "TRANSFORM",
  "transformations": [
    {
      "target": "full_name",
      "operation": "CONCAT",
      "sources": ["client.first_name", "client.last_name"],
      "pattern": " "
    },
    {
      "target": "effective_date",
      "operation": "FORMAT_DATE",
      "sources": ["policy.start_date"],
      "pattern": "dd/MM/yyyy"
    },
    {
      "target": "total_ttc",
      "operation": "CALCULATE",
      "sources": ["policy.premium"],
      "pattern": "{{policy.premium}} * 1.20"
    },
    {
      "target": "ref_label",
      "operation": "TEMPLATE",
      "pattern": "Police N° {{policy.number}} — {{full_name}}"
    },
    {
      "target": "status_upper",
      "operation": "UPPERCASE",
      "sources": ["policy.status"]
    }
  ],
  "output_key": "formatted"
}
GENERATE

Generate dynamic values: unique IDs, formatted dates, sequential references, random tokens. Useful for document numbers and tracking references.

{
  "id": "gen_ref",
  "type": "GENERATE",
  "generate_type": "REFERENCE_NUMBER",
  "format_pattern": "ATT-{YEAR}-{ORG}-{SEQ:8}",
  "output_key": "doc_reference"
}

// Other generate_type values:
// "UUID"           → "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// "UUID_SHORT"     → "f47ac10b"
// "SEQUENCE"       → "00000042" (format_pattern: "%08d")
// "DATE"           → "2026-02-28" (format_pattern: "yyyy-MM-dd")
// "DATETIME"       → "2026-02-28T12:00:00Z"
// "YEAR"           → "2026"
// "RANDOM"         → random alphanumeric string
MERGE

Merge results from multiple steps into one object. Supports DEEP_MERGE, SHALLOW_MERGE, OVERRIDE, ARRAY_CONCAT, CHERRY_PICK strategies. Use field_mapping for selective cherry-picking.

{
  "id": "assemble",
  "type": "MERGE",
  "sources": ["policy", "client", "formatted", "gen_ref"],
  "merge_strategy": "DEEP_MERGE",
  "conflict_resolution": "LAST_WINS",
  "field_mapping": {
    "holder_name":    "{{client.full_name}}",
    "policy_number":  "$.policy.reference",
    "document_ref":   "{{gen_ref}}",
    "premium_ttc":    "{{formatted.total_ttc}}",
    "effective_date": "{{formatted.effective_date}}"
  },
  "output_key": "final_payload"
}
VALIDATION

Validate required fields before rendering. Supports REQUIRED, REGEX, MIN_LENGTH, MAX_LENGTH, ENUM, RANGE rules. By default, a failed validation stops the pipeline.

{
  "id": "validate_payload",
  "type": "VALIDATION",
  "validation_rules": [
    { "field": "client.email",     "rule": "REGEX",     "value": "^[^@]+@[^@]+$", "message": "Invalid email format" },
    { "field": "policy.premium",   "rule": "RANGE",     "value": "0,999999",       "message": "Premium out of range" },
    { "field": "policy.type",      "rule": "ENUM",      "value": "AUTO,HABITATION,VIE", "message": "Unknown policy type" },
    { "field": "client.last_name", "rule": "MIN_LENGTH","value": "2",              "message": "Name too short" },
    { "field": "policy.number",    "rule": "REQUIRED",  "value": "",               "message": "Policy number is mandatory" }
  ]
}
FIELD_MAPPING

Fine-grained field-level mapping from one context path to another. Lighter than MERGE — use when you just need to reshape a few fields.

{
  "id": "remap",
  "type": "FIELD_MAPPING",
  "field_mapping": {
    "insured_name":    "$.client.full_name",
    "policy_ref":      "$.policy.number",
    "coverage_amount": "$.policy.coverage.total"
  },
  "output_key": "mapped"
}
SCRIPT

Evaluate a SpEL (Spring Expression Language) expression for complex business logic not covered by TRANSFORM. Has access to the full context.

{
  "id": "compute_risk",
  "type": "SCRIPT",
  "script_language": "spel",
  "expression": "T(Math).round(#policy.premium * #client.risk_factor * 100.0) / 100.0",
  "output_key": "adjusted_premium"
}
SUB_PIPELINE

Execute another template's pipeline as a sub-routine. Perfect for reusable data enrichment flows (e.g., a "fetch-client" pipeline shared across many templates).

{
  "id": "run_kyc",
  "type": "SUB_PIPELINE",
  "sub_pipeline_template_code": "KYC_ENRICHMENT",
  "sub_pipeline_input": {
    "client_id": "{{input.client_id}}",
    "check_level": "FULL"
  },
  "output_key": "kyc_result"
}
POST_EXTERNAL_RESULT

Final step to call an external rendering service and retrieve the document as base64 or a URL link. Used when rendering is delegated to a third-party (e.g., partner document system).

{
  "id": "external_render",
  "type": "POST_EXTERNAL_RESULT",
  "url": "https://render.partner.com/generate",
  "method": "POST",
  "body_template": { "data": "{{final_payload}}", "format": "PDF" },
  "response_mode": "BUFFER_BASE64",
  "buffer_base64_path": "$.document.content",
  "content_type_path": "$.document.mime_type",
  "file_name_path": "$.document.name",
  "output_key": "external_result"
}
CACHE

Read or write from Redis cache. Set cache_ttl_seconds to write. Omit it to read. Avoids redundant external calls for stable data (e.g., reference tables, exchange rates).

// Write to cache
{
  "id": "cache_rates",
  "type": "CACHE",
  "cache_key": "exchange_rates_eur",
  "cache_ttl_seconds": 3600,
  "sources": ["eur_rate"]
}

// Read from cache
{
  "id": "read_rates",
  "type": "CACHE",
  "cache_key": "exchange_rates_eur",
  "output_key": "cached_rates",
  "continue_on_error": true
}
DEBUG_LOG

Log the current pipeline context to the application logs. Only active in staging environment (sk_test_ key). No-op in production.

{
  "id": "debug",
  "type": "DEBUG_LOG",
  "description": "Inspect context after merge"
}

HTML → PDF Templates

Write standard HTML/CSS. DocuForge resolves placeholders, executes loops, computes aggregations and arithmetic, then converts to a pixel-perfect PDF via Flying Saucer + iText.

Basic placeholders
template.html
<!-- Direct field access -->
<p>Dear {{client.last_name}},</p>

<!-- Nested object -->
<p>Policy: {{policy.details.reference}}</p>

<!-- Global variable (defined in pipeline.variables) -->
<p>Issued by: {{vars.company_name}}</p>

<!-- Current item inside a loop (use dot notation) -->
{{#items}}
  <p>{{.name}} — {{.quantity}}</p>
{{/items}}
Loop blocks — repeat a section for each array item
template.html
<!-- Loop over invoice lines -->
<table>
  <thead>
    <tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th></tr>
  </thead>
  <tbody>
    {{#invoice.lines}}
    <tr>
      <td>{{description}}</td>
      <td>{{quantity}}</td>
      <td>{{unit_price}} €</td>
      <td>{{total}} €</td>
    </tr>
    {{/invoice.lines}}
  </tbody>
</table>

<!-- Nested loops (two levels) -->
{{#sections}}
  <h2>{{section_title}}</h2>
  {{#clauses}}
    <p>{{clause_number}}. {{text}}</p>
  {{/clauses}}
{{/sections}}
Aggregation functions — compute totals directly in the template
template.html
<!-- Sum all "total" values in invoice.lines -->
<strong>Total HT: {{sum:invoice.lines.total}} €</strong>

<!-- Count items -->
<p>Number of lines: {{count:invoice.lines}}</p>

<!-- Average, min, max -->
<p>Average premium: {{avg:policies.premium}} €</p>
<p>Lowest:  {{min:policies.premium}} €</p>
<p>Highest: {{max:policies.premium}} €</p>
Inline arithmetic — {{calc: expression | decimal_places}}
template.html
<!-- Multiply qty × unit price, 2 decimal places -->
<td>{{calc: quantity * unit_price | 2}} €</td>

<!-- Apply tax rate -->
<td>{{calc: premium * 1.20 | 2}} €</td>

<!-- No decimal rounding -->
<td>{{calc: (price - discount) * qty}}</td>

<!-- Round to integer -->
<td>{{calc: total * 1.20 | 0}} €</td>

DOCX Templates

Upload a .docx file. DocuForge resolves placeholders in paragraphs and table cells, expands loop blocks inside tables, and can embed images from the asset library. The output is a .docx file (or PDF after conversion).

Placeholder syntax in paragraphs
In any paragraph or text run:

{{client.full_name}}        → resolved from enriched payload
{{policy.number}}
{{vars.company_name}}       → global variable from pipeline

Nested path:
{{policy.holder.address.city}}
Table loop — repeat rows for each array item
Table structure in the .docx file:

┌─────────────────┬──────────┬────────────┐
│ {{#items}}      │          │            │  ← marker row (first column)
├─────────────────┼──────────┼────────────┤
│ {{description}} │ {{qty}}  │ {{price}}  │  ← template row (cloned per item)
├─────────────────┼──────────┼────────────┤
│ {{/items}}      │          │            │  ← end marker row
└─────────────────┴──────────┴────────────┘

Rules:
  • Start marker {{#array.path}} must be in the FIRST column of a row
  • End marker   {{/array.path}} must be in the FIRST column of a row
  • All rows between markers are cloned for each item
  • Inside the loop, use {{field}}, {{nested.field}}, or {{.}} for the item itself
  • Marker rows are removed from the final document
Asset image insertion
In a paragraph (alone in its own paragraph):

{{assets.company_logo}}

Rules:
  • The placeholder must be the ONLY content in its paragraph
  • DocuForge detects {{assets.<name>}} and replaces the paragraph with the image
  • The asset must be pre-uploaded in Dashboard → Assets
  • Provide asset name as stored in the asset library
  • Supported: PNG, JPG, GIF (converted to embedded image in docx)

XLSX Templates

Upload a .xlsx file. DocuForge fills cell values and expands loop blocks — both horizontal (same row) and vertical (rows stacked downward).

Vertical loop — expand rows downward
Sheet layout:

Row 5  │ {{#invoice.lines}}   │            │            │
Row 6  │ {{description}}      │ {{qty}}    │ {{price}}  │  ← template row
Row 7  │ {{/invoice.lines}}   │            │            │

Result for 3 items: rows 5–7 expand to rows 5–7 (original 3 template rows → 3 × n rows).
Marker rows are removed. Subsequent sheet content is shifted down automatically.
Horizontal loop — expand columns rightward
Row 3: │ {{#months}} │ {{name}} │ {{/months}} │

Result: each month becomes a new column group.
Use for pivot-style reports where items become columns.
Plain cell substitution (no loop)
Any cell can contain:
  {{client.name}}          → resolved to string value
  {{policy.start_date}}    → resolved, keeps cell formatting
  {{vars.report_title}}    → global variable

PDF Form Fill

Upload a PDF with AcroForm fields. DocuForge maps data fields to form fields by name, fills them, and optionally flattens the result (making it non-editable). Use the rendering_config to define the mapping.

Field mapping — object format
rendering_config.json
{
  "fields": {
    "pdf_field_name_1": "client.full_name",
    "pdf_field_name_2": "policy.number",
    "pdf_field_date":   "formatted.effective_date",
    "pdf_field_amount": "formatted.total_ttc"
  },
  "flatten": true
}
Field mapping — array format (explicit order)
rendering_config.json
{
  "fields": [
    { "name": "ClientName",    "path": "client.full_name" },
    { "name": "PolicyNumber",  "path": "policy.number" },
    { "name": "StartDate",     "path": "formatted.effective_date" },
    { "name": "PremiumAmount", "path": "formatted.total_ttc" }
  ],
  "flatten": true,
  "image_fields": [
    {
      "name": "SignatureBox",
      "source": "client.signature_base64"
    }
  ]
}
To list all AcroForm field names in your PDF, download the template and inspect it with Adobe Acrobat or a PDF reader that shows field names. Field names are case-sensitive and must match exactly.

Batch & ZIP

Generate one document per item in an array with a single API call. DocuForge runs the pipeline in FAN_OUT mode — each item gets its own enriched context — then aggregates all outputs into a ZIP archive or a merged PDF.

FAN_OUT pipeline config
pipeline_config.json
{
  "version": "2.0",
  "mode": "FAN_OUT",
  "output": "ZIP",

  // Path to the array that drives iteration
  "fan_out_on": "$.input.insured_persons",

  // Max parallel executions (default: 5)
  "fan_out_parallelism": 10,

  // Naming template for each file inside the ZIP
  "file_naming_template": "attestation-{index}-{item.last_name}",

  "steps": [
    {
      // run_once=false → executes for EACH item (default)
      "id": "enrich_person",
      "type": "GATEWAY_CALL",
      "endpoint": "/persons/{{current_item.id}}",
      "output_key": "person_data"
    },
    {
      // run_once=true → executes ONCE before fan-out starts
      "id": "fetch_company_info",
      "type": "HTTP_CALL",
      "url": "https://api.internal.com/company",
      "run_once": true,
      "output_key": "company"
    }
  ]
}
Output modes
ZIP
One file per item, all bundled in a .zip archive. Default for batch generation.
MERGED_PDF
All PDF pages concatenated into a single PDF. Useful for printing all items as one document.
INDIVIDUAL
Return only the first successfully generated document. Use when batch is optional.
ORIGINAL_FORMAT
Preserve original format (DOCX, XLSX, PDF). Automatically zips if multiple files.
ZIP archive structure & batch metadata
batch-documents.zip
batch-documents.zip
├── attestation-0-Martin.pdf
├── attestation-1-Dupont.pdf
├── attestation-2-Bernard.pdf
└── links.txt          ← only if some documents are served as URLs

// Batch metadata in the pipeline result:
{
  "batch_meta": {
    "mode": "FAN_OUT",
    "output": "ZIP",
    "items_total": 3,
    "items_success": 3,
    "items_failed": 0
  },
  "external_result": {
    "response_mode": "BUFFER_BASE64",
    "document_base64": "<base64-zip-content>",
    "content_type": "application/zip",
    "file_name": "batch-documents.zip"
  }
}
Context variables inside a FAN_OUT loop
Inside any for_each step or template, these variables are available:

{{current_item}}          → the current array element (full object)
{{current_item.field}}    → specific field of the current item
{{current_index}}         → 0-based index in the array
{{company}}               → result of run_once=true steps (shared across all items)

Example in a template:
  <p>Person {{current_index}} of {{count:insured_persons}}</p>
  <p>Name: {{current_item.last_name}}, {{current_item.first_name}}</p>

Post-Processors

Post-processors are applied to the final PDF after rendering. They overlay QR codes, barcodes, stamps, signatures, text, or page numbers. Each one can be conditionally applied at runtime.

QR_CODE

Inject a QR code at a specific position. Source can be a URL, a document reference, or any data field.

{ "type": "QR_CODE", "source": "{{document.verification_url}}", "page": 1, "x": 450, "y": 700, "size": 80 }
BARCODE

Inject a barcode (CODE_128, EAN_13, EAN_8, QR_CODE…). Useful for inventory or tracking documents.

{ "type": "BARCODE", "source": "{{policy.barcode_value}}", "barcodeFormat": "CODE_128", "page": 1, "x": 40, "y": 760, "width": 200, "height": 40 }
SIGNATURE

Overlay an electronic signature image (base64 or URL) at a precise position.

{ "type": "SIGNATURE", "imageUrl": "{{client.signature_url}}", "page": -1, "x": 380, "y": 100, "width": 160, "height": 60 }
STAMP

Apply a stamp or seal image (approval stamp, company seal…). Supports opacity and rotation.

{ "type": "STAMP", "imageBase64": "{{vars.approval_stamp}}", "page": 1, "x": 300, "y": 400, "width": 120, "height": 120, "opacity": 0.6, "rotation": -15.0 }
IMAGE_OVERLAY

Place any image (logo, photo, icon) anywhere on any page.

{ "type": "IMAGE_OVERLAY", "imageUrl": "https://cdn.company.com/logo.png", "page": 0, "x": 40, "y": 20, "width": 100, "height": 40 }
TEXT_OVERLAY

Overlay dynamic text (generation date, document number, "CONFIDENTIEL"…). Supports font, size, color, rotation.

{ "type": "TEXT_OVERLAY", "text": "Generated {{vars.today}} — Ref: {{doc_reference}}", "page": 0, "x": 40, "y": 800, "fontSize": 8, "color": "#888888" }
CUSTOM_WATERMARK

Custom watermark text diagonal across pages. Distinct from the automatic staging watermark.

{ "type": "CUSTOM_WATERMARK", "text": "DRAFT — NOT VALID", "opacity": 0.12, "fontSize": 52, "color": "#FF0000", "rotation": -45 }
PAGE_NUMBERING

Add page numbers to every page. Format: "Page {n} of {total}".

{ "type": "PAGE_NUMBERING", "text": "Page {n} / {total}", "x": 500, "y": 20, "fontSize": 9, "color": "#666666" }
HEADER_FOOTER

Add a fixed header and/or footer to every page.

{ "type": "HEADER_FOOTER", "text": "CONFIDENTIEL — {{vars.company_name}}", "page": 0, "x": 40, "y": 820, "fontSize": 8 }
PDF_ENCRYPTION

Password-protect the PDF and restrict permissions (printing, copying, editing).

{ "type": "PDF_ENCRYPTION", "password": "{{client.dob_as_password}}", "permissions": "PRINT" }
Conditional post-processors

Every post-processor supports a condition field. If the expression evaluates to false, that post-processor is silently skipped.

// Only apply "DRAFT" watermark if the document is not yet validated
{ "type": "CUSTOM_WATERMARK", "text": "DRAFT", "condition": "{{policy.status}} != 'VALIDATED'" }

// Only add signature overlay if client has uploaded one
{ "type": "SIGNATURE", "imageUrl": "{{client.signature_url}}", "condition": "{{client.signature_url}} != null" }

// Only stamp on the last page
{ "type": "STAMP", "imageBase64": "{{vars.seal}}", "page": -1, "condition": "{{policy.approved}} == true" }
Page reference values
1, 2, 3…
1-indexed specific page number
-1
Last page of the document
0
All pages (header/footer, watermark)

Assets

Assets are reusable binary files (logos, stamps, signatures, fonts…) uploaded once in Dashboard → Assets and referenced from any template or post-processor. DocuForge injects them at render time.

Reference an asset in a DOCX template
In a DOCX paragraph (alone in its own paragraph):

{{assets.company_logo}}
{{assets.approval_stamp}}
{{assets.client_signature}}
Reference an asset in an HTML template
template.html
<!-- DocuForge resolves assets.xxx to a data URI at render time -->
<img src="{{assets.company_logo}}" width="150" height="60" alt="Logo" />

<!-- Inside a loop (same syntax) -->
{{#signatories}}
  <img src="{{assets.signature_{{role}}}}" />
{{/signatories}}
Reference an asset in a post-processor
// In post_processing array:
{
  "type": "STAMP",
  "imageBase64": "{{assets.company_seal}}",
  "page": -1,
  "x": 380, "y": 80, "width": 100, "height": 100,
  "opacity": 0.7
}
Inject an asset dynamically from the pipeline
// In a TRANSFORM step, fetch and inject an asset base64 into context:
{
  "id": "load_signature",
  "type": "GATEWAY_CALL",
  "endpoint": "/signatories/{{input.signatory_id}}/signature",
  "output_key": "signatory_sig"
}

// Then in post_processing:
{
  "type": "SIGNATURE",
  "imageBase64": "{{signatory_sig.base64}}",
  "condition": "{{signatory_sig}} != null",
  "page": -1, "x": 400, "y": 120, "width": 150, "height": 60
}
business_key — automatic document supersession

Configure business_key_field in the template metadata to automatically supersede the previous active version when a new one is generated.

// Template metadata (set in Dashboard → Template → Edit Metadata):
{
  "business_key_field": "policy_number"
}

// When DocuForge generates a new document, it reads payload.policy_number,
// finds any ACTIVE document with the same key for the same organisation,
// and marks it SUPERSEDED automatically — no extra API call needed.

// You can also pass businessKey explicitly in the generation request:
{
  "templateCode": "AVENANT_AUTO",
  "payload": { "policy_number": "POL-2024-00123", ... },
  "businessKey": "POL-2024-00123"   // overrides template metadata if present
}
READY TO BUILD?

Start integrating in minutes

Create your free developer account, generate a staging API key, and send your first document generation request before lunch.