Docman
Docman is the platform's document generation service. Generate PDFs from HTML Handlebars templates or AcroForm PDF templates, store them in S3 with presigned download links and public share URLs, and perform PDF operations (merge, split, page count) — all through a single API with the @insureco/docman SDK.
Overview
Docman handles two kinds of templates and three delivery modes:
| Concept | Description |
|---|---|
| HTML template | Handlebars markup rendered to PDF via Puppeteer. Full CSS, landscape/portrait, dynamic content. |
| PDF template | Existing AcroForm PDF with fill-in fields. Field values are injected via pdf-lib. |
| Template group | Ordered list of HTML and/or PDF templates merged into one output document. |
| Managed delivery | Document stored in S3. Returns presigned download URL (1-hour TTL) and permanent public share URL. |
| Passthrough delivery | Raw PDF buffer returned directly — nothing stored. Useful for previews. |
Your Service
│
└─ DocmanClient.fromEnv()
│
├─ POST /documents/generate → PDF stored in S3
│ returns downloadUrl + publicUrl
│
├─ POST /documents/generate/passthrough → raw Buffer
│
├─ CRUD /templates/html → Handlebars templates
├─ CRUD /templates/pdf → AcroForm templates
├─ CRUD /template-groups → multi-template compositions
│
└─ POST /operations/{merge,split,page-count}docman.tawa.insureco.io. Add it as an internalDependency in catalog-info.yaml to have DOCMAN_URL auto-injected on deploy.Setup
1. Declare the dependency
# catalog-info.yaml
spec:
internalDependencies:
- service: docman # injects DOCMAN_URL2. Install the SDK
npm install @insureco/docman3. Initialise the client
import { DocmanClient } from '@insureco/docman'
// Reads DOCMAN_URL from env. Auth priority:
// 1. BIO_CLIENT_ID + BIO_CLIENT_SECRET → client_credentials token
// 2. INTERNAL_SERVICE_KEY → internal key header
// 3. No auth → local dev / testing
const docman = DocmanClient.fromEnv()Manual construction
const docman = new DocmanClient({
baseUrl: 'https://docman.tawa.insureco.io',
accessTokenFn: async () => myTokenProvider.getToken(),
retries: 2, // default
timeoutMs: 15000 // default
})Generating Documents
Managed (store in S3)
The most common pattern. Docman renders the templates, merges the output, stores the PDF in S3, and returns a presigned URL and a permanent public share link.
const result = await docman.generate({
templateIds: ['tmpl_abc123'], // HTML or PDF template IDs
data: {
insuredName: 'Acme Corp',
policyNumber: 'POL-00123',
effectiveDate: '2026-03-01',
},
name: 'Policy Declarations — POL-00123',
description: 'Renewal declarations page',
})
// result.documentId — stored DocmanDocument ID
// result.version — version number (starts at 1)
// result.downloadUrl — presigned S3 URL (1-hour TTL)
// result.publicUrl — /d/:shareToken (permanent)
// result.pages — page count
// result.sizeBytestemplateIds to generate a multi-template merged document in one call. Template groups apply their own ordering and coordinate metadata automatically.Passthrough (raw buffer)
Returns the PDF buffer directly. Nothing is stored. Useful for on-the-fly previews or streaming to a client.
const pdfBuffer = await docman.generatePassthrough({
templateIds: ['tmpl_abc123'],
data: { insuredName: 'Acme Corp' },
name: 'preview',
})
// Express: stream directly to client
res.setHeader('Content-Type', 'application/pdf')
res.send(pdfBuffer)HTML Templates
HTML templates use Handlebars syntax. Docman renders them with Puppeteer — full CSS, images, and landscape support.
Create
const template = await docman.createHtmlTemplate({
name: 'Policy Declarations',
html: `
<style>
body { font-family: sans-serif; padding: 40px; }
.header { border-bottom: 2px solid #000; margin-bottom: 20px; }
</style>
<div class="header">
<h1>{{policyNumber}}</h1>
<p>Insured: {{insuredName}}</p>
</div>
<p>Effective: {{effectiveDate}}</p>
`,
landscape: false,
isSystem: false,
})
// template._id — use this as templateId when generatingList / Get / Update / Delete
// List (active templates)
const { data, meta } = await docman.listHtmlTemplates({ page: 1, limit: 20 })
// Get single
const template = await docman.getHtmlTemplate('tmpl_abc123')
// Update
const updated = await docman.updateHtmlTemplate('tmpl_abc123', {
html: '<p>Updated HTML: {{insuredName}}</p>',
})
// Delete (archives — does not permanently remove)
await docman.deleteHtmlTemplate('tmpl_abc123')
// List archived
const { data: archived } = await docman.listHtmlTemplates({ archived: true })PDF Templates
Upload an AcroForm PDF (a PDF with fillable form fields). Docman extracts the field names automatically and fills them at generation time using the data map.
Upload
import { readFileSync } from 'node:fs'
const pdfBuffer = readFileSync('./acord25.pdf')
const template = await docman.uploadPdfTemplate(pdfBuffer, {
name: 'ACORD 25 — Certificate of Insurance',
description: 'Standard liability certificate',
})
// template._id — use as templateId when generating
// template.fields — extracted AcroForm fields
// template.pages — page countInspect fields
// Get the AcroForm field names to know what keys to pass in data{}
const fields = await docman.getPdfTemplateFields('tmpl_abc123')
// [
// { name: 'InsuredName', type: 'text', required: false },
// { name: 'PolicyNumber', type: 'text', required: false },
// { name: 'EffectiveDate', type: 'text', required: false },
// ]Download the original PDF
const url = await docman.getPdfTemplateDownloadUrl('tmpl_abc123')
// Presigned S3 URL (1-hour TTL)Update (replace file or metadata)
// Metadata only
await docman.updatePdfTemplate('tmpl_abc123', { name: 'ACORD 25 v2' })
// Replace the PDF file (re-extracts fields)
const newBuffer = readFileSync('./acord25-v2.pdf')
await docman.updatePdfTemplate('tmpl_abc123', { name: 'ACORD 25 v2' }, newBuffer)Template Groups
A template group is an ordered list of HTML and PDF templates that Docman renders and merges into a single output PDF. Useful for policy packages that always include the same set of documents in order.
// Create a group
const group = await docman.createTemplateGroup({
name: 'Policy Package — GL',
templates: [
{ templateId: 'tmpl_decpage', templateType: 'html' },
{ templateId: 'tmpl_acord25', templateType: 'pdf' },
{ templateId: 'tmpl_endorsements', templateType: 'html' },
],
})
// Generate the full package in one call
const result = await docman.generate({
templateIds: [group._id],
data: {
insuredName: 'Acme Corp',
policyNumber: 'POL-00123',
InsuredName: 'Acme Corp', // AcroForm field key (case-sensitive)
PolicyNumber: 'POL-00123',
},
name: 'GL Policy Package — Acme Corp',
})Managing Documents
List & Get
const { data: docs, meta } = await docman.listDocuments({ page: 1, limit: 20 })
const doc = await docman.getDocument('doc_xyz789')
// doc.name, doc.version, doc.pages, doc.sizeBytes
// doc.shareToken, doc.deliveryMode
// doc.sourceTemplates, doc.templateDataDownload URL
// Presigned S3 URL, 1-hour TTL
const url = await docman.downloadUrl('doc_xyz789')Versions
const versions = await docman.getVersions('doc_xyz789')
// [{ _id, version, pages, sizeBytes, createdAt, createdBy }]Regenerate
Re-renders the document using the stored template IDs and original templateData. Creates a new version. Useful for reprocessing after a template change.
const result = await docman.regenerate('doc_xyz789')
// result.documentId, result.version, result.downloadUrl, result.publicUrlPublic share URL
Every managed document gets a permanent public share URL at /d/:shareToken. This URL never expires and does not require authentication. Click tracking is recorded.
// result.publicUrl from generate() or regenerate()
const shareUrl = `https://docman.tawa.insureco.io/d/${doc.shareToken}`PDF Operations
These operations work on raw PDF buffers — no templates required.
Merge
import { readFileSync } from 'node:fs'
const a = readFileSync('./part1.pdf')
const b = readFileSync('./part2.pdf')
const c = readFileSync('./part3.pdf')
// Merges in order — returns combined PDF as Buffer
const merged = await docman.merge([a, b, c])Split
const pdf = readFileSync('./policy.pdf')
const parts = await docman.split(pdf, [
{ start: 1, end: 3 }, // pages 1-3
{ start: 4, end: 6 }, // pages 4-6
])
// parts[0].buffer — PDF for pages 1-3
// parts[0].range — { start: 1, end: 3 }
// parts[0].index — 0
// parts[0].sizeBytesPage count
const pdf = readFileSync('./document.pdf')
const pages = await docman.pageCount(pdf)
// 12Testing
Import MockDocmanClient from @insureco/docman/testing. It is a full in-memory implementation — no network calls, no S3. Call reset() between tests.
import { MockDocmanClient } from '@insureco/docman/testing'
import { describe, it, expect, beforeEach } from 'vitest'
describe('policy generation', () => {
const docman = new MockDocmanClient()
beforeEach(() => docman.reset())
it('generates a declarations page', async () => {
const template = await docman.createHtmlTemplate({
name: 'Declarations',
html: '<p>{{insuredName}} — {{policyNumber}}</p>',
})
const result = await docman.generate({
templateIds: [template._id],
data: { insuredName: 'Acme Corp', policyNumber: 'POL-00123' },
name: 'Test Declarations',
})
expect(result.documentId).toBeTruthy()
expect(result.pages).toBe(1)
expect(result.publicUrl).toMatch(//d//)
})
it('stores the document in memory', async () => {
const template = await docman.createHtmlTemplate({
name: 'T',
html: '<p>{{name}}</p>',
})
const { documentId } = await docman.generate({
templateIds: [template._id],
data: { name: 'test' },
name: 'doc',
})
const doc = await docman.getDocument(documentId)
expect(doc.name).toBe('doc')
expect(docman.getDocuments().size).toBe(1)
})
})Test helpers
| Method | Returns | Use for |
|---|---|---|
reset() | void | Clear all in-memory state between tests |
getDocuments() | ReadonlyMap<string, DocmanDocument> | Assert on generated documents |
getHtmlTemplates() | ReadonlyMap<string, HtmlTemplate> | Assert on created templates |
getPdfTemplates() | ReadonlyMap<string, PdfTemplate> | Assert on uploaded PDF templates |
Error Handling
import { DocmanClient, DocmanError } from '@insureco/docman'
try {
const result = await docman.generate({ /* ... */ })
} catch (err) {
if (err instanceof DocmanError) {
// err.statusCode — HTTP status (400, 401, 404, 500, …)
// err.code — 'NOT_FOUND' | 'VALIDATION_ERROR' | 'AUTH_ERROR' | …
// err.message — human-readable message
}
}| Code | HTTP | Meaning |
|---|---|---|
CONFIG_ERROR | 500 | DOCMAN_URL not set — missing internalDependency |
AUTH_ERROR | 401 | Failed to obtain Bio-ID access token |
VALIDATION_ERROR | 400 | Invalid request body or insufficient buffers for merge/split |
NOT_FOUND | 404 | Template, group, or document not found |
NETWORK_ERROR | 0 | Network timeout or connection failure (after retries) |
REQUEST_ERROR | varies | Generic upstream error |
The SDK retries automatically on network errors and 5xx responses (default 2 retries, exponential backoff up to 5 seconds). Override with retries: 0 to disable.
git.insureco.io/insureco/docman-sdk — SDK source code and changelog. See also: Object Storage, InsureRelay, Septor.