Developer dictionary

Quick reference for integrating and operating DOCCRM.

Overview

This service merges Microsoft Dataverse (Dynamics 365) data into Word (.docx) templates using FetchXML, producing Word (.docx) via the Open XML SDK. Integrations call the REST API with a template name and a record GUID only — they do not send FetchXML in the request body.

FetchXML for each template (optional header query + zero or more table queries) is defined and stored in the portal per user and per .docx file. The portal also holds per-user Dataverse connection settings, uploaded templates, license keys, and activity logs.

Architecture

End-to-end flow for POST /api/document/generate:

  1. Authenticate the caller (X-Api-Key or signed-in session) and resolve their Dataverse connection.
  2. Resolve the uploaded template file name (case-insensitive match to GET /api/document/templates).
  3. Load that user’s template fetch mapping from the database: optional header FetchXML + JSON list of table FetchXML strings (table order = Word table order in the document).
  4. Substitute the literal token {recordid} everywhere it appears in those strings with the request’s recordId (canonical GUID format).
  5. Query Dataverse: single-row header query and/or one multi-row query per configured table.
  6. Merge into the .docx (simple replacement or table-aware path). PDF is not generated in-app; convert externally if needed.

Ad-hoc FetchXML debugging does not use stored mappings: POST /api/document/test-fetchxml runs a raw query and returns JSON (same auth and Dataverse connection as the user).

Legacy integrations that posted FetchXML in the body or called generate-by-id URLs receive 410 Gone; see HTTP errors.

Glossary

Dataverse
Microsoft’s data platform for Dynamics 365; query it with FetchXML from this API.
FetchXML
XML query language for Dataverse. For generation, queries are not passed in the API body; they are stored per user and template under Portal → Templates → Configure FetchXML. Use the token {recordid} in conditions (or anywhere the primary row GUID is required); the server replaces it before execution. Use POST /api/document/test-fetchxml for ad-hoc raw queries during development.
Template fetch mapping
Database row per portal user + template file name: optional header FetchXML, plus an ordered list of table FetchXML strings (same semantics as a JSON array of queries, one per Word table). Unique per user and file name; must exist before POST /api/document/generate can succeed for that template.
recordId
Primary Dataverse row GUID sent in the generate request. Substituted for {recordid} in all stored queries for that generation. Typically the “main” entity for the document (e.g. invoice, case, account).
Template
A .docx file with text placeholders (and optional Word tables). Signed-in users and API-key calls generate from files in UserTemplates/{userId}/ (uploaded in Portal → Templates). The shared Templates/ folder may be used for legacy anonymous access when enabled.
Placeholder
Token in the document such as {fieldname} or {account.name}, replaced at generation time.
_formatted
Suffix for lookup and option-set display values, e.g. {ownerid_formatted}.
Header FetchXML
Single-row query for “document header” fields (letterhead, invoice header, etc.).
Table FetchXML
Query returning multiple rows; each row becomes a repeated table line in Word.
License key
String assigned to a portal user; required for generation (except admin). Validated by POST /api/license/validate for the Word add-in.
API key
Per-user secret shown in the portal; send as header X-Api-Key for REST calls.
API authentication
All /api/* endpoints require a portal identity: send X-Api-Key (from the portal) or use a signed-in browser session. Dataverse access uses that user's Portal → D365 settings only.

Authentication

MethodUsage
X-Api-Key: <key> Identifies a portal user for API calls (same rights as that user’s session).
Cookie (portal) Browser login at /Account/Login — for UI and same-origin API calls from the browser.

Dataverse access always uses the connection saved for that user under Portal → D365 settings.

REST API (summary)

Base URL: your site origin (e.g. https://localhost:7001). Prefer HTTPS.

MethodPathPurpose
POST/api/document/generatetemplateName + recordId → JSON metadata (default) or .docx download when returnFile: true (Open XML only; no PDF)
POST/api/document/test-fetchxmlDebug: raw fetchXml in body → JSON (does not use template mapping)
GET/api/document/templatesList uploaded .docx file names for the authenticated user
POST/api/license/validateAdd-in license check
POST/api/metadata/connectTest org connection (add-in)
GET/api/metadata/entitiesBuilt-in entity metadata

Generate request body

JSON only. FetchXML is never included here.

{
  "templateName": "InvoiceTemplate.docx",
  "recordId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "outputFileName": "OptionalBaseName",
  "outputFormat": "doc",
  "returnFile": false
}

templateName and recordId are required. Set returnFile to true to return the Word file as the HTTP body (with Content-Disposition attachment). Default false returns JSON (file name, size, annotation status, etc.) and still creates the Dataverse note when configured. outputFormat must be doc (default). pdf returns 400 with code pdf_not_supported—only WordprocessingML is produced (Open XML SDK).

Convert the downloaded .docx to PDF externally (Word print, LibreOffice, automation) if you need PDF.

Placeholders

  • Simple: {fieldname} — must match a column alias in your FetchXML.
  • Linked entity: {alias.field} — use the same alias as in FetchXML link-entity.
  • Formatting (templates with tables): {{field|format:yyyy-MM-dd}}, {{field|uppercase}}
  • Images: insert a picture; set alt text to {fieldname}; supply image bytes from Dataverse in FetchXML.

Conditionals (Word)

Paragraph-level blocks evaluated against header data after replacement:

  • {{#if fieldname}}{{/if}} — truthiness
  • {{#if amount >= 100}} — comparisons: = != < > <= >=
  • {{#if name contains Ltd}} — substring match

Tables & FetchXML

  • Word: one header row + one template row (last row) with placeholders; rows are cloned per Dataverse row returned for that table’s query.
  • Portal mapping: optional header FetchXML (single row) plus one table FetchXML per Word table, in top-to-bottom document order (first list entry = first table in the file). Header-only templates use no table queries.
  • Each table query should return the columns your template row placeholders need; use {recordid} in filters to scope lines to the same primary record (e.g. invoice lines for the invoice in recordId).
  • Zero rows: a table whose query returns no rows may be removed from the output when the generator is configured to do so.

Portal & license

Operational setup mirrors the architecture: Dataverse credentials and template queries are owned per portal user.

D365 connection
Each user saves app registration / connection details under Portal (Dataverse settings). All generate and test-fetchxml calls use that user’s connection.
Templates
Upload .docx files; each file name must match templateName in API calls. Use Configure FetchXML to create or edit the stored mapping (header + table list). At least one non-empty query is required before save.
Admin
Creates users, sets license key and expiry, optional Admin role.
Hits
Successful document generations increment Total generation hits for the user.
Activity log
Recent API actions (document generation, test FetchXML) per user.

HTTP errors (typical)

CodeMeaning
400Bad JSON body, missing templateName / recordId, empty mapping, FetchXML without {recordid} (fetchxml_recordid_token_missing), or outputFormat: pdf (pdf_not_supported — API outputs .docx only)
401Missing or invalid identity — send X-Api-Key or sign in
403License missing or expired
404Template missing, no portal mapping for that template, or FetchXML returned no data
410Removed endpoints: old POST .../generate-with-table or GET .../generate-by-id/... — use POST /api/document/generate and portal mapping
503Dataverse connection not configured or failing for this user

For full template syntax see Templates/TEMPLATE_GUIDE.md in the repository.