Incidents API
Die Incidents API ermöglicht die Verwaltung von IT-Störungen mit SLA-Tracking, Priority-Matrix, Closure-Approval, Post-Incident-Review (PIR), DSGVO/Data-Breach-Flow, Kategorie-Management und Evidence-Checklisten.
Endpoints Übersicht
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/incidents | Liste mit Filtern ({data, pagination}); erfordert incidents.viewAll oder viewOwn, sonst 403 |
GET | /api/incidents/stats | Tab-Counts (all, myIncidents, assigned, major, pendingApprovals) |
GET | /api/incidents/:id | Einzelnes Incident (inkl. Activities, Approver, SLA-Tracking) |
POST | /api/incidents | Neues Incident erstellen (incidents.create) → 201 |
PATCH | /api/incidents/:id | Incident aktualisieren (Rechte je Feldgruppe, Optimistic Locking) |
DELETE | /api/incidents/:id | Incident löschen (Soft-Delete, incidents.delete) → 204 |
POST | /api/incidents/:id/restore | Aus dem Papierkorb zurückholen (restore + viewDeleted) → 204 |
POST | /api/incidents/:id/reopen | Geschlossenes Incident wiedereröffnen (incidents.reopen; CLOSED → ACKNOWLEDGED, standardmäßig Approval-pflichtig) |
PATCH | /api/incidents/:id/assign | Bearbeiter setzen/entfernen (incidents.assign) |
POST | /api/incidents/from-ticket | Ticket zu einem Incident eskalieren (incidents.create) → 201 |
POST | /api/incidents/:id/bulk-message | Sammel-Nachricht an die Kunden verknüpfter Tickets (incidents.bulkMessage + Sicht, nur Major) |
GET | /api/incidents/:id/suggest-tickets | Verknüpfbare Tickets vorschlagen (zusätzlich incidents.linkToTickets) |
GET | /api/incidents/:id/impact-tree | Downstream-Wirkung vor dem Abschluss (verknüpfte Tickets + SLA) |
GET | /api/incidents/:id/unified-timeline | Timeline über Incident + verknüpfte Tickets/Problems (?limit/?offset) |
POST | /api/incidents/:id/activity | Kommentar hinzufügen → 201 |
POST | /api/incidents/:id/evidence-checklist/initialize | Evidence-Checkliste aus der Vorlage anlegen |
PUT | /api/incidents/:id/evidence-checklist | Evidence-Checkliste aktualisieren (Items-Vollmenge) |
PATCH | /api/incidents/:id/data-breach-details | DSGVO: Breach-Details annotieren (DSB-Rolle) |
ID oder Nummer: Jede Route der Domäne akzeptiert an der Stelle :id entweder die technische ID oder die Incident-Nummer (INC-2026-000123) — das Format entscheidet, welches von beiden gemeint ist. Schreibende Aufrufe erfordern einen angemeldeten Benutzer (API-Keys werden abgelehnt); lesende Aufrufe prüfen die Sicht-Rechte viewAll/viewOwn.
Enum-Werte in Großschreibung: Status, Priority, Impact, Urgency und detectionSource werden in Großschreibung gesendet und geliefert (NEW, HIGH, P1, MONITORING …). Kleingeschriebene Werte ergeben 400.
Status-Maschine
Incidents durchlaufen folgende Status:
NEW → ACKNOWLEDGED → IN_PROGRESS → [ON_HOLD] → MITIGATED → RESOLVED → PENDING_CLOSURE → CLOSED Status-Beschreibung: • NEW = Neu gemeldet, noch nicht bestätigt • ACKNOWLEDGED = Bestätigt, SLA Response-Timer gestoppt • IN_PROGRESS = In Bearbeitung, SLA Resolution-Timer läuft • ON_HOLD = Pausiert (SLA-Timer pausiert) • MITIGATED = Workaround aktiv, Störung umgangen • RESOLVED = Gelöst, Root-Cause behoben • PENDING_CLOSURE = Abschluss beantragt, wartet auf Genehmigung • CLOSED = Geschlossen & archiviert
Erlaubte Übergänge
| Von Status | Nach Status | Bedingung / Recht |
|---|---|---|
| NEW | ACKNOWLEDGED · IN_PROGRESS · ON_HOLD · MITIGATED · RESOLVED | incidents.changeStatus |
| ACKNOWLEDGED | IN_PROGRESS · ON_HOLD · MITIGATED · RESOLVED | incidents.changeStatus |
| IN_PROGRESS | ON_HOLD · MITIGATED · RESOLVED | incidents.changeStatus |
| ON_HOLD | NEW · ACKNOWLEDGED · IN_PROGRESS · MITIGATED · RESOLVED | incidents.changeStatus |
| MITIGATED | ON_HOLD · RESOLVED | MITIGATED verlangt workaroundAvailable=true ODER currentMitigation |
| RESOLVED | ON_HOLD · PENDING_CLOSURE · CLOSED | RESOLVED verlangt resolutionCode + rootCauseShort; PENDING_CLOSURE verlangt incidents.requestClosure |
| PENDING_CLOSURE | RESOLVED · CLOSED | Entscheidung der Genehmiger über die Approvals API (changeStatus genügt nicht) |
| CLOSED | ACKNOWLEDGED | einziger Reopen-Pfad — incidents.reopen |
ON_HOLD: Erreichbar aus allen offenen Status und aus RESOLVED. Aus CLOSED führt nur der Reopen (incidents.reopen) zurück, aus PENDING_CLOSURE nur die Ablehnung durch die Genehmiger (zurück auf RESOLVED).
Priority-Matrix (Impact × Urgency)
Die Priority wird serverseitig aus Impact und Urgency berechnet — beim Anlegen und bei jeder Änderung eines der beiden Felder:
| Impact ↓ / Urgency → | LOW | MEDIUM | HIGH |
|---|---|---|---|
| HIGH | P2 | P1 | P1 |
| MEDIUM | P3 | P2 | P1 |
| LOW | P4 | P3 | P2 |
Major Incident: majorIncident ist ein bewusster Toggle, kein Automatismus: das Flag folgt NICHT der berechneten Priorität, sondern wird ausdrücklich gesetzt und verlangt incidents.declareMajor (kritische Aktion — das Setzen löst NOC-Alarm und Stakeholder-Benachrichtigungen aus). Auf einem geschlossenen Incident lässt es sich nicht umschalten (422 MAJOR_TOGGLE_ON_CLOSED).
Incident erstellen
Request
POST /api/incidents
{
"title": "Production database offline",
"description": "Main PostgreSQL database is not responding. All services affected.",
"businessImpact": "Complete service outage. 500+ users cannot access the system.",
"impact": "HIGH",
"urgency": "HIGH",
"categoryId": "clx-incident-category-id",
"affectedServices": ["API", "Frontend", "Mobile App"],
"detectionSource": "MONITORING",
"scope": "EU data center",
"assignedToId": "clx...",
"assignedGroupId": "clx-group-id",
"isSecurityRelevant": false,
"isDataBreach": false
}
Pflichtfelder
title(5–200 Zeichen)description(20–5000 Zeichen)businessImpact(10–1000 Zeichen)impact·urgency— LOW | MEDIUM | HIGHcategoryId— ID einer dynamischen Incident-KategorieaffectedServices— mindestens ein Eintrag, maximal 50 (400 AFFECTED_SERVICES_REQUIRED bei leerer Liste)
Optionale Felder
detectionSource— MONITORING | USER | INTERNAL | THIRD_PARTY (Default INTERNAL)scope(max. 500 Zeichen) ·assignedToId·assignedGroupIdisSecurityRelevant·isDataBreach·majorIncident(Default jeweils false)ignoreSubstitution— bewusste Zuweisung an eine abwesende Person (überspringt die Vertretungs-Umleitung)idempotencyKey(UUID) — ein wiederholter Aufruf mit demselben Schlüssel liefert das bestehende Incident zurück, statt ein zweites anzulegen
Melder (reporterId): Bei einem angemeldeten Benutzer setzt der Server den Melder auf den Aufrufer selbst; ein im Body mitgeschickter Wert wird ignoriert. So kann niemand Incidents unter fremdem Namen anlegen. Nur API-Key-Zugriffe müssen reporterId angeben — fehlt er dort, antwortet der Server 400 REPORTER_ID_REQUIRED.
Response (201 Created)
{
"id": "clx...",
"number": "INC-2026-000123",
"version": 1,
"title": "Production database offline",
"status": "NEW",
"priority": "P1",
"majorIncident": false,
"impact": "HIGH",
"urgency": "HIGH",
"detectionSource": "MONITORING",
"category": { "id": "clx...", "name": "Database", "color": "#ef4444" },
"affectedServices": ["API", "Frontend", "Mobile App"],
"reporter": { "id": "clx...", "name": "John Doe", "email": "john@example.com" },
"assignedTo": { "id": "clx...", "name": "Jane Smith", "email": "jane@example.com" },
"assignedGroup": { "id": "clx...", "name": "Database Team", "color": "#3b82f6" },
"closure": { "pendingClosureAt": null, "requestedBy": null, "approved": null },
"pir": { "pirCompleted": false, "evidenceChecklist": null },
"detectedAt": "2026-01-27T14:30:00.000Z",
"createdAt": "2026-01-27T14:30:00.000Z"
}
Incident aktualisieren
Request
PATCH /api/incidents/:id
{
"status": "ACKNOWLEDGED",
"currentMitigation": "Failover to secondary database activated",
"nextUpdateETA": "2026-01-27T15:00:00Z",
"version": 3
}
Feld-Gruppen und ihre Rechte
Die Basis-Berechtigung ist incidents.editAll oder incidents.editOwn (Melder oder Bearbeiter). Darüber hinaus verlangt jede Feldgruppe ein eigenes Recht — wer nur bearbeiten darf, darf damit noch nicht zuweisen oder eskalieren:
| Felder | Zusätzliches Recht |
|---|---|
title, description, businessImpact, scope, categoryId, affectedServices, workaroundAvailable, currentMitigation, nextUpdateETA, isSecurityRelevant, PIR-Felder | — (Basis-Edit genügt) |
status (operativ) | incidents.changeStatus |
status: PENDING_CLOSURE | incidents.requestClosure |
status CLOSED/RESOLVED aus PENDING_CLOSURE | Entscheidung der Genehmiger (changeStatus genügt nicht) |
status: ACKNOWLEDGED aus CLOSED (Reopen) | incidents.reopen |
impact, urgency | incidents.changePriority |
majorIncident | incidents.declareMajor |
assignedToId, assignedGroupId | incidents.assign |
affectedDataSubjects | incidents.acknowledgeDataBreach |
Abschluss-Regeln prüfen den Zustand nach der Änderung: Priorität, PIR-Häkchen und das Data-Breach-Flag werden zuerst angewendet und erst dann gegen die Abschluss-Regeln geprüft. Wer im selben Aufruf {impact:HIGH, urgency:HIGH, status:PENDING_CLOSURE} sendet, landet damit korrekt in der PIR-Pflicht des neuen P1 — statt an der alten Priorität vorbei zu schließen. Der Status selbst bleibt für die Übergangs-Prüfung immer der alte: eine Transition wird stets vom Ist-Status aus bewertet.
Antwort: Incident + Warnungen
Die Antwort ist das aktualisierte Incident. Kommen Hinweise dazu, trägt sie zusätzlich ein warnings-Array mit Codes (die Anzeige-Sprache bestimmt der Client, nicht der Server):
{
"id": "clx...",
"status": "PENDING_CLOSURE",
"version": 4,
"warnings": [
{ "code": "OPEN_LINKED_TICKETS", "params": { "count": 3 } }
]
}
| Code | Bedeutung |
|---|---|
OPEN_LINKED_TICKETS | Beim Lösen/Schließen sind noch offene verknüpfte Tickets vorhanden (params.count) |
CLOSURE_APPROVAL_NO_APPROVERS | Der Abschluss wurde beantragt, aber die konfigurierte Genehmigungs-Gruppe hat kein Mitglied — der Incident bleibt in PENDING_CLOSURE |
CLOSURE_APPROVAL_INIT_FAILED | Die Genehmigungs-Runde konnte nicht angelegt werden |
Optimistic Locking: Wird version mitgeschickt und stimmt sie nicht mit dem Server-Stand überein, antwortet der Server 409 VERSION_CONFLICT (mit expectedVersion und currentVersion in den Details). Jede erfolgreiche Mutation erhöht version.
Abschluss-Workflow (Closure)
Ob ein Abschluss genehmigt werden muss, entscheidet die Closure-Policy der Unified Approvals (je Priorität konfigurierbar, mit Gruppe und Strategie). Ist keine Genehmigung nötig, wird der Incident direkt geschlossen; andernfalls startet mit PENDING_CLOSURE eine Genehmigungs-Runde.
Schritt 1: Abschluss beantragen
PATCH /api/incidents/:id
{
"status": "PENDING_CLOSURE",
"closureComment": "Incident has been resolved. PIR is complete.",
"closureChecklist": {
"pirCompleted": true,
"relationshipsVerified": true,
"communicationDone": true
}
}
Die Genehmiger kommen aus der konfigurierten Approval-Gruppe und werden automatisch zugewiesen — sie werden nicht im Request benannt. Bei P1/P2 muss pirCompleted bereits true sein, sonst 422 PIR_REQUIRED.
Schritt 2: Entscheiden
POST /api/approvals/:id/decide
{
"decision": true,
"comment": "Approved. All documentation complete."
}
Ist die Runde nach der konfigurierten Strategie vollständig, setzt das System den Incident auf CLOSED. Eine Ablehnung führt ihn zurück auf RESOLVED (mit closureRejectionReason) und räumt die Genehmigungs-Runde ab, damit ein erneuter Antrag frisch startet. Der Genehmigungsstand steht im Detail-Payload des Incidents (Feld approvers). Approvals API
Ohne Genehmiger kein Abschluss: Ist eine Genehmigung vorgeschrieben, aber kein Genehmiger zugewiesen, bleibt der Incident in PENDING_CLOSURE. Der Antragsteller erhält die Warnung CLOSURE_APPROVAL_NO_APPROVERS; zusätzlich wird ein Audit-Eintrag geschrieben, damit die fehlende Konfiguration auffällt.
Wiedereröffnen (Reopen)
POST /api/incidents/:id/reopen
{
"reasonCode": "SYMPTOM_RETURNED",
"note": "Same outage pattern reappeared after 20 minutes.",
"version": 7
}
Wiedereröffnen erfordert ein eigenes Recht (incidents.reopen) und ist nur aus CLOSED möglich. Ist der Incident als Duplikat abgeschlossen, bleibt er gesperrt (400 REOPEN_RESOLUTION_LOCKED). Die Policy prüft zusätzlich Fenster, Maximalzahl und Grund-Pflicht; incidents.reopenOverride umgeht Fenster und Limit, niemals die Grund- oder Genehmigungs-Pflicht.
Die Antwort hat zwei Formen:
// 1) Ohne Genehmigungspflicht: das reaktivierte Incident
{ "id": "clx...", "status": "ACKNOWLEDGED", "reopenCount": 1, "reopenedAt": "..." }
// 2) Mit Genehmigungspflicht: der Incident bleibt CLOSED
{ "status": "PENDING_REOPEN_APPROVAL", "approverCount": 2, "message": "..." }
Die Wiedereröffnungs-Genehmigung läuft über eine eigene Gruppe (Typ INCIDENT_REOPEN) und das eigene Genehmiger-Recht incidents.approveReopen — getrennt vom Abschluss-Approval, damit „darf schließen" und „darf wieder öffnen" unterschiedliche Personenkreise sein können. Gibt es keinen Genehmiger, wird der Reopen abgelehnt (400 REOPEN_NO_APPROVERS). Nach der Freigabe setzt das System den Incident auf ACKNOWLEDGED und setzt Resolution- und Abschluss-Felder zurück, damit ein erneuter Abschluss eine neue Genehmigungs-Runde durchläuft. Reopen & Lifecycle
Liste & Filter
GET /api/incidents?f.status=in:NEW,ACKNOWLEDGED&f.priority=P1&page=1&per=20&sort=detectedAt:desc
Ein Filter hat die Form f.<feld>=<operator>:<wert>; ohne Operator-Präfix gilt Gleichheit (f.priority=P1). Mehrwertige Operatoren nehmen eine Komma-Liste (in:A,B), isNull und isNotNull stehen ohne Wert. Sortiert wird mit sort=<feld>:asc|desc.
RBAC-gefiltert (viewAll/viewOwn). Antwort:
{
"data": [
{
"id": "clx...",
"number": "INC-2026-000123",
"title": "Production database offline",
"status": "ACKNOWLEDGED",
"priority": "P1",
"majorIncident": true,
"impact": "HIGH",
"urgency": "HIGH",
"category": { "id": "clx...", "name": "Database", "color": "#ef4444" },
"reporter": { "id": "clx...", "name": "John Doe", "email": "john@example.com" },
"assignedTo": { "id": "clx...", "name": "Jane Smith", "email": "jane@example.com" },
"linkedTicketCount": 12,
"detectedAt": "2026-01-27T14:30:00.000Z",
"acknowledgedAt": "2026-01-27T14:32:00.000Z"
}
],
"pagination": { "page": 1, "limit": 20, "total": 42, "totalPages": 3, "hasMore": true }
}
| Parameter | Beschreibung |
|---|---|
f.status, f.priority, f.impact, f.urgency | Enum-Filter (Großschreibung; Operatoren eq/neq/in/notIn) |
f.categoryId, f.assignedToId, f.assignedGroupId, f.reporterId | Zuordnungs-Filter (eq/in/isNull/isNotNull) |
f.majorIncident, f.isSecurityRelevant, f.isDataBreach | Flag-Filter (true/false) |
f.detectedAt, f.createdAt, f.updatedAt, f.resolvedAt | Zeit-Filter (gt/gte/lt/lte/between/relative) |
f.number, f.title | Text-Filter (eq/contains/startsWith) |
q | Suche über Nummer, Titel, Beschreibung, Business-Impact |
page / per / sort | Seitenweise Ausgabe (per Standard 25, maximal 200; Standard-Sortierung updatedAt absteigend) |
deleted=1 | Papierkorb: NUR gelöschte Incidents (erfordert incidents.viewDeleted) |
includeDeleted=true | Mischliste inkl. gelöschter (erfordert incidents.viewDeleted) |
Ohne Sicht-Recht: 403. Wer weder incidents.viewAll noch viewOwn hat, erhält auf der Liste 403. GET /incidents/stats liefert in diesem Fall Nullwerte.
Löschen & Papierkorb
DELETE /api/incidents/:id → 204 No Content
POST /api/incidents/:id/restore → 204 No Content
Löschen ist ein Soft-Delete und eine kritische Aktion (incidents.delete, wird auditiert). Wiederherstellen verlangt incidents.restore (ebenfalls kritisch) und zusätzlich incidents.viewDeleted. Beide Wege prüfen außerdem die Sicht auf den Incident: Wer ihn nicht sehen darf, erhält 404, damit nicht erkennbar ist, ob er existiert.
SLA beim Löschen und Wiederherstellen: Solange ein offener Incident an einem Ticket hängt, ist dessen SLA-Uhr pausiert. Beim Löschen des Incidents laufen diese Uhren wieder an; die Verknüpfungen bleiben bestehen. Kommt der Incident beim Wiederherstellen offen zurück, werden die Pausen wieder gesetzt. Das eigene SLA-Tracking des Incidents wird beim Löschen beendet und beim Wiederherstellen frisch aufgesetzt, damit der Monitor ihn nicht sofort wegen zwischenzeitlich abgelaufener Fristen eskaliert.
Incident zuweisen
Request
PATCH /api/incidents/:id/assign
{
"assignedToId": "clx...",
"ignoreSubstitution": false
}
assignedToId: null entfernt die Zuweisung. Der Bearbeiter muss das Recht incidents.assignable haben; ist er abwesend, leitet das System auf seine hinterlegte Vertretung um — ignoreSubstitution: true weist bewusst trotzdem der abwesenden Person zu. Die Gruppen-Zuweisung läuft über PATCH /api/incidents/:id (Feld assignedGroupId, gleiches Recht incidents.assign).
Response
{
"incident": {
"id": "clx...",
"number": "INC-2026-000123",
"assignedTo": { "id": "clx...", "name": "Senior Agent", "email": "senior@example.com" },
"assignedGroup": { "id": "clx...", "name": "Senior Database Team", "color": "#3b82f6" },
"version": 5,
"updatedAt": "2026-01-27T14:45:00.000Z"
},
"assignmentChanged": true
}
assignmentChanged sagt, ob sich die Zuweisung tatsächlich geändert hat — bei einer Umleitung auf die Vertretung kann das Ziel ein anderes sein als angefordert.
Post-Incident Review (PIR)
PIR ist bei P1 und P2 verpflichtend und muss vor dem Abschluss-Antrag abgeschlossen sein. Die PIR-Felder liegen im Response unter pir, werden aber flach per PATCH geschrieben:
{
"pirCompleted": true,
"rcaTimeline": "14:30 alert, 14:42 failover, 15:10 root cause identified",
"lessonsLearned": "Root cause was insufficient database monitoring. Added health checks and alerting rules.",
"longTermActions": "Introduce disk-space budget alerts per cluster",
"isoControlReference": "ISO 27001 A.16.1.7"
}
pirCompleted(boolean) — setzt/löscht pirCompletedAt automatischpirReopenReason— Begründung beim Zurücknehmen eines abgeschlossenen PIR (landet in der Timeline und im Audit)rcaTimeline,lessonsLearned,longTermActions(je max. 2000 Zeichen),isoControlReference(max. 200)
PIR und Nachweise hängen zusammen: Solange Pflicht-Positionen der Evidence-Checkliste offen sind, lässt sich das PIR nicht abschließen (422 PIR_REQUIRED_EVIDENCE_UNCHECKED, mit Anzahl und Bezeichnungen). Umgekehrt friert ein abgeschlossenes PIR die Checkliste ein (409 EVIDENCE_CHECKLIST_FROZEN) — erst das PIR zurücknehmen, dann die Nachweise ändern.
Evidence-Checklisten
Die Checkliste wird pro Incident aus einer Vorlage angelegt — der Vorlage der Incident-Kategorie oder, falls dort keine hinterlegt ist, dem globalen Default. Anlegen und Ändern der Checkliste verlangt dasselbe Recht wie das Bearbeiten des Incidents.
# Checkliste aus der Vorlage anlegen
POST /api/incidents/:id/evidence-checklist/initialize
# Positionen aktualisieren (Vollmenge)
PUT /api/incidents/:id/evidence-checklist
{
"items": [
{ "id": "item-1", "label": "Screenshot der Fehlermeldung", "source": "TEMPLATE", "required": true, "checked": true },
{ "id": "item-2", "label": "Log-Auszug", "source": "TEMPLATE", "required": true, "checked": false, "uncheckReason": "Log rotiert, wird nachgereicht" },
{ "id": "item-3", "label": "Notiz Rufbereitschaft", "source": "USER", "required": false, "checked": true }
],
"version": 6
}
- Positionen aus der Vorlage (source: TEMPLATE) können nicht entfernt werden (400
EVIDENCE_TEMPLATE_ITEM_IMMUTABLE) - Ein Häkchen wieder zu entfernen verlangt einen Grund (400
EVIDENCE_UNCHECK_REASON_REQUIRED) checkedAt/checkedBysetzt ausschließlich der Server- version ist optional; wird sie mitgeschickt, antwortet ein veralteter Stand mit 409 — sonst überschreiben sich zwei parallel arbeitende Personen still
- Ist weder für die Kategorie noch global eine Vorlage hinterlegt, antwortet initialize 422
EVIDENCE_TEMPLATE_MISSING
Duplikat-Erkennung
Ein Incident kann beim Lösen als Duplikat eines anderen ausgewiesen werden. Der Resolution-Code steuert das: verlangt der Code eine Verknüpfung (z.B. DUPLICATE), ist duplicateOfId Pflicht.
{
"status": "RESOLVED",
"resolutionCode": "DUPLICATE",
"rootCauseShort": "Same root cause as INC-2026-000100",
"duplicateOfId": "clx-master-incident-id"
}
- Ein Incident kann nicht Duplikat seiner selbst sein (422
DUPLICATE_SELF) - Ketten sind ausgeschlossen: ist das Ziel selbst ein Duplikat, verweist der Fehler auf das Original (422
DUPLICATE_CHAIN) - Ein so abgeschlossener Incident bleibt gesperrt — er kann nicht wiedereröffnet werden, nur vorwärts abgeschlossen
Workaround & Mitigation
{
"status": "MITIGATED",
"workaroundAvailable": true,
"currentMitigation": "Failover to secondary database cluster activated. Users can access the system again with 10% performance degradation.",
"nextUpdateETA": "2026-01-27T16:00:00Z"
}
MITIGATED verlangt entweder workaroundAvailable = true oder einen Text in currentMitigation (422 MITIGATION_REQUIRED) — der Status soll nicht ohne dokumentierte Umgehung gesetzt werden. nextUpdateETA ist die zugesagte nächste Statusmeldung; verstreicht sie ohne Update, wird benachrichtigt.
Timeline & Kommentare
Jede Änderung schreibt einen Timeline-Eintrag. Die Einträge kommen im Detail-Payload des Incidents mit (Feld activities, neueste zuerst); die Liste enthält sie nicht.
{
"activities": [
{
"id": "clx...",
"type": "STATUS_CHANGED",
"details": "Status changed from NEW to ACKNOWLEDGED",
"data": { "oldStatus": "NEW", "newStatus": "ACKNOWLEDGED" },
"actor": { "id": "clx...", "name": "Jane Smith", "email": "jane@example.com" },
"apiKey": null,
"actorName": "Jane Smith",
"createdAt": "2026-01-27T14:32:00.000Z"
}
]
}
Kommentar hinzufügen
POST /api/incidents/:id/activity → 201
{
"details": "Vendor confirmed a firmware bug; patch expected tonight."
}
details ist Pflicht (1–1000 Zeichen), data optional. Ein geschlossener Incident nimmt keine Kommentare an (400 INCIDENT_ALREADY_CLOSED) — die Historie bleibt lesbar, aber nicht fortschreibbar.
Unified Timeline
GET /api/incidents/:id/unified-timeline?limit=50&offset=0
Führt die Einträge des Incidents mit denen der verknüpften Tickets und Problems zu einer chronologischen Liste zusammen ({timeline, total, hasMore}, limit 1–200, Default 50). Jeder Fremdeintrag wird einzeln geprüft: Tickets und Problems, die der Aufrufer nicht sehen darf, tauchen gar nicht erst auf.
Verknüpfte Entitäten im Payload
Verknüpfungen stehen im Payload als ID-Arrays; die Inhalte liefern die Linking-Endpunkte. Eingebettet bleibt nur die Problem-Referenz (Status-Anzeige in der Seitenleiste) — als schlanke Referenz ohne Titel:
{
"linkedTicketIds": ["clx...", "clx..."],
"linkedProblemIds": ["clx..."],
"linkedChangeIds": [],
"linkedAssetIds": ["clx..."],
"linkedArticleIds": [],
"linkedProblems": [{ "id": "clx...", "problemNumber": "PRB-2026-000012", "status": "INVESTIGATING", "priority": "HIGH" }],
"duplicateOf": { "id": "clx...", "number": "INC-2026-000100", "status": "IN_PROGRESS", "priority": "P1" },
"affectedCustomerIds": ["clx..."],
"linkedTicketCount": 12,
"linkedProblemCount": 1
}
Vollständige Daten über die Linking-Endpunkte: Titel und Kundendaten verknüpfter Vorgänge liefern die Linking-Endpunkte, die für jeden Eintrag die Sicht des Aufrufers prüfen. Der Incident-Payload enthält deshalb nur IDs; duplicateOf nennt nur Nummer und Status des Original-Incidents, die Problem-Referenz nur Nummer, Status und Priorität. Entity Linking API
Ticket-Vorschläge & Impact-Baum
GET /:id/suggest-tickets— schlägt offene Tickets der letzten 48 Stunden vor, die zu den betroffenen Services passen und noch nicht verknüpft sind. Weil dieser Picker Ticket-Titel und Kunden zeigt, verlangt er zusätzlich incidents.linkToTickets, und jeder Kandidat wird einzeln gegen die Ticket-Sicht geprüft. Antwort:{suggestions, total}GET /:id/impact-tree— zeigt vor dem Abschluss die verknüpften Tickets samt SLA-Zustand und wie viele SLA-Uhren der Abschluss wieder anlaufen lässt (summary.slasToResume). Zeilen ohne Leserecht erscheinen als accessible: false statt zu verschwinden — der Umfang bleibt sichtbar, die Inhalte nicht.
Sammel-Nachricht bei Major Incidents
POST /api/incidents/:id/bulk-message
{
"subject": "Update: Production database outage",
"message": "The failover is active. We expect full service within the hour."
}
{ "sentCount": 34, "totalRecipients": 36, "ticketCount": 12 }
Erreicht die Kunden aller verknüpften Tickets plus deren CC-Beteiligte (dedupliziert über die E-Mail-Adresse); jedes betroffene Ticket bekommt einen Timeline-Eintrag mit dem Wortlaut. Nur auf Major Incidents möglich (400 BULK_MESSAGE_NOT_MAJOR). message 10–5000, subject optional 5–200 Zeichen.
Recht plus Sicht: incidents.bulkMessage ist ein globales Recht. Zusätzlich muss der Aufrufer dieses Incident sehen dürfen (sonst 403). Empfänger gelöschter Tickets werden nicht adressiert.
Ticket zu Incident eskalieren
POST /api/incidents/from-ticket → 201
{
"ticketId": "clx-ticket-id",
"title": "Production database offline",
"description": "Multiple customers report identical timeouts across all services.",
"businessImpact": "Complete service outage for all customers.",
"impact": "HIGH",
"urgency": "HIGH",
"categoryId": "clx-incident-category-id",
"affectedServices": ["API", "Frontend"],
"majorIncident": true
}
Legt das Incident an, verknüpft es mit dem Ticket, überträgt den Kundenbezug und schreibt auf beiden Seiten einen Timeline-Eintrag; der Ticket-Kunde wird über die Eskalation informiert. Ein bereits geschlossenes Ticket lässt sich nicht eskalieren.
Security & Datenschutzvorfall
isSecurityRelevant(boolean) — Incident betrifft die InformationssicherheitisDataBreach(boolean) — DSGVO-relevanter Datenschutzvorfall; das Setzen startet den DSB-GenehmigungslaufdsbNotifiedAt·dsbAcknowledgedAt·affectedDataSubjects— Zeitstempel und Umfang der Meldung
Drei harte Regeln: (1) Das Flag lässt sich über die normale Bearbeitung nicht zurücknehmen (403 DATA_BREACH_CLEARING_BLOCKED) — nur der DSB kann es im Genehmigungs-Workflow ablehnen, damit die DSGVO-Spur lückenlos bleibt. (2) Auf einem geschlossenen Incident kann es nicht gesetzt werden (422 DATA_BREACH_FLAG_ON_CLOSED): die 72-Stunden-Frist wird nur auf offenen Incidents überwacht, sie liefe sonst unbemerkt ab — erst wiedereröffnen, dann kennzeichnen. (3) Solange die DSB-Freigabe nicht vollständig nach der konfigurierten Strategie vorliegt, sind RESOLVED, PENDING_CLOSURE und CLOSED gesperrt (422 DATA_BREACH_ACK_REQUIRED).
DSGVO: Breach-Details durch den DSB
Der Datenschutzbeauftragte erfasst den Umfang über einen eigenen Endpunkt. Dafür genügt incidents.acknowledgeDataBreach — eine allgemeine Bearbeitungs-Berechtigung ist nicht nötig (und umgekehrt reicht sie allein nicht).
PATCH /api/incidents/:id/data-breach-details
// Request
{ "affectedDataSubjects": 1500 }
// Response
{ "id": "clx...", "number": "INC-2026-000123", "affectedDataSubjects": 1500, "version": 8 }
Ist der Incident nicht als Datenschutzvorfall gekennzeichnet, antwortet der Endpunkt 422 NOT_A_DATA_BREACH. Die Bewertungs-Kommentare der DSB-Genehmiger sind im Incident-Payload nur für Träger von incidents.acknowledgeDataBreach (und den jeweiligen Genehmiger selbst) sichtbar; alle anderen sehen die Genehmiger und deren Entscheidung, aber nicht den Kommentartext.
Kategorie-Management
Incident-Kategorien werden dynamisch über eine eigene API verwaltet. Jede Kategorie kann eine Evidence-Checklisten-Vorlage tragen.
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /api/incidents/categories | Liste als {data} (?includeInactive=true zeigt auch deaktivierte) |
POST | /api/incidents/categories | Kategorie erstellen → 201 |
PUT | /api/incidents/categories/:id | Kategorie aktualisieren |
DELETE | /api/incidents/categories/:id | Kategorie löschen → 204 |
Lesen darf, wer Incidents sieht (viewOwn ‖ viewAll), Kategorien verwaltet (incidents.manageCategories) oder SLA-Policies pflegt (settings.editSLA — der Policy-Dialog bietet die Kategorie-Zuordnung an). Schreiben verlangt incidents.manageCategories. Eine Kategorie, die noch an Incidents hängt, lässt sich nicht löschen (409 CATEGORY_IN_USE, mit incidentCount) — stattdessen deaktivieren.
SLA-Management
Incidents werden vom zentralen SLA-System getrackt — demselben, das auch Tickets und Problems bedient. Beim Anlegen wird die passende SLA-Policy für INCIDENT gewählt (kategorie-spezifisch, sonst Default) und deren Zielzeiten für die berechnete Priorität übernommen. Die Zielzeiten sind in der SLA-Policy konfigurierbar.
Die mitgelieferte Default-Policy „Default Incident SLA" setzt pro Priorität:
| Priority | Response (MTTA) | Resolution (MTTR) |
|---|---|---|
| P1 (Critical) | 15 Minuten | 1 Stunde |
| P2 (High) | 30 Minuten | 4 Stunden |
| P3 (Medium) | 2 Stunden | 24 Stunden |
| P4 (Low) | 8 Stunden | 48 Stunden |
Wichtig: Das sind Startwerte einer bearbeitbaren Policy, keine festen Systemgrenzen — jede Installation kann eigene Targets, Business Hours und Eskalationsstufen definieren. Ist der Policy eine Business-Hours-Definition zugeordnet, zählen nur Geschäftszeiten (Feiertage inklusive), sonst 24/7. Status in pauseOnStatus (Default ON_HOLD) pausiert die Uhr. SLA Management API
Response-SLA & Timestamps
Bei Incidents gilt die ITIL-Semantik: die Zuweisung bzw. Annahme erfüllt die Response-SLA (anders als bei Tickets, wo dafür eine öffentliche Agenten-Antwort nötig ist). Als Reaktion zählt jeder Status ab ACKNOWLEDGED — nicht nur der Wechsel genau dorthin. Wer von NEW direkt auf IN_PROGRESS springt, hat faktisch reagiert; die Antwortfrist ist damit erfüllt und eskaliert nicht weiter.
acknowledgedAt– Stoppt Response-TimerinProgressAt– Startet Resolution-TimermitigatedAt– Workaround aktivresolvedAt– Stoppt Resolution-TimerclosedAt– Final geschlossenonHoldEnteredAt·onHoldExitedAt·pausedTotalSec– Pausenzeiten
Erfüllung, Fristen, Eskalationslevel und Pausenzeit stehen im SLA-Tracking des Incidents (Feld slaTracking im Detail-Payload); historische MTTA-/MTTR-Kennzahlen und Erfüllungsquoten liefert GET /api/sla/report?entityType=INCIDENT (erfordert incidents.viewAll). Eine Prioritäts-Änderung berechnet die Fristen neu.
Fehlerbehandlung
| Error-Code | HTTP | Bedeutung |
|---|---|---|
INVALID_STATUS_TRANSITION | 422 | Übergang laut Matrix nicht erlaubt (Details nennen from/to und Grund) |
MITIGATION_REQUIRED | 422 | MITIGATED ohne Workaround oder Mitigationstext |
RESOLUTION_REQUIRED | 422 | RESOLVED ohne resolutionCode + rootCauseShort |
INVALID_RESOLUTION_CODE | 400 | Resolution-Code nicht in der Konfiguration |
DUPLICATE_ID_REQUIRED · DUPLICATE_SELF · DUPLICATE_CHAIN | 422 | Duplikat-Verknüpfung fehlt, zeigt auf sich selbst oder auf ein weiteres Duplikat |
MASTER_NOT_FOUND | 404 | Referenziertes Original-Incident existiert nicht |
PIR_REQUIRED | 422 | Abschluss-Antrag auf P1/P2 ohne abgeschlossenes PIR |
PIR_REQUIRED_EVIDENCE_UNCHECKED | 422 | PIR-Abschluss mit offenen Pflicht-Nachweisen |
APPROVAL_REQUIRED | 422 | CLOSED ohne vollständige Genehmigung |
DATA_BREACH_ACK_REQUIRED | 422 | Lösen/Schließen vor der DSB-Freigabe |
DATA_BREACH_FLAG_ON_CLOSED · MAJOR_TOGGLE_ON_CLOSED | 422 | Data-Breach-Flag bzw. Major-Toggle auf geschlossenem Incident |
DATA_BREACH_CLEARING_BLOCKED | 403 | Data-Breach-Flag zurücknehmen (nur über den DSB-Workflow) |
NOT_A_DATA_BREACH | 422 | Breach-Details auf einem Incident ohne das Flag |
EVIDENCE_TEMPLATE_MISSING | 422 | Keine Checklisten-Vorlage für Kategorie oder global hinterlegt |
EVIDENCE_CHECKLIST_FROZEN | 409 | Checkliste bei abgeschlossenem PIR eingefroren |
EVIDENCE_CHECKLIST_NOT_INITIALIZED · EVIDENCE_TEMPLATE_ITEM_IMMUTABLE · EVIDENCE_UNCHECK_REASON_REQUIRED | 400 | Checkliste nicht angelegt, Vorlagen-Position entfernt, Häkchen ohne Grund entfernt |
VERSION_CONFLICT | 409 | Zwischenzeitlich geänderter Stand (Optimistic Locking) |
REOPEN_NOT_TERMINAL · REOPEN_RESOLUTION_LOCKED · REOPEN_REQUIRES_APPROVAL · REOPEN_NO_APPROVERS · REOPEN_WINDOW_EXPIRED · REOPEN_LIMIT_REACHED · REOPEN_REASON_REQUIRED | 400 | Reopen-Governance (Quelle, Sperre, Genehmigung, Fenster, Limit, Grund) |
BULK_MESSAGE_NOT_MAJOR | 400 | Sammel-Nachricht auf einem Nicht-Major-Incident |
REPORTER_ID_REQUIRED · AFFECTED_SERVICES_REQUIRED | 400 | Pflichtangaben beim Anlegen |
AGENT_GROUP_NOT_FOUND · AGENT_GROUP_INACTIVE · AGENT_GROUP_ENTITY_TYPE_MISMATCH | 404 / 400 | Zielgruppe fehlt, ist archiviert oder unterstützt den Typ INCIDENT nicht |
INCIDENT_ALREADY_CLOSED | 400 | Kommentar auf einem geschlossenen Incident |
INCIDENT_NOT_FOUND | 404 | Unbekannt — oder für den Aufrufer nicht sichtbar (Löschen/Wiederherstellen) |
{
"error": "PIR is mandatory for P1/P2 incidents before closure",
"errorCode": "PIR_REQUIRED"
}
incidents.viewAll/viewOwn/viewDeletedincidents.create/editAll/editOwnincidents.changeStatus/changePriority/declareMajorincidents.assign/assignableincidents.requestClosure/approveClosureincidents.reopen/reopenOverride/approveReopenincidents.delete/restoreincidents.acknowledgeDataBreach/viewPIRincidents.bulkMessage/manageCategories/reportingincidents.linkToTickets/linkToProblems/linkToChanges/linkToAssets/linkToKB
Auth-/Rollenmodell: Permissions & RBAC
- ✓ Priority-Matrix (Impact × Urgency)
- ✓ Response-SLA schon durch Annahme erfüllt
- ✓ Abschluss-Genehmigung statt einfachem Schließen
- ✓ PIR-Pflicht bei P1/P2
- ✓ Major-Incident-Flag + Sammel-Nachricht
- ✓ Workaround-Tracking
Kritische Rechte: Bei delete, restore, declareMajor, approveClosure, approveReopen und acknowledgeDataBreach wirkt ein Entzug des Rechts sofort, auch bei bereits angemeldeten Benutzern.
Code-Beispiele
Kompletter Incident-Lifecycle (JavaScript)
// 1. P1-Incident anlegen
const incident = await fetch('https://your-instance.com/api/incidents', {
method: 'POST',
credentials: 'include', // HttpOnly Cookie auth
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Production database offline',
description: 'Database not responding, all services affected by timeouts.',
businessImpact: 'All services down',
impact: 'HIGH',
urgency: 'HIGH',
categoryId: 'clx-incident-category-id',
affectedServices: ['API', 'Frontend']
})
}).then(r => r.json());
console.log('Priority:', incident.priority); // P1
// 2. Annehmen (erfüllt die Response-SLA)
await fetch(`https://your-instance.com/api/incidents/${incident.id}`, {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'ACKNOWLEDGED' })
});
// 3. Bearbeitung starten (startet den Resolution-Timer)
await fetch(`https://your-instance.com/api/incidents/${incident.id}`, {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'IN_PROGRESS',
currentMitigation: 'Investigating database logs'
})
});
// 4. Workaround aktivieren
await fetch(`https://your-instance.com/api/incidents/${incident.id}`, {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'MITIGATED',
workaroundAvailable: true,
currentMitigation: 'Failover to secondary cluster'
})
});
// 5. Lösen (resolutionCode + rootCauseShort sind Pflicht)
await fetch(`https://your-instance.com/api/incidents/${incident.id}`, {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'RESOLVED',
resolutionCode: 'FIXED',
rootCauseShort: 'Disk full on primary DB server'
})
});
// 6. PIR abschließen (Pflicht bei P1/P2)
await fetch(`https://your-instance.com/api/incidents/${incident.id}`, {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
pirCompleted: true,
lessonsLearned: 'Added disk space monitoring alerts'
})
});
// 7. Abschluss beantragen — Genehmiger kommen aus der Approval-Gruppe
const pending = await fetch(`https://your-instance.com/api/incidents/${incident.id}`, {
method: 'PATCH',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
status: 'PENDING_CLOSURE',
closureComment: 'Incident resolved, PIR complete'
})
}).then(r => r.json());
console.log(pending.status, pending.warnings); // PENDING_CLOSURE, ggf. Hinweis-Codes
// 8. Ein Genehmiger entscheidet (eigene Session) → Status wird CLOSED
// POST /api/approvals/{approvalId}/decide { "decision": true }
Attachments
Incidents nutzen das Unified Attachment System für PIR-Reports, Screenshots, etc.:
# Upload file to incident
POST /api/attachments/INCIDENT/:incidentId
# All attachments of an incident
GET /api/attachments/INCIDENT/:incidentId
# Download
GET /api/attachments/:id/download
Details: Siehe Attachments & File Settings API für Zero-Trust Virus-Scan, File-Settings und Retention-Policies.
Problems API →
Erfahre mehr über die Problems API
Entity Linking API →
Incidents mit Tickets/Problems/Changes/Assets verknüpfen
Reopen & Lifecycle →
Reopen mit Genehmigung, incidents.reopen, INCIDENT_REOPENED