REFRACT Platform for Dynamics 365 Business Central

Automated Document Generation - VaultPDF Business Central

Trigger VaultPDF document generation automatically on posting, from external systems via API, or programmatically from AL code. Covers the Generation Queue, job setup, and monitoring.

Overview

VaultPDF supports four ways to trigger document generation. All four paths funnel into the same Generation Queue - a persistent table that the VaultPDF Gen Processor job drains asynchronously.

TriggerWhen to use
Interactive galleryUser opens a document and clicks Generate - standard on-demand use
Auto-generate on postTemplate flag queues generation automatically when a BC document is posted
External APIAn integrated system (ERP, custom app, integration platform) calls the BC API after creating or posting a document
AL codeA partner or customer extension calls the queue table helper directly

The first three scenarios are covered in detail below. The interactive gallery is described in Processing Documents.


Architecture: The Generation Queue

The Generation Queue (VaultPDF Gen Request, table 77113) decouples the act of requesting PDF generation from the act of executing it. Any trigger path inserts a row; the VaultPDF Gen Processor (codeunit 77136), running as a recurring Job Queue entry, picks up Pending rows in batches and drives each one through the full VaultPDF pipeline (JSON build → SharePoint upload → Dispatcher call → Record Link).

Trigger (Post event / API call / AL code)

  Gen Request row (Status = Pending)

  Job Queue → VaultPDF Gen Processor (codeunit 77136)

  SharePoint Upload + Dispatcher Call

  Status = Completed  (or Failed / Dead after retries)

Key design properties:

  • Trigger-agnostic - the processor does not know or care how a row was created
  • Retry-safe - failed rows are retried up to VAULTPDF_GEN_QUEUE_MAX_RETRIES times before being marked Dead
  • Concurrent-safe - the processor marks a row Processing before committing, so parallel Job Queue workers cannot double-process the same request
  • Non-blocking for posting - the posting subscriber inserts Gen Request rows in a new transaction after posting commits, so a subscriber failure never rolls back a completed posting

Scenario 1: Auto-Generate on Post

Enable this flag on a template to have VaultPDF automatically queue a generation request whenever a Business Central document from that template's source table is posted.

Supported posting events:

  • Sales Invoice posted from a Sales Order or Sales Invoice
  • Sales Credit Memo
  • Purchase Invoice
  • Purchase Credit Memo

Enable Auto Generate on Post on the Template

Open VaultPDF Templates and open the template you want to automate.

In the Variants subform, select the variant to automate (usually the default variant) and click Edit Variant Settings. In the Dispatch section, enable the Auto Generate on Post toggle.

Standard Dispatch Mode Only

Auto Generate on Post is only available for variants with Dispatch Mode = Standard. Batch modes (Per Group, Per Record, Single Array, Grouped Array) are not supported for posting automation because each posted document generates exactly one header record - batch modes require a filtered record set selected at dispatch time.

Verify that the template's Source Table ID maps to the correct Business Central document table. The posting subscriber matches on this table ID:

Document typeSource Table ID
Sales Invoice112 (Sales Invoice Header)
Sales Credit Memo114 (Sales Cr.Memo Header)
Purchase Invoice122 (Purch. Inv. Header)
Purchase Credit Memo124 (Purch. Cr. Memo Hdr.)

Create the VaultPDF Gen Processor Job Queue Entry

The posting subscriber inserts queue rows, but nothing processes them unless the Gen Processor job is running.

Search for Job Queue Entries in Business Central and click New.

Configure the entry with these values:

FieldValue
Object Type to RunCodeunit
Object ID to Run77136
Object Caption to RunVaultPDF Gen Processor
Recurring JobYes
Run on Mondays ... SundaysEnable all days (or the days matching your posting schedule)
Starting Time00:00:00
Ending Time23:59:59
No. of Minutes between Runs2 (poll every 2 minutes) or 5 for lower-frequency environments
StatusReady

One Job Entry is Sufficient

A single recurring Job Queue entry processes all pending generation requests for all templates. Do not create one entry per template - this wastes Job Queue worker capacity and can cause contention.

Post a Document and Verify

Post any Sales Order or Purchase Invoice that uses a template with Auto Generate on Post enabled.

After posting, open VaultPDF Generation Queue from the Tell Me bar (or via the Generation Queue action on the Template Card).

