What this is about
When a request goes wrong, CDMS answers with an error status (400 and above) and an error response. The error response is a small JSON object without data. It tells you what happened, in a form a program can evaluate. CIAS answers in exactly the same shape.
The response contains no sentence for people, but a key. Your frontend turns it into the text the user sees, in the user’s language.
An error response, labeled
POST /api/rest/hr/employee/read/5a2b…
{ "response": ["name"] }{
"messageKey": "missing-permission|hr-employee-read",
└── key ──────┘ └── detail ──┘ ← branch on this
"code": "403", ← status as text
"layer": "validation" ← layer, for debugging only
}| Field | always present? | Meaning | in the client |
|---|---|---|---|
messageKey | yes | fixed key, often with details after | | branch on this |
detail | only in the server’s debug mode | technical text of the cause | never show it, never evaluate it |
code | yes | the HTTP status as text, e.g. "422" | same as the status of the response |
layer | yes | layer in which the error occurred | for debugging only |
violations | only for validation errors | all violated field rules with path | map them to the form fields |
id | only in the LENIENT special case | id of the created object | keep it, read later |
stacktrace | only in the server’s debug mode | technical call stack | never evaluate it |
The messageKey in detail
The messageKey has the form key|detail|detail. Before the first | is the key, i.e. the kind of problem. After it, depending on the error, come details such as a field name, a model name, a role name or an id. Values you send in fields or filters are never in it.
messageKey | key | details |
|---|---|---|
missing-permission|hr-employee-read | missing-permission | the missing role |
invalid-value|data.amount | invalid-value | path of the field in the body |
wrong-value-in-where|amount | wrong-value-in-where | the field of the filter, never the value |
recursive-create-not-allowed|employees | recursive-create-not-allowed | the relation |
not-found|Person|5a2b… | not-found | model and id |
validation-failed | validation-failed | none, the details are in violations |
optimistic-lock-conflict | optimistic-lock-conflict | none |
This is how you get the key in the client:
const [key, ...details] = body.messageKey.split('|');
// key = "missing-permission"
// details = ["hr-employee-read"]switch (key) {
case 'missing-permission': … // role missing
case 'not-found': … // object gone or invisible
case 'validation-failed': … // violations into the form
default: … // general message
}All CDMS keys are written in lowercase with hyphens, such as tenant-required or missing-id. Compare the key character by character, exactly as it is.
Three shapes of error bodies
Not every refusal comes from CDMS itself. The CIAS filter chain runs first. It answers in the same shape, and its key starts with cias.authentication.. Some refusals have no body at all.
messageKeymessageKey,code,layer- every refusal from the role check on
- also 400 for a request that cannot be read
messageKey, key cias.authentication.…{"messageKey": "cias.authentication.tenant-unresolved", "code": "403", "layer": "api"}- the tenant cannot be determined, is not served or is missing
- the same shape as CDMS
- 401: token invalid or expired, header
WWW-Authenticate - 403: no token at all
- 404: the path does not exist
More about the refusals of the filter chain in Access without a token and 401, 403 or 404?.
Validation: violations
Only a validation error has a list violations. Each entry names the field with its path and the violated rule:
POST /api/rest/crm/customer/create
{ "data": { "name": "" }, "response": ["id"] }{
"messageKey": "validation-failed",
"code": "422",
"layer": "API",
"violations": [
{ "field": "name", "rule": "cannot-be-empty" },
{ "field": "email", "rule": "cannot-be-null" }
]
}How to map the entries to the form fields is described in Getting validation errors into the form. Which rules exist: Validation.
The special case: 200 in error shape
After a create in LENIENT mode, creating can succeed while only the read-back fails. Then you get status 200, but a body in error shape with the id of the new object:
{
"messageKey": "create-succeeded-read-failed",
"code": "200",
"layer": "system",
"id": "5a2b…"
}The object exists.
Keep the id and read it later,
do NOT create it again.See Create and read back: STRICT or LENIENT.
How a client evaluates it
-
1ClientStatus 2xx?
-
2Clientyes, and
messageKeyiscreate-succeeded-read-failed→ created, keep theid -
3Clientyes, otherwise → use
data -
4Clientno → read the body if there is one. Does it have a
messageKey? -
5Clientyes → evaluate the key before the first
|, for 422 alsoviolations. If it starts withcias.authentication., the filter chain has already refused, for example because of the tenant -
6Clientno → decide by status: 401 renew the token, 403 without a token sign in, otherwise a general message
Pitfalls
Where to go next
- What each status means: Map of status codes
- The three codes that are often mixed up: 401, 403 or 404?
- The envelope of a success response: The response format