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:

ConceptDescription
HTML templateHandlebars markup rendered to PDF via Puppeteer. Full CSS, landscape/portrait, dynamic content.
PDF templateExisting AcroForm PDF with fill-in fields. Field values are injected via pdf-lib.
Template groupOrdered list of HTML and/or PDF templates merged into one output document.
Managed deliveryDocument stored in S3. Returns presigned download URL (1-hour TTL) and permanent public share URL.
Passthrough deliveryRaw 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}
Service URL: Docman is available at 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_URL

2. Install the SDK

npm install @insureco/docman

3. 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.sizeBytes
Template groups: Pass a template group ID in templateIds 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 generating

List / 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 count

Inspect 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.templateData

Download 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.publicUrl

Public 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].sizeBytes

Page count

const pdf = readFileSync('./document.pdf')
const pages = await docman.pageCount(pdf)
// 12

Testing

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

MethodReturnsUse for
reset()voidClear 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
  }
}
CodeHTTPMeaning
CONFIG_ERROR500DOCMAN_URL not set — missing internalDependency
AUTH_ERROR401Failed to obtain Bio-ID access token
VALIDATION_ERROR400Invalid request body or insufficient buffers for merge/split
NOT_FOUND404Template, group, or document not found
NETWORK_ERROR0Network timeout or connection failure (after retries)
REQUEST_ERRORvariesGeneric 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.

Source: git.insureco.io/insureco/docman-sdk — SDK source code and changelog. See also: Object Storage, InsureRelay, Septor.