Zum Inhalt springen

Payloads und Antwortformate

Kurzantwort: Jede schreibende Anfrage schickt ein WritePayload mit den Feldern data (das Objekt) und response (welche Felder zurückkommen sollen). Lesende Anfragen schicken ein ReadPayload (response, exclude), Listen zusätzlich parameter (Filter, Sortierung, Pagination). Antworten sind SingleResponse (ein Objekt) oder QueryResponse (Liste plus meta).


classDiagram
    class ReadPayload {
        +List response
        +List~String~ exclude
    }
    class RestListPayload {
        +ListSearchParameter parameter
    }
    class ModelResponse {
        +String field
    }
    class WritePayload~T~ {
        +T data
        +CreateReadMode createReadMode
    }
    class PatchPayload {
        +HashMap data
    }
    class AbstractPayload {
        +UUID id
        +UUID _internalId
    }
    ReadPayload <|-- RestListPayload
    RestListPayload <|-- ModelResponse
    ReadPayload <|-- WritePayload
    ReadPayload <|-- PatchPayload
    AbstractPayload <|-- CreatePayload
    AbstractPayload <|-- UpdatePayload
Payloadbenutzt für
ReadPayloadPOST /read/{id}
RestListPayloadPOST /query, POST /{id}/history
WritePayload<CreatePayload>POST /create
WritePayload<UpdatePayload>PUT /update/{id}
PatchPayloadPATCH /update/{id}
ModelResponseverschachtelter Response Request innerhalb von response
POST /api/rest/crm/customer/create
{
"data": {
"name": "Muster GmbH",
"email": "info@muster.de"
},
"response": ["id", "name"]
}
  • id gehört nicht in ein Create-Payload; sie wird erzeugt.
  • Optional steuert createReadMode (STRICT | LENIENT) das Verhalten, wenn das Zurücklesen nach dem Anlegen fehlschlägt – siehe Transaktionen.
PUT /api/rest/crm/customer/update/{id}
{
"data": {
"id": "5a2b…",
"name": "Muster GmbH",
"email": "neu@muster.de"
},
"response": ["+"]
}

id ist Pflicht (@NotNull(groups = OnUpdate.class)). Was im data-Objekt fehlt, wird geleert – siehe Schreibsemantik.

PATCH /api/rest/crm/customer/update/{id}
{
"data": {
"id": "5a2b…",
"email": "neu@muster.de"
},
"response": ["+"]
}

data ist hier eine freie Map, kein typisiertes Objekt. Nur dadurch kann PATCH zwischen „Feld fehlt” (unverändert) und „Feld ist null” (leeren) unterscheiden.

Falle: Ein generiertes Payload-Objekt an PATCH zu schicken, serialisiert alle nicht gesetzten Felder als null – und leert sie damit. Siehe Schreibsemantik.

POST /api/rest/crm/customer/read/{id}
{
"response": ["+", {"field": "orders", "response": ["id", "total"]}],
"exclude": ["internalNote"]
}
POST /api/rest/crm/customer/query
{
"response": ["id", "name"],
"parameter": {
"meta": true,
"page": 0,
"limit": 25,
"order": [{"field": "name", "order": "ASC"}],
"query": {
"type": "AND",
"filter": [{"key": "name", "value": "Muster", "param": "LIKE"}],
"group": []
}
}
}

Details zu parameter in Suche und Filter.

Zwei Wege:

1. MultipartPOST /create/upload mit den Teilen data (JSON) und files (eine oder mehrere Dateien). Die Zuordnung läuft über den Dateinamen: das Feld name im Datei-Objekt muss dem Originalnamen der hochgeladenen Datei entsprechen. Doppelte Dateinamen werden abgelehnt (duplicate-filenames|…), fehlende Namen ebenso (missing-filename).

2. Base64 im JSON – jedes Objekt im Payload, das einen content-String trägt, wird als Datei erkannt, dekodiert und in eine Temp-Datei geschrieben. Der Name kommt aus name, filename oder fileName. Ein data:-Präfix wird abgeschnitten. Das funktioniert in beliebiger Verschachtelungstiefe.

{
"data": { "id": "5a2b…", "name": "Muster GmbH" },
"meta": { "error": false, "errorMessage": null, "notNull": false }
}
{
"data": [ { "id": "…" }, { "id": "…" } ],
"meta": {
"error": false,
"errorMessage": null,
"totalCount": 42,
"currentPage": 0,
"currentPageSize": 25,
"currentLimit": 25
}
}

totalCount wird nur berechnet, wenn parameter.meta = true (Standard).

{
"data": [
{
"revision": { "id": "…", "name": "Muster GmbH" },
"revisionMeta": {
"ref": 17, "ts": 1785574268973,
"ip": "10.0.0.5", "useragent": "Mozilla/5.0…",
"username": "d.mertins"
},
"revisionType": "MOD"
}
],
"meta": { "totalCount": 3, "currentPage": 0, "currentPageSize": 0, "currentLimit": 0 }
}

Siehe Auditing.

{
"error": "ApiValidationException",
"message": "validation-failed",
"messageKey": "CDMS_VALIDATION_FAILED",
"code": "422",
"layer": "API",
"violations": [
{ "field": "name", "rule": "cannot-be-null" },
{ "field": "orders.0.total", "rule": "min-number" }
]
}

Siehe Fehler und Statuscodes.

Bei abstrakten Modellen trägt das Payload-Objekt den konkreten Typ:

{ "data": { "@type": "model.datamodel", "name": "Customer" }, "response": ["+"] }
  • Top-Level-Payloads verwenden im Hub-Frontend den kurzen Modellnamen (system, folder), verschachtelte den qualifizierten (enums.enumitem).
  • Beim Lesen ist @type nicht nötig – die Auflösung läuft polymorph über die ID.
  • Beim rekursiven Anlegen einer abstrakten Referenz im PATCH ist @type Pflicht, sonst missing-type-for-abstract-field|<feld>.