You should see a new row with:

  • Source = Posting Event
  • Status = Pending (or Processing if the job picked it up quickly, or Completed if already done)
  • Template Code = the template that matched the posted document

Within the next Job Queue cycle, the row transitions to Completed and a Record Link (PDF link) is attached to the posted document.


Scenario 2: External System Triggers Generation via API

Use this pattern when an external application - a custom ERP, an integration platform (Power Automate, Logic Apps, Boomi, MuleSoft), or a BC API client - creates or posts a BC document and then wants to trigger VaultPDF generation without requiring a user to open Business Central.

API Endpoint

POST /api/refractlogic/vaultpdf/v1.0/companies({companyId})/generationRequests

Authentication: Standard BC OData API authentication (OAuth 2.0 with Dynamics 365 Business Central scope, or Basic auth for on-premises).

Request body:

{
  "templateCode": "PINV-STANDARD",
  "tableId": 122,
  "docNo": "PINV-00001",
  "externalRef": "ERP-JOB-REF-789"
}
FieldTypeRequiredDescription
templateCodestringYesVaultPDF template code, e.g. PINV-STANDARD. Must exist and be enabled.
tableIdintegerYesBusiness Central table ID for the posted document. See table IDs below.
docNostringYesThe document number (No. field) of the posted document.
externalRefstringNoFree-text reference from the calling system (e.g. correlation ID, job number). Stored on the queue row for traceability.

Common table IDs:

DocumentTable ID
Sales Invoice Header112
Sales Credit Memo Header114
Purchase Invoice Header122
Purchase Credit Memo124
Sales Order Header (pre-post)36
Purchase Header (pre-post)38

Response (201 Created):

{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "templateCode": "PINV-STANDARD",
  "tableId": 122,
  "docNo": "PINV-00001",
  "status": "Pending",
  "externalRef": "ERP-JOB-REF-789",
  "createdAt": "2026-06-20T14:32:00Z"
}

Polling for Completion

Poll the returned requestId to track processing:

GET /api/refractlogic/vaultpdf/v1.0/companies({companyId})/generationRequests({requestId})

Response when complete:

{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "Completed",
  "blobPath": "RefractLogic/PurchaseInvoices/PINV-00001_PINV-STANDARD.pdf",
  "correlationId": "vpdf-corr-xyz",
  "processedAt": "2026-06-20T14:33:15Z"
}
StatusMeaning
PendingQueued; not yet picked up by the processor
ProcessingProcessor is actively working on this request
CompletedPDF generated and stored; blobPath and correlationId are populated
FailedProcessing failed; errorMessage contains the reason; will be retried
Dead (Max Retries Reached)All retry attempts exhausted; manual intervention required

Polling Interval Recommendation

The Gen Processor runs every 2 - 5 minutes by default. Poll at a 30 - 60 second interval and implement an overall timeout of 15 - 30 minutes for large documents or documents involving workflow routing.

End-to-End Example: External System Posts an AP Invoice

This is the canonical scenario: an external procurement system creates and posts a Purchase Invoice via the BC standard API, then triggers VaultPDF generation.

Create and post the Purchase Invoice using the standard BC purchaseInvoices API (or equivalent custom API). Retrieve the posted invoice's No. from the response.

Request PDF generation by calling the VaultPDF generation API with the invoice number and the appropriate template code.

POST /api/refractlogic/vaultpdf/v1.0/companies({companyId})/generationRequests
Content-Type: application/json

{
  "templateCode": "PINV-STANDARD",
  "tableId": 122,
  "docNo": "PINV-00001",
  "externalRef": "PROC-SYSTEM-JOB-42"
}

Store the requestId from the 201 response. Use it to poll for completion.

Poll until status = Completed and use the blobPath or open the Vault Lifecycle portal via correlationId to retrieve the generated PDF.

Power Automate / Logic Apps Integration

To integrate with Power Automate or Azure Logic Apps, use the Business Central connector's Call a Bound Action action - or make a direct HTTP call to the OData endpoint using the HTTP with Microsoft Entra ID connector.

Recommended flow pattern:

  1. Trigger: When a record is created/modified in Business Central (or on a schedule)
  2. Action: POST to generationRequests using HTTP connector
  3. Action: Do-Until loop polling GET generationRequests({requestId}) until status = Completed or timeout
  4. Action: Send Teams/email notification with blobPath

Scenario 3: AL Developer Queues Programmatically

A partner or customer extension can queue generation requests directly from AL code without going through a user action or a posting event.

Using the Public API: QueueGenerationForPosting

The preferred way to trigger generation from AL code is VaultPDF Batch Dispatcher.QueueGenerationForPosting. This is the same path the built-in posting subscriber uses, so your requests participate in the same retry policy, queue monitoring, and extensibility hooks.

local procedure TriggerVaultGenerationForMyDoc(PostedHeader: Record "My Posted Header")
var
    BatchDispatcher: Codeunit "VaultPDF Batch Dispatcher";
    RecRef: RecordRef;
begin
    RecRef.GetTable(PostedHeader);
    BatchDispatcher.QueueGenerationForPosting('MY-TEMPLATE', RecRef);
end;

Calling QueueGenerationForPosting fires OnBeforeQueueForTable (described below) before inserting the Gen Request row. This means any partner or customer subscriber that intercepts or redirects posting-triggered generation will also apply to your call - consistent behaviour across the system.

Queue from a Custom Posting Flow (Low-Level)

If you need direct control over the Gen Request row fields, call QueueFromPosting on the table directly:

local procedure AfterMyDocumentPosting(var PostedHeader: Record "My Posted Header")
var
    GenRequest: Record "VaultPDF Gen Request";
    RecRef: RecordRef;
begin
    RecRef.GetTable(PostedHeader);
    GenRequest.QueueFromPosting('MY-TEMPLATE', RecRef);
end;

QueueFromPosting sets Source = Posting Event, captures the RecordId directly from the RecordRef, and resolves the Doc No. from the record's No. field. It is safe to call within a posting transaction - the Gen Request insert is committed independently.

OnBeforeQueueForTable

Published by "VaultPDF Posting Subscriber". Fires before the posting subscriber queues a generation request for a specific posted document table. Allows partners to skip, redirect, or substitute the template code before the Gen Request row is created.

[EventSubscriber(ObjectType::Codeunit, Codeunit::"VaultPDF Posting Subscriber",
  'OnBeforeQueueForTable', '', false, false)]
local procedure OnBeforeQueueForTable(
    TableId: Integer;
    var TemplateCode: Code[20];
    var IsHandled: Boolean)
begin
    // Example 1: Skip generation entirely for a specific custom table
    if TableId = Database::"My Custom Header" then begin
        IsHandled := true; // Prevent default queue; handle in your own subscriber
        exit;
    end;

    // Example 2: Redirect to a different template per company
    if (TableId = Database::"Sales Invoice Header") and (CompanyName = 'SUBSIDIARY') then begin
        TemplateCode := 'SINV-SUBSIDIARY';
        IsHandled := true; // Use the overridden template code instead of the default
    end;
end;

Setting IsHandled := true prevents the default posting subscriber from inserting a Gen Request row. Set it without changing TemplateCode to suppress generation completely, or set it after assigning a new TemplateCode to redirect to a different template.

Queue a Specific Record Internally

Use QueueInternal when triggering generation from a background codeunit, upgrade routine, or any non-posting context:

local procedure ReprocessDocument(DocNo: Code[20])
var
    InvHeader: Record "Sales Invoice Header";
    GenRequest: Record "VaultPDF Gen Request";
    RecRef: RecordRef;
begin
    InvHeader.Get(DocNo);
    RecRef.GetTable(InvHeader);
    GenRequest.QueueInternal('SINV-STANDARD', RecRef);
end;

Queue from an API (Server-Side Validation)

If you need to add validation logic before accepting an API-originated request, subscribe to the OnInsertRecord trigger override pattern by creating a page extension on VaultPDF Gen Request API (page 77138) and adding your logic in OnBeforeInsertRecord or a custom validation codeunit.


Configuring Maximum Retries

The Gen Processor retries failed requests up to a configurable maximum before marking them Dead (Max Retries Reached).

Navigate to VaultPDF System Settings and find the key VAULTPDF_GEN_QUEUE_MAX_RETRIES.

KeyDefaultDescription
VAULTPDF_GEN_QUEUE_MAX_RETRIES3Maximum processing attempts per request before the row is marked Dead. Increase for environments with transient connectivity issues; decrease for faster failure detection.

This key is created automatically by Initialize Default Keys with a default value of 3. Individual queue rows also carry their own Max Retries field - the system setting provides the default; the per-row value can be overridden by AL code when queuing.


Log Retention and Auto-Purge

VaultPDF automatically purges old batch job entries and upload log rows to prevent unbounded table growth. The purge runs at the start of each VaultPDF Batch Job Runner execution (codeunit 77132).

KeyDefaultDescription
VAULTPDF_LOG_RETENTION_DAYS90Days to retain completed and failed batch jobs and upload log entries before automatic deletion. Entries older than this threshold are permanently removed. Set to 0 to disable auto-purge entirely.

Configure this key in VaultPDF System Settings after running Initialize Default Keys.

What gets purged:

  • VaultPDF Batch Job Entry records with status Completed or Failed that are older than the retention threshold
  • VaultPDF Upload Log rows linked to purged batch job entries
  • VaultPDF Batch Detail Line rows linked to purged batch job entries

What is not purged:

  • Queued, Running, or Partial batch jobs (regardless of age) - active records are never deleted
  • Generation Queue rows (VaultPDF Gen Request) - these are managed independently via Purge Completed on the Generation Queue page

If VAULTPDF_LOG_RETENTION_DAYS is set to 0, auto-purge is disabled entirely and records accumulate indefinitely. Plan a manual maintenance strategy (e.g., periodic Purge Completed from the batch page) if you disable automatic purge.


Monitoring the Generation Queue

Generation Queue Page

Open VaultPDF Generation Queue from the Tell Me bar to view all requests across all templates.

From the Template Card, the Generation Queue action in the action bar opens the same list pre-filtered to the current template.

Page columns:

ColumnDescription
Request IDUnique identifier (GUID) for this generation request
Template CodeThe VaultPDF template used
Doc No.Document number from the source BC record
StatusCurrent state: Pending, Processing, Completed, Failed, Dead
SourceHow the request was created: Internal, API, Posting Event, Batch
Retry CountNumber of processing attempts so far
Error MessageError text from the last failed attempt
Blob PathSharePoint path of the generated PDF (populated on Completed)
Created AtWhen the request was queued
Processed AtWhen processing completed or last failed

Status color coding:

StatusStyle
CompletedGreen (Favorable)
FailedRed (Unfavorable)
Dead (Max Retries Reached)Red (Unfavorable)
PendingStandard (Ambiguous)
ProcessingYellow (Attention)

Actions

ActionPurpose
Retry SelectedResets selected Failed or Dead rows back to Pending for reprocessing. Only enabled when selected rows are in a retryable state.
Delete SelectedRemoves selected rows. Use for cleaning up rows that are no longer needed.
Purge CompletedRemoves all Completed rows for the current template filter. Use for routine maintenance of the queue table.
Filter: PendingApplies a quick filter showing only rows awaiting processing
Filter: Failed / DeadShows rows that require attention
Clear FilterRemoves the quick filter

Dead Rows Require Manual Intervention

A row enters Dead (Max Retries Reached) status when all retry attempts have failed. Investigate the Error Message column to identify the root cause (connectivity, template misconfiguration, missing data) before using Retry Selected to requeue the row. Retrying without fixing the underlying issue will exhaust retries again.


Troubleshooting

SymptomLikely causeResolution
Queue rows stay Pending indefinitelyGen Processor Job Queue entry is not running or is on holdOpen Job Queue Entries, find codeunit 77136, confirm status is Ready and not on hold
All rows show Failed immediatelyDispatcher or SharePoint connection errorRun System Status Check in System Settings; check Error Message on a failed row
Auto Generate on Post not queuing any rowsTemplate's Source Table ID does not match the posted document tableVerify the template's Page ID auto-resolved to the correct source table; check that Enabled = true
API returns 401 or 403OData authentication missing or caller lacks permissionsEnsure the API caller has VPDF-USER permission set; verify OAuth scope includes Dynamics 365 Business Central
API returns 400 with "Template Code not found"templateCode value in the request body does not match any enabled templateCheck VaultPDF Templates - template must exist and have Enabled = true
Row completes but no PDF link on the documentCommitPendingLink was not called, or Record Link creation failedCheck the Blob Path field on the queue row - if populated, the PDF exists in SharePoint; the Record Link attachment may have been skipped due to a missing RecordId on the request
Dead rows accumulating faster than expectedVAULTPDF_GEN_QUEUE_MAX_RETRIES set too low, or a systemic error is failing all requestsIncrease VAULTPDF_GEN_QUEUE_MAX_RETRIES in System Settings; investigate root cause from Error Message before retrying

On this page