Eviworx
Docs

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.

🚨
Funktionen
✓ Priority-Matrix (Impact × Urgency → P1–P4)
✓ SLA-Tracking für Reaktion und Lösung
✓ Abschluss-Genehmigung (Unified Approvals)
✓ Post-Incident-Review bei P1/P2 Pflicht
✓ Evidence-Checklisten aus Kategorie-Vorlage
✓ Major-Incident-Flag (manuell gesetzt)
✓ Breach-Details durch den DSB (eigenes Recht)
✓ Reopen standardmäßig mit Genehmigung
✓ Papierkorb mit Wiederherstellung
✓ Verknüpfungen (Tickets, Problems, Changes, Assets, KB)

Endpoints Übersicht

Method Endpoint Beschreibung
GET/api/incidentsListe mit Filtern ({data, pagination}); erfordert incidents.viewAll oder viewOwn, sonst 403
GET/api/incidents/statsTab-Counts (all, myIncidents, assigned, major, pendingApprovals)
GET/api/incidents/:idEinzelnes Incident (inkl. Activities, Approver, SLA-Tracking)
POST/api/incidentsNeues Incident erstellen (incidents.create) → 201
PATCH/api/incidents/:idIncident aktualisieren (Rechte je Feldgruppe, Optimistic Locking)
DELETE/api/incidents/:idIncident löschen (Soft-Delete, incidents.delete) → 204
POST/api/incidents/:id/restoreAus dem Papierkorb zurückholen (restore + viewDeleted) → 204
POST/api/incidents/:id/reopenGeschlossenes Incident wiedereröffnen (incidents.reopen; CLOSED → ACKNOWLEDGED, standardmäßig Approval-pflichtig)
PATCH/api/incidents/:id/assignBearbeiter setzen/entfernen (incidents.assign)
POST/api/incidents/from-ticketTicket zu einem Incident eskalieren (incidents.create) → 201
POST/api/incidents/:id/bulk-messageSammel-Nachricht an die Kunden verknüpfter Tickets (incidents.bulkMessage + Sicht, nur Major)
GET/api/incidents/:id/suggest-ticketsVerknüpfbare Tickets vorschlagen (zusätzlich incidents.linkToTickets)
GET/api/incidents/:id/impact-treeDownstream-Wirkung vor dem Abschluss (verknüpfte Tickets + SLA)
GET/api/incidents/:id/unified-timelineTimeline über Incident + verknüpfte Tickets/Problems (?limit/?offset)
POST/api/incidents/:id/activityKommentar hinzufügen → 201
POST/api/incidents/:id/evidence-checklist/initializeEvidence-Checkliste aus der Vorlage anlegen
PUT/api/incidents/:id/evidence-checklistEvidence-Checkliste aktualisieren (Items-Vollmenge)
PATCH/api/incidents/:id/data-breach-detailsDSGVO: 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
NEWACKNOWLEDGED · IN_PROGRESS · ON_HOLD · MITIGATED · RESOLVEDincidents.changeStatus
ACKNOWLEDGEDIN_PROGRESS · ON_HOLD · MITIGATED · RESOLVEDincidents.changeStatus
IN_PROGRESSON_HOLD · MITIGATED · RESOLVEDincidents.changeStatus
ON_HOLDNEW · ACKNOWLEDGED · IN_PROGRESS · MITIGATED · RESOLVEDincidents.changeStatus
MITIGATEDON_HOLD · RESOLVEDMITIGATED verlangt workaroundAvailable=true ODER currentMitigation
RESOLVEDON_HOLD · PENDING_CLOSURE · CLOSEDRESOLVED verlangt resolutionCode + rootCauseShort; PENDING_CLOSURE verlangt incidents.requestClosure
PENDING_CLOSURERESOLVED · CLOSEDEntscheidung der Genehmiger über die Approvals API (changeStatus genügt nicht)
CLOSEDACKNOWLEDGEDeinziger 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
HIGHP2P1P1
MEDIUMP3P2P1
LOWP4P3P2

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 | HIGH
  • categoryId — ID einer dynamischen Incident-Kategorie
  • affectedServices — 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 · assignedGroupId
  • isSecurityRelevant · 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:

FelderZusätzliches Recht
title, description, businessImpact, scope, categoryId, affectedServices, workaroundAvailable, currentMitigation, nextUpdateETA, isSecurityRelevant, PIR-Felder— (Basis-Edit genügt)
status (operativ)incidents.changeStatus
status: PENDING_CLOSUREincidents.requestClosure
status CLOSED/RESOLVED aus PENDING_CLOSUREEntscheidung der Genehmiger (changeStatus genügt nicht)
status: ACKNOWLEDGED aus CLOSED (Reopen)incidents.reopen
impact, urgencyincidents.changePriority
majorIncidentincidents.declareMajor
assignedToId, assignedGroupIdincidents.assign
affectedDataSubjectsincidents.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 } }
  ]
}
CodeBedeutung
OPEN_LINKED_TICKETSBeim Lösen/Schließen sind noch offene verknüpfte Tickets vorhanden (params.count)
CLOSURE_APPROVAL_NO_APPROVERSDer Abschluss wurde beantragt, aber die konfigurierte Genehmigungs-Gruppe hat kein Mitglied — der Incident bleibt in PENDING_CLOSURE
CLOSURE_APPROVAL_INIT_FAILEDDie 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.urgencyEnum-Filter (Großschreibung; Operatoren eq/neq/in/notIn)
f.categoryId, f.assignedToId, f.assignedGroupId, f.reporterIdZuordnungs-Filter (eq/in/isNull/isNotNull)
f.majorIncident, f.isSecurityRelevant, f.isDataBreachFlag-Filter (true/false)
f.detectedAt, f.createdAt, f.updatedAt, f.resolvedAtZeit-Filter (gt/gte/lt/lte/between/relative)
f.number, f.titleText-Filter (eq/contains/startsWith)
qSuche über Nummer, Titel, Beschreibung, Business-Impact
page / per / sortSeitenweise Ausgabe (per Standard 25, maximal 200; Standard-Sortierung updatedAt absteigend)
deleted=1Papierkorb: NUR gelöschte Incidents (erfordert incidents.viewDeleted)
includeDeleted=trueMischliste 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 automatisch
  • pirReopenReason — 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 / checkedBy setzt 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 Informationssicherheit
  • isDataBreach (boolean) — DSGVO-relevanter Datenschutzvorfall; das Setzen startet den DSB-Genehmigungslauf
  • dsbNotifiedAt · 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/categoriesListe als {data} (?includeInactive=true zeigt auch deaktivierte)
POST/api/incidents/categoriesKategorie erstellen → 201
PUT/api/incidents/categories/:idKategorie aktualisieren
DELETE/api/incidents/categories/:idKategorie 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 Minuten1 Stunde
P2 (High)30 Minuten4 Stunden
P3 (Medium)2 Stunden24 Stunden
P4 (Low)8 Stunden48 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-Timer
  • inProgressAt – Startet Resolution-Timer
  • mitigatedAt – Workaround aktiv
  • resolvedAt – Stoppt Resolution-Timer
  • closedAt – Final geschlossen
  • onHoldEnteredAt · 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_TRANSITION422Übergang laut Matrix nicht erlaubt (Details nennen from/to und Grund)
MITIGATION_REQUIRED422MITIGATED ohne Workaround oder Mitigationstext
RESOLUTION_REQUIRED422RESOLVED ohne resolutionCode + rootCauseShort
INVALID_RESOLUTION_CODE400Resolution-Code nicht in der Konfiguration
DUPLICATE_ID_REQUIRED · DUPLICATE_SELF · DUPLICATE_CHAIN422Duplikat-Verknüpfung fehlt, zeigt auf sich selbst oder auf ein weiteres Duplikat
MASTER_NOT_FOUND404Referenziertes Original-Incident existiert nicht
PIR_REQUIRED422Abschluss-Antrag auf P1/P2 ohne abgeschlossenes PIR
PIR_REQUIRED_EVIDENCE_UNCHECKED422PIR-Abschluss mit offenen Pflicht-Nachweisen
APPROVAL_REQUIRED422CLOSED ohne vollständige Genehmigung
DATA_BREACH_ACK_REQUIRED422Lösen/Schließen vor der DSB-Freigabe
DATA_BREACH_FLAG_ON_CLOSED · MAJOR_TOGGLE_ON_CLOSED422Data-Breach-Flag bzw. Major-Toggle auf geschlossenem Incident
DATA_BREACH_CLEARING_BLOCKED403Data-Breach-Flag zurücknehmen (nur über den DSB-Workflow)
NOT_A_DATA_BREACH422Breach-Details auf einem Incident ohne das Flag
EVIDENCE_TEMPLATE_MISSING422Keine Checklisten-Vorlage für Kategorie oder global hinterlegt
EVIDENCE_CHECKLIST_FROZEN409Checkliste bei abgeschlossenem PIR eingefroren
EVIDENCE_CHECKLIST_NOT_INITIALIZED · EVIDENCE_TEMPLATE_ITEM_IMMUTABLE · EVIDENCE_UNCHECK_REASON_REQUIRED400Checkliste nicht angelegt, Vorlagen-Position entfernt, Häkchen ohne Grund entfernt
VERSION_CONFLICT409Zwischenzeitlich 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_REQUIRED400Reopen-Governance (Quelle, Sperre, Genehmigung, Fenster, Limit, Grund)
BULK_MESSAGE_NOT_MAJOR400Sammel-Nachricht auf einem Nicht-Major-Incident
REPORTER_ID_REQUIRED · AFFECTED_SERVICES_REQUIRED400Pflichtangaben beim Anlegen
AGENT_GROUP_NOT_FOUND · AGENT_GROUP_INACTIVE · AGENT_GROUP_ENTITY_TYPE_MISMATCH404 / 400Zielgruppe fehlt, ist archiviert oder unterstützt den Typ INCIDENT nicht
INCIDENT_ALREADY_CLOSED400Kommentar auf einem geschlossenen Incident
INCIDENT_NOT_FOUND404Unbekannt — oder für den Aufrufer nicht sichtbar (Löschen/Wiederherstellen)
{
  "error": "PIR is mandatory for P1/P2 incidents before closure",
  "errorCode": "PIR_REQUIRED"
}
🔐
Berechtigungen (RBAC)
  • incidents.viewAll / viewOwn / viewDeleted
  • incidents.create / editAll / editOwn
  • incidents.changeStatus / changePriority / declareMajor
  • incidents.assign / assignable
  • incidents.requestClosure / approveClosure
  • incidents.reopen / reopenOverride / approveReopen
  • incidents.delete / restore
  • incidents.acknowledgeDataBreach / viewPIR
  • incidents.bulkMessage / manageCategories / reporting
  • incidents.linkToTickets / linkToProblems / linkToChanges / linkToAssets / linkToKB

Auth-/Rollenmodell: Permissions & RBAC

📊
Wichtige Unterschiede zu Tickets
  • ✓ 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.
Nächster Schritt

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