What this is about
CRMS offers its functions over HTTP, all under the path /crms. This is done by the CRMS HTTP interface. It is an adapter: it reads a request, calls the matching function of a CRMS module and writes the response. It has no business logic of its own.
Where the routes run
The routes exist where CRMS runs embedded, meaning in the same process as the application, and the CRMS HTTP interface is on the classpath. The separate CRMS service always brings it along.
| Embedded with HTTP interface | Embedded without HTTP interface | Remote | The CRMS service itself | |
|---|---|---|---|---|
| CRMS runs | in the application's process | in the application's process | as a separate service | as a separate service |
| HTTP interface | in the build | not in the build | forbidden, the start fails | always included |
| Routes /crms/** | yes, behind the application's security chain | no, the application only calls CRMS in code | no, the application uses crms-client | yes |
In the remote operating mode the application would otherwise pass the CRMS service on under its own routes, behind its own security. That is why the start refuses the HTTP interface there. More on the operating modes in Embedded or standalone.
The path of a request
-
CIASSecurity chainIs the token valid? CIAS determines person, tenant and roles.↳ no 401
-
CRMSReading the requestIs someone signed in? Is the body JSON, not too large, are all fields known and of the right type?↳ no 401
crms-authentication-required, 400malformed-json, 413, 415 or 422validation-failed -
CRMSRole and visibilityDoes the person have the required role, and can they see the object?↳ no 403 or 404
- The module runs the function, the response goes back
The HTTP interface reads no token itself. The person comes through CIAS, see The roles.
Rendering
| Route | Body | Response | Who |
|---|---|---|---|
POST /crms/render | the render request | the document’s bytes | every signed-in person, for templates they can see |
POST /crms/render/validate | the render request | 200 {"violations": [...]}, empty means valid | as above |
GET /crms/capabilities | – | what this installation offers | CRMS checks no person, the application’s security chain applies |
Rendering needs no role of its own. It is enough to be signed in and to be allowed to see the template. Which template a request finds is described in How a template is found, the fields of the request in The render request.
POST /crms/render
Content-Type: application/json
{ "template": "audit-report", "format": "PDF", "mode": "FINAL",
"reference": "audit-4711", "locale": "de-DE",
"data": { "customer": { "name": "ACME" } } }200
Content-Type: application/pdf
Content-Disposition: inline; filename="audit-report.pdf"
Crms-Template-Key: audit-report
Crms-Template-Scope: TENANT ← which edition won
Crms-Template-Variant: de-DE ← which language variant
Crms-Template-Revision: 3f1c… ← which revision of the template
Crms-Template-Revision-Hash: sha-256:…
Crms-Document-Revision: 9a7e… ← with FINAL only
X-Content-Type-Options: nosniff
<the bytes of the PDF>/crms/render/validate checks the same request without rendering. GET /crms/capabilities names the formats, the default PDF profile, the admitted scopes, the languages, whether final documents are stored (documentRevisions), whether transient documents are allowed (transientDocuments), and the limits (timeout, maxOutputBytes, maxPayloadBytes, maxAttachmentCount, maxAttachmentBytes).
Templates
A template editor uses these routes. Each route calls exactly one function of the template module. See means: the template belongs to the system, to your tenant or to you. Write means: in addition, the write role of the edition’s scope. Review means: the role of the current approval stage.
| Route | Body | Response | Who |
|---|---|---|---|
GET /crms/templates | – | the editions you can see | see |
POST /crms/templates | key, outputType, scope, defaultLocale | 201 the edition, with Location | write |
GET /crms/templates/{edition} | – | the edition | see |
GET /crms/templates/{edition}/variants | – | its variants, the default language first | see |
POST /crms/templates/{edition}/variants | locale | 201 the variant | write |
GET /crms/variants/{variant}/draft | – | the draft, or 204 without a draft | write |
PUT /crms/variants/{variant}/draft | expectedVersion, content | the saved draft | write |
GET /crms/variants/{variant}/draft/assets/{name} | – | an asset of the draft, as a download | write |
POST /crms/revisions/{revision}/draft | expectedVersion | the draft with the content of this revision | write |
POST /crms/variants/{variant}/submit | reason (optional) | the new revision | write |
POST /crms/revisions/{revision}/approve | – | the revision afterwards | review |
POST /crms/revisions/{revision}/reject | reason | the revision afterwards | review |
POST /crms/variants/{variant}/rollback | target, reason (optional) | the new, published revision | write |
POST /crms/variants/{variant}/retire | reason (optional) | 204 | write |
GET /crms/variants/{variant}/revisions | – | all revisions, oldest first | see |
GET /crms/variants/{variant}/trail | – | every entry of the chain, with all hashes | see |
POST /crms/variants/{variant}/verify | – | {"intact": true} or {"intact": false, "brokenAt": 7} | see |
GET /crms/revisions/{revision}/content | – | the revision’s content, assets described | see |
GET /crms/revisions/{revision}/assets/{name} | – | an asset of the revision, as a download | see |
A few rules apply to all template routes:
- A draft is always saved whole.
contentreplaces the old content, withsource,fieldsand allassetsas Base64. Base64 is a notation that writes bytes as text. expectedVersionis the version of the draft your change builds on,0for none. If someone else saved in the meantime, CRMS answers 409template-draft-changed.- Whether a reason is required is decided by the installation. When rejecting, it is always required.
- The body is limited, 16 MiB by default, adjustable through
CODAMAI_CRMS_REST_TEMPLATE_MAX_BYTES. Above that: 413request-too-large|<limit>. - Assets come as a download (
Content-Disposition: attachment) withX-Content-Type-Options: nosniffandContent-Security-Policy: sandbox. This way an SVG cannot run scripts on the application’s page in the browser.
The whole flow from draft to publication is described in The life cycle of a template and Approval.
Documents
These routes work with final documents. The archive checks the role before it looks anything up, the same as for an embedded call.
| Route | Role | Response |
|---|---|---|
GET /crms/documents?template=…&scope=…&reference=… | read | the revisions of the chain, oldest first |
GET /crms/documents/{revision} | read | the document’s bytes, inline |
POST /crms/documents/{revision}/verify | read | {"ok": true} or {"ok": false, "problem": "<key>"} |
POST /crms/documents/{revision}/legal-hold | legal hold | 204 |
POST /crms/documents/{revision}/legal-hold/release | legal hold | 204 |
POST /crms/documents/{revision}/erase | erase | 204 |
- When listing, all three parameters are required. If one is missing, you get 422
validation-failedwith the rulerequired. - The three changing routes need a body
{"reason": "litigation-hold"}. The reason is a key in kebab-case, not free text, because it ends up in a chain that is never deleted. Free text gives 422validation-failed. - Reading returns the whole description of the revision in headers:
Crms-Document-Revision,Crms-Document-Number,Crms-Document-Format,Crms-Document-Stored-At,Crms-Document-Retain-Until,Crms-Document-Legal-Hold,Crms-Document-Content-Hashand theCrms-Template-*headers as when rendering. - The archive always works in the tenant of the person asking and records them as the actor.
What verify, legal hold and erase mean in practice is described in Verify and export and Retention, legal hold and erasure.
Rules for all routes
| Rule | What it means |
|---|---|
| Known fields only | An unknown field in the body gives the rule not-allowed. Duplicate keys and text after the JSON object give malformed-json. |
| No silent conversion | "5" is not a number. A value with the wrong JSON type is invalid. null counts as a missing field. |
| One spelling per ID | IDs in the path are UUIDs in the usual long form, in lower or upper case. Short forms like 1-1-1-1-1 give invalid, with the name of the path variable as the field. |
| Text instead of numbers and objects | Times are ISO-8601 text, languages are language tags like de-DE, hashes are <algorithm>:<hex>. |
| Compressed body | Every route accepts Content-Encoding: gzip. The limit applies to the unpacked bytes. Any other encoding gives 415 content-encoding-unsupported. |
| Documents protected | Every document response carries X-Content-Type-Options: nosniff, HTML also Content-Security-Policy: sandbox. A script in a template therefore does not run with the reader’s session. |
| Whole responses | A document always comes in one piece, not streamed. This way even a late error can still come as an error answer. |