What this is about
A template has several parts that are easy to mix up. The key names the template, for example audit-report. Under this key there can be several editions: one of the platform, one per tenant, one per person. Every edition has one variant per language. And every variant has a draft to work in and a chain with all its revisions.
The picture
flowchart TB
K["Key<br/>audit-report"]
K --> E1["Edition SYSTEM<br/>output type DOCUMENT<br/>default language de"]
K --> E2["Edition TENANT<br/>tenant acme"]
K --> E3["Edition USER<br/>person anna"]
E1 --> V1["Variant de<br/>(default)"]
E1 --> V2["Variant en"]
V1 --> D1["Draft<br/>saved freely"]
V1 --> C1["Chain<br/>revision 1, 2, 3 …"]
The parts
| Part | What it is | Example |
|---|---|---|
| Key | the stable name of the template, the same in every scope and every language | audit-report |
| Output type | what the template produces: DOCUMENT, WORD or TABLE. It decides which formats are possible | DOCUMENT → PDF, HTML |
| Edition | the template of one owner: the platform, a tenant or a person | the system edition of audit-report |
| Variant | an edition in one language. One of them is the default variant | de, de-CH, en |
| Draft | the workspace of a variant. It is not part of the chain | — |
| Revision | a submitted state: source, fields, assets. Immutable | revision 3 |
The key
A key is kebab-case: lowercase letters, digits and hyphens. It starts with a letter and has at most 128 characters. audit-report is valid, Audit_Report is not.
The scope
The scope says who an edition belongs to. There are three.
| SYSTEM | TENANT | USER | |
|---|---|---|---|
| Belongs to | the platform | one tenant | one person in their tenant |
| Who sees it | every signed-in person | the people of this tenant | only this person |
| Who writes it | the platform administrators CIAS knows | whoever has the role crms-template-author | the person themselves, with the role crms-template-personal |
| Where it is stored | system database | the tenant's database | the tenant's database |
The role names crms-template-author and crms-template-personal are the defaults. A project can change them, see Roles. An installation can also switch scopes off (CODAMAI_CRMS_TEMPLATE_SCOPES). Creating an edition in a scope that is switched off ends with 422 template-scope-not-admitted|<scope>.
A person without a tenant has no tenant edition. If they try to create a TENANT edition, CRMS answers 422 template-tenant-required.
Edition and output type
There is exactly one edition per key and owner. A second one ends with 409 template-edition-exists.
The output type belongs to the key. If a tenant creates an edition of audit-report, it must have the same output type as the system edition. Otherwise CRMS answers 422 template-output-type-mismatch. The check only looks upward: person against tenant and system, tenant against system.
POST /crms/templates
{
"key": "audit-report",
"outputType": "DOCUMENT",
"scope": "TENANT",
"defaultLocale": "de"
}201 Created
{
"id": "6f1c…",
"key": "audit-report",
"outputType": "DOCUMENT",
"scope": "TENANT",
"defaultLocale": "de",
"createdBy": "anna",
"createdAt": "2026-10-17T08:12:00Z"
}Together with the edition, CRMS creates its default variant in the language defaultLocale.
The variant
You add another language with POST /crms/templates/{edition}/variants and {"locale": "en"}. There is one variant per language; a second one ends with 409 template-variant-exists. The language is a BCP 47 language tag, so de, de-CH or en. How CRMS picks the right variant for a request is described in How CRMS finds the template.
The draft
Every variant has one draft. You save it as often as you like, always as a whole. So that two people do not silently overwrite each other, every save names the version it is based on (expectedVersion, 0 for the first draft). If someone else saved in the meantime, CRMS answers 409 template-draft-changed.
What a revision holds
A draft and a revision have the same content: source, fields and assets.
PUT /crms/variants/{variant}/draft
{
"expectedVersion": 2,
"content": {
"source": "<p>${title}</p>",
"fields": [
{ "path": "title", "type": "STRING", "required": true },
{ "path": "items[].amount", "type": "NUMBER" }
],
"assets": [
{ "name": "logo.png", "contentType": "image/png",
"content": "<base64>" }
]
}
}{ "variant": "…", "version": 3, "updatedBy": "anna",
"updatedAt": "…", "content": { … } }| Part | Content | Rules |
|---|---|---|
source | the FreeMarker source, see The template language | must not be empty: template-source-empty |
fields | which fields the template expects in the payload | see below |
assets | files the template uses: logo, font, style sheet | see below |
Fields
A template has no JSON schema. It declares the fields it needs. Every declaration has a path, a type and two switches.
| Entry | Values | Meaning |
|---|---|---|
path | title, customer.name, items[].amount | where the field sits in the payload, at most two levels |
type | STRING, NUMBER, BOOLEAN, DATE, DATETIME | what the field holds |
required | true / false (default) | whether the payload must have the field |
list | true / false (default) | whether the field is a list of values of the type |
items[] means: items is a list of objects, and each one has a field amount. Only the first level may be such a list. A third part like a.b.c ends with template-field-too-deep, a broken path with template-field-path-invalid. If two declarations contradict each other, for example the same path twice or customer once as a value and once as an object, CRMS answers template-field-conflict|<position>. How CRMS checks the payload against these fields is described in The payload check.
Assets
An asset is a file that belongs to the revision, not to the template. So a new logo never changes an older revision.
- The name is a plain file name: letters, digits,
.,_,-, no path separator, no leading dot, at most 128 characters. Otherwisetemplate-asset-name-invalid. - Allowed types:
image/png,image/jpeg,image/gif,image/svg+xml,image/webp,font/ttf,font/otf,font/woff,font/woff2,text/css. Otherwisetemplate-asset-type-not-allowed. - An asset without content is
template-asset-empty, a name used twice istemplate-asset-duplicate|<position>.
The template names an asset as asset:logo.png. More in The template language.
Where the rows are stored
System editions are stored in the system database. Tenant and person editions are stored in their tenant’s database. So a tenant’s templates stay with its other data. If an installation has only one database, it is the same one. More in Database.
Everything except the draft is only inserted, never changed or deleted. The draft is the only row that changes.