Approvals API
Approvals bündelt alle Genehmigungen und Bestätigungen des Systems in zwei Teilen: (1) EntityApproval — persistierte Multi-Approver-Records mit konfigurierbaren Strategien (ALL, ANY, MAJORITY, QUORUM), Approval-Groups und -Configs, für Changes, Change-Templates, Incidents (Closure/Data-Breach) und Workflow-Genehmigungsschritte; (2) die Union-Inbox (GET /api/approvals/inbox), die offene Entscheidungen aus mehreren Bereichen in einer Liste zusammenführt (Abwesenheiten, Asset-Übergaben, Change-Task-Freigaben u. a.).
Unterstützte Entity-Types
| Entity-Type | Beschreibung | Status-Auswirkung |
|---|---|---|
CHANGE |
Change-Genehmigung (Multi-Approver) | → APPROVED / REJECTED |
INCIDENT |
Incident-Abschluss und -Wiedereröffnung (inkl. DSGVO Data-Breach) | → CLOSED / RESOLVED |
CHANGE_TEMPLATE |
Change-Template-Genehmigung | → APPROVED / REJECTED |
WORKFLOW |
Genehmigungs-Schritte laufender Workflows (Approver-Recht: workflows.completeSteps) | → Schritt APPROVED / REJECTED |
Genehmigungsschritte in Workflows können automatisch entschieden werden: Auto-Approve- und Auto-Reject-Bedingungen prüfen einen Feldwert der Workflow-Daten, ein Recht oder eine Rolle; mehrere Bedingungen werden mit UND oder ODER verknüpft. Auto-Reject wird vor Auto-Approve geprüft. Eine Frist erhält der Schritt über Menge und Einheit (dueTimeAmount, dueTimeUnit); daraus ergibt sich das Fälligkeitsdatum der Genehmigung. Von Hand entschieden wird ein solcher Schritt über den Approve-Endpoint der Instanz (APPROVED oder REJECTED, dazu ein optionaler Kommentar); abschließen lässt er sich nicht. So nimmt jede Entscheidung denselben Weg — mit derselben Rechteprüfung, demselben Protokoll und demselben Verhalten bei Ablehnung. Der Kommentar erscheint anschließend im Vorgang unter den Schritt-Ausgaben. Konfiguration und Beispiele: Workflows API →
Cross-Domain Pending-Inbox (Union-Inbox)
Über die EntityApproval-Records hinaus sammelt die Union-Inbox (GET /api/approvals/inbox) offene Entscheidungen aus mehreren Domains zu einer einheitlichen Liste. Jede Quelle liefert nur Einträge, die der Aufrufer sehen darf; fehlt das Recht für eine Quelle, bleibt sie leer (kein 403). Zugewiesene ARBEIT (Tickets, Incidents, Probleme, Changes, Change-Tasks, Workflow-Schritte) ist davon getrennt und läuft über /api/my-tasks.
| Provider | Quelle |
|---|---|
entityApproval | EntityApproval-Records (Change/Incident/Template) |
absence | Abwesenheits-Anträge (Manager-Entscheidung) |
handover | Asset-Handover-Bestätigungen |
changeTaskReady | Change-Task „ready"-Bestätigungen |
cascadingAwareness | Cascading-Awareness-Hinweise |
Endpoints Übersicht
Approvals (User-Facing)
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/my-tasks | Zugewiesene Arbeits-Items (keine Entscheidungen) — dokumentiert in der API-Übersicht |
GET | /api/my-tasks/count | Anzahl zugewiesener Arbeits-Items je Scope (für Badges) |
GET | /api/approvals/inbox | Union-Inbox der offenen Approvals über alle Quellen (?types= CSV, ?limit 1–200 [Default 50], ?offset) |
GET | /api/approvals/inbox/count | Zähler je Quelle (Badge/Tabs) |
POST | /api/approvals/inbox/decide | Universal-Entscheidung über die Inbox-ID (TYPE:id) |
GET | /api/approvals/check | Prüft, ob ein entityType/subType ein Approval erfordert (Konfigurationsdaten für Erstell- und Schließen-Dialoge) |
POST | /api/approvals/:id/decide | Entscheidung treffen (Approve/Reject) |
Die Union-Inbox hat keine eigene Berechtigung. Jede Quelle prüft ihre Rechte selbst, damit auch Endanwender ihre Bestätigungs-Aufgaben sehen. Wer für keine Quelle berechtigt ist, erhält eine leere Liste. Ungültige Eingaben lehnt die API mit 400 ab: ein unbekannter types-Wert sowie ein nicht-numerisches limit oder ein limit außerhalb 1–200. Ebenso verlangt /check einen gültigen entityType.
Approver und Kommentare einer Entität kommen aus deren eigenem Detail-Payload (Change, Incident, Problem) — dort gelten die Sichtrechte der jeweiligen Entität.
Approval Groups (Admin)
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/admin/approval-groups | Alle Approval-Groups |
POST | /api/admin/approval-groups | Neue Approval-Group erstellen |
PATCH | /api/admin/approval-groups/:id | Group aktualisieren (partiell) |
DELETE | /api/admin/approval-groups/:id | Group löschen |
GET | /api/admin/approval-groups/:id/members | Group-Mitglieder |
GET | /api/admin/approval-groups/:id/eligible-members | Potentielle Mitglieder (noch nicht in Group) — ?search=, ?take/skip; liefert {data, total} |
POST | /api/admin/approval-groups/:id/members | Mitglied hinzufügen |
PATCH | /api/admin/approval-groups/:id/members/:memberId | Mitglied aktualisieren (z.B. required-Flag) |
DELETE | /api/admin/approval-groups/:id/members/:memberId | Mitglied entfernen (memberId = Entry-ID) |
Approval Configs (Admin)
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/admin/approval-configs | Alle Approval-Konfigurationen |
GET | /api/admin/approval-configs/:id | Config Details |
POST | /api/admin/approval-configs | Neue Config erstellen |
PUT | /api/admin/approval-configs/:id | Config aktualisieren |
DELETE | /api/admin/approval-configs/:id | Config löschen |
Konfiguration: Groups, Configs & Closure Policy
Die Konfigurations-Ebene (Approval-Groups + Approval-Configs) wird in der UI unter Admin-Center → Service-Konfiguration → Genehmigungen verwaltet (/admin/approvals). Der Bereich hat drei Tabs: Groups, Configurations und Closure Policy (= Incident-Closure-Policy). Alle Admin-Endpoints erfordern eine Benutzer-Anmeldung; API-Keys werden nicht akzeptiert.
Approval-Group — Felder
| Feld | Typ | Beschreibung |
|---|---|---|
name | String (1–50) | Technischer Name, lowercase-alphanumerisch mit Bindestrichen |
displayName | String (1–100) | Anzeigename |
description | String? (max. 500) | Beschreibung |
type | enum | CAB, EMERGENCY_CAB, INCIDENT_CLOSURE, INCIDENT_REOPEN, MAJOR_INCIDENT, DATA_PROTECTION, CUSTOM |
defaultStrategy | enum | ANY, ALL, QUORUM, MAJORITY |
defaultQuorum | Int? | Mindestzahl für QUORUM |
isActive | Boolean | Aktiv/inaktiv (nur PATCH) |
members[] | — | userId + weight (1–10) + isBackup. eligible-members listet nur User mit der nötigen Approver-Permission. |
POST und PATCH prüfen den Body streng: Ein Feld, das diese Tabelle nicht nennt, lehnt die API mit 400 ab. name wird nur beim Anlegen gesetzt, Mitglieder laufen über die members-Endpoints. Beim PATCH bleibt ein weggelassenes Feld unverändert; null leert description und defaultQuorum.
Approval-Config (Closure Policy) — Felder
Eine Approval-Config legt fest, OB und WIE eine Entität genehmigt werden muss. Für INCIDENT mit subType-Bezug zur Schließung ist genau das die „Closure Policy". Pro (entityType, subType) existiert höchstens eine Config (unique).
| Feld | Typ | Beschreibung |
|---|---|---|
entityType | enum | CHANGE, INCIDENT, PROBLEM, CHANGE_TEMPLATE, WORKFLOW |
subType | String? | z.B. CLOSURE, DATA_BREACH, EMERGENCY, MAJOR, P1, REOPEN … (verfeinert die Regel) |
requiresApproval | Boolean | Default true. false → kein Approval nötig (Closure ohne Freigabe) |
approvalGroupId | cuid? | Welche Group genehmigt |
strategy / quorum | enum? / Int? | Override; null = Group-Default verwenden |
autoAssign | Boolean | Default true — Approver automatisch aus der Group zuweisen |
conditions | JSON? | Bedingungs-Matching, z.B. {"riskLevel":["HIGH","VERY_HIGH"]} |
priority | Int (0–100) | Höher = zuerst geprüft (bei mehreren passenden Configs) |
isActive | Boolean | Aktiv/inaktiv |
Incident Closure Policy: Der Closure-Policy-Tab konfiguriert für INCIDENT, ob das Schließen eine Freigabe erfordert (requiresApproval), welche Group + Strategie greift und ggf. Bedingungen. Approver brauchen incidents.approveClosure; der SubType DATA_BREACH verlangt stattdessen incidents.acknowledgeDataBreach (DSGVO). Mehrere Approvals derselben Entität (z.B. Closure + Data-Breach) laufen parallel.
Incident Reopen Approval: Das Wiederöffnen eines geschlossenen Incidents ist standardmäßig genehmigungspflichtig (reopenRequiresApproval). Es läuft über eine eigene Approval-Gruppe vom Typ INCIDENT_REOPEN bzw. den subType REOPEN und über die eigene Approver-Berechtigung incidents.approveReopen. So bleibt die Reopen-Genehmigung vom Closure-Approval getrennt, und „darf schließen" und „darf wieder öffnen" können unterschiedliche Personenkreise sein. Der Reopen läuft über POST /api/incidents/:id/reopen; nach Freigabe (Entscheidung via POST /:id/decide) wird der Incident auf ACKNOWLEDGED gesetzt, bei Ablehnung bleibt er CLOSED. reopenOverride umgeht NIE die Approval-Pflicht. Reopen & Lifecycle
Approval-Strategien
| Strategie | Beschreibung | Ergebnis |
|---|---|---|
ALL |
Alle Approver müssen zustimmen | APPROVED wenn alle zustimmen, REJECTED bei einer Ablehnung |
ANY |
Ein einziger Approver reicht | APPROVED bei erster Zustimmung |
MAJORITY |
Einfache Mehrheit (>50%) | APPROVED wenn >50% zustimmen |
QUORUM |
Konfigurierbare Mindestzahl | APPROVED wenn quorum-Anzahl zustimmt |
API-Beispiele
Meine offenen Entscheidungen abrufen (cross-domain)
GET /api/approvals/inbox?types=CHANGE,HANDOVER_CONFIRM&limit=50&offset=0
Aggregiert über alle Provider; jeder Eintrag trägt seinen Quell-Typ und sagt, ob er direkt in der Liste entschieden werden kann (decideMode: inline) oder auf seine Seite verweist (deeplink). Beispiel (gekürzt):
{
"data": [
{
"itemId": "CHANGE:approval-uuid-1",
"type": "CHANGE",
"entityId": "change-uuid",
"entityNumber": "CHG-2026-000042",
"title": "Upgrade PostgreSQL",
"decideMode": "inline",
"deeplinkUrl": "/changes/change-uuid",
"inlineActions": { "rejectRequiresReason": true },
"initiatedAt": "2026-03-17T10:00:00Z",
"initiatedBy": { "id": "user-uuid", "name": "Jane Smith" },
"priority": "HIGH"
},
{
"itemId": "HANDOVER_CONFIRM:handover-uuid",
"type": "HANDOVER_CONFIRM",
"entityId": "handover-uuid",
"entityNumber": "HO-00007",
"title": "Dell XPS 15 Laptop",
"decideMode": "deeplink",
"deeplinkUrl": "/assets/handovers/handover-uuid",
"initiatedAt": "2026-03-17T14:00:00Z"
}
],
"pagination": { "total": 2, "limit": 50, "offset": 0 },
"counts": { "total": 2, "byType": { "CHANGE": 1, "HANDOVER_CONFIRM": 1 } }
}
Entschieden wird über POST /api/approvals/inbox/decide mit genau dieser itemId ({ itemId, decision: "APPROVE" | "REJECT", reason?, comment? }) — der Aggregator leitet anhand des Präfixes an den richtigen Provider weiter. Items mit rejectRequiresReason: true verlangen bei REJECT eine Begründung.
Entscheidung treffen
POST /api/approvals/:id/decide
{
"decision": true,
"comment": "Approved. Implementation plan looks solid."
}
Response
{
"approval": {
"id": "approval-uuid-1",
"entityType": "CHANGE",
"entityId": "change-uuid",
"decision": true,
"decisionAt": "2026-03-17T11:30:00Z",
"comment": "Approved. Implementation plan looks solid."
},
"evaluation": {
"isComplete": true,
"outcome": "APPROVED",
"approvals": 2,
"rejections": 0,
"pending": 0,
"total": 2,
"requiredForApproval": 2,
"strategy": "ALL"
}
}
Wenn die Evaluation ergibt, dass alle benötigten Approvals vorliegen, wird der Entity-Status automatisch aktualisiert (z.B. Change → APPROVED, Incident → CLOSED).
Genehmigungspflicht prüfen
GET /api/approvals/check?entityType=INCIDENT&subType=CLOSURE
{
"requiresApproval": true,
"strategy": "ALL",
"approvalGroupId": "clx-group-incident-closure"
}
Der Endpoint liefert die Konfigurations-Metadaten für Create-/Close-Dialoge. Den laufenden Genehmigungsstand einer konkreten Entität trägt deren Detail-Payload (z.B. GET /api/changes/:id).
Verhalten bei Ablehnung
Bei Genehmigungsschritten in Workflows legt die Schritt-Konfiguration fest, was nach einer Ablehnung passiert:
| Einstellung | Verhalten |
|---|---|
failOnReject (Standard: an) | Der Workflow endet mit Status FAILED, sofern kein Eskalations-Schritt konfiguriert ist; der Initiator wird benachrichtigt. |
failOnReject aus | Der Workflow läuft mit den nächsten Schritten weiter. |
escalationStepId | Greift bei jeder Ablehnung — von Hand wie automatisch — und geht failOnReject vor: statt zu scheitern, startet der angegebene Schritt. Benachrichtigt wird, wer diesen Schritt bearbeitet; führt ihn das System selbst aus, entfällt die Meldung, und lässt sich kein Bearbeiter bestimmen, geht sie an den Initiator. |
Permissions
| Permission | Beschreibung |
|---|---|
approvals.decide | Entscheidungen buchen (zusätzlich zur Zuweisung und zum entity-spezifischen Approver-Recht) |
approvals.viewGroups | Approval-Groups lesen (Settings-Tab Groups) |
approvals.manageGroups | Approval-Groups anlegen/ändern/löschen |
approvals.manageMemberships | Group-Mitglieder verwalten (hinzufügen/ändern/entfernen) |
approvals.viewConfigs | Approval-Configs + Closure Policy lesen (Settings-Tabs Configs/Closure Policy) |
approvals.manageConfigs | Approval-Configs + Closure Policy verwalten |
Wer entscheiden darf: Eine Entscheidung (POST /:id/decide bzw. /inbox/decide) setzt dreierlei voraus: (1) die Berechtigung approvals.decide; (2) die Entscheidung ist dem Aufrufer zugewiesen (sonst 403 NOT_ASSIGNED); (3) die Approver-Berechtigung des Entitätstyps — INCIDENT → incidents.approveClosure, INCIDENT:DATA_BREACH → incidents.acknowledgeDataBreach, INCIDENT:REOPEN → incidents.approveReopen, CHANGE/CHANGE_TEMPLATE → changes.approve, WORKFLOW → workflows.completeSteps. Bei kritischen Aktionen werden die Rechte mit dem aktuellen Stand aus der Datenbank geprüft, damit ein gerade entzogenes Recht sofort wirkt. Wer die Genehmigung angefordert hat, kann sie nicht selbst erteilen. Detail-Seite und Union-Inbox prüfen identisch.
Fehlerbehandlung
| Error | HTTP | Beschreibung |
|---|---|---|
APPROVAL_NOT_FOUND | 404 | Approval-ID existiert nicht |
NOT_ASSIGNED | 403 | Approval ist nicht dem Aufrufer zugewiesen |
ALREADY_DECIDED | 409 | Entscheidung wurde bereits getroffen |
REJECTION_COMMENT_REQUIRED | 400 | Ablehnung erfordert einen Kommentar |
NO_VALID_APPROVERS | 400 | Keine gültigen Approver konfiguriert |
GROUP_IN_USE | 409 | Group wird in aktiven Configs verwendet |
Change-Approvals im Detail
Incident-Closure-Approval & DSGVO