What this is about
The payload is the data a template inserts into the document: customer name, line items, date. Your application puts it together. CRMS does not calculate, loads nothing and follows no references. It only inserts what you send.
That is why CRMS checks the payload before the template sees it. There are two checks:
- The structure, for every payload: at most two levels and only plain values.
- The declared fields: a template can state which fields it expects, with type and whether they are required. CRMS checks the payload against these declarations.
A valid payload
{
"title": "Inspection hall 3",
"approved": true,
"tags": ["fire safety", "escape routes"],
"customer": { "name": "ACME", "city": "Cologne", "phones": ["0221 1234"] },
"items": [
{ "label": "Fire extinguisher checked", "amount": 480.00 },
{ "label": "Emergency exit clear", "amount": 120.50 }
],
"due": "2026-10-17"
}
| Value | Form | Levels |
|---|---|---|
title, approved, due | plain value | 1 |
tags | list of plain values | 1 |
customer | object with plain values or lists of plain values | 2 |
items | list of objects with plain values or lists of plain values | 2 |
Plain values are text, numbers, true/false, dates and points in time. null is allowed everywhere. Embedded, you may also send Java date values: LocalDate, OffsetDateTime, ZonedDateTime, Instant. Over JSON, dates come as ISO text, see below.
Arbitrary Java objects are not allowed. An object would bring its methods into the template, and that is exactly what the sandbox is meant to prevent.
The structure rules
| Where is the value? | What is it? | Result |
|---|---|---|
| top | plain value or null | allowed |
| top | list of plain values | allowed |
| top | object | allowed, its values are checked on level 2 |
| top | list of objects | allowed, each object is checked on level 2 |
| top | list of lists | depth-exceeded |
| level 2 | plain value or list of plain values | allowed |
| level 2 | object or list with objects or lists | depth-exceeded |
| – | other object or infinite number | value-type-unsupported |
| level 2 | key empty or not text | key-invalid |
Before anything else, CRMS estimates the size of the payload. If it is larger than the installation’s limit (default 5 MiB, CODAMAI_CRMS_PAYLOAD_MAX_BYTES), only one violation comes back: field payload, rule payload-too-large|<limit>. The limit also applies to an SVG in the payload.
The declared fields
A revision of a template can declare its fields. Each declaration has four parts:
| Part | Meaning | Example |
|---|---|---|
path | where the field sits: a name, object.name or list[].name. At most two levels, only the first may be a list of objects | customer.name, items[].amount |
type | STRING, NUMBER, BOOLEAN, DATE or DATETIME | NUMBER |
required | whether the field must be present and not null | true |
list | whether the field is a list of values of this type | false |
How you declare fields in a template is described in How a template is built. For the check:
- Undeclared values are allowed. A template may use more than it declares. Only the structure rules apply to them.
DATEmatches a date or ISO text like"2026-10-17".DATETIMEmatches a point in time or ISO text with a time zone offset, for example"2026-10-17T08:30:00+02:00"or"…Z". Without an offset it is a violation.NUMBERmatches any number, not text like"480".- For
items[].amount, CRMS checks every element of the list on its own.
| Rule | Meaning |
|---|---|
required | a required field is missing or null |
type|string, type|number, type|boolean, type|date, type|datetime | the value does not have the declared type |
list-expected | a declared list, or list of objects, is not a list |
object-expected | a declared object, or a list element, is not an object |
How violations come back
CRMS collects all violations and answers once: 422 with the key payload-rejected and the list violations. Each violation names the path of the value and a rule. A value from your request is never sent back.
POST /crms/render
{
"template": "audit-report", "format": "PDF", "mode": "PREVIEW",
"data": {
"items": [
{ "label": "Fire extinguisher", "amount": "480" },
{ "label": "Emergency exit", "amount": 120.5,
"detail": { "note": "too deep" } }
],
"due": "17.10.2026"
}
}HTTP 422
{
"messageKey": "payload-rejected",
"code": "422",
"layer": "crms",
"violations": [
{ "field": "items[1].detail", "rule": "depth-exceeded" },
{ "field": "items[0].amount", "rule": "type|number" },
{ "field": "due", "rule": "type|date" }
]
}The structure violations come before the violations of declared fields. Your frontend translates the rule keys, see Errors and message keys. You can also check a payload without rendering, with POST /crms/render/validate, see The render request.
What the template sees afterwards
The template gets a copy of the checked payload. CRMS turns every field declared as DATE or DATETIME into a real date value, even if it came as ISO text. So the template can format it in the document’s language and time zone. A calendar date counts as midnight in the document’s time zone. More in Language and time zone.