Incidents API
The Incidents API enables management of IT disruptions with SLA tracking, priority matrix, closure approval, post-incident review (PIR), GDPR/data breach flow, category management and evidence checklists.
Endpoints Overview
| Method | Endpoint | Description |
|---|---|---|
GET | /api/incidents | List with filters ({data, pagination}); requires incidents.viewAll or viewOwn, otherwise 403 |
GET | /api/incidents/stats | Tab counts (all, myIncidents, assigned, major, pendingApprovals) |
GET | /api/incidents/:id | Single incident (incl. activities, approvers, SLA tracking) |
POST | /api/incidents | Create new incident (incidents.create) → 201 |
PATCH | /api/incidents/:id | Update incident (permissions per field group, optimistic locking) |
DELETE | /api/incidents/:id | Delete incident (soft-delete, incidents.delete) → 204 |
POST | /api/incidents/:id/restore | Restore from trash (restore + viewDeleted) → 204 |
POST | /api/incidents/:id/reopen | Reopen a closed incident (incidents.reopen; CLOSED → ACKNOWLEDGED, requires approval by default) |
PATCH | /api/incidents/:id/assign | Set/clear assignee (incidents.assign) |
POST | /api/incidents/from-ticket | Escalate a ticket into an incident (incidents.create) → 201 |
POST | /api/incidents/:id/bulk-message | Bulk message to the customers of linked tickets (incidents.bulkMessage + visibility, major only) |
GET | /api/incidents/:id/suggest-tickets | Suggest linkable tickets (additionally incidents.linkToTickets) |
GET | /api/incidents/:id/impact-tree | Downstream impact before closure (linked tickets + SLA) |
GET | /api/incidents/:id/unified-timeline | Timeline across incident + linked tickets/problems (?limit/?offset) |
POST | /api/incidents/:id/activity | Add comment → 201 |
POST | /api/incidents/:id/evidence-checklist/initialize | Create evidence checklist from the template |
PUT | /api/incidents/:id/evidence-checklist | Update evidence checklist (full item set) |
PATCH | /api/incidents/:id/data-breach-details | GDPR: annotate breach details (DPO role) |
ID or number: Every route of the domain accepts either the technical ID or the incident number (INC-2026-000123) in place of :id — the format decides which of the two is meant. Write calls require a signed-in user (API keys are rejected); read calls check the visibility permissions viewAll/viewOwn.
Enum values in upper case: Status, priority, impact, urgency and detectionSource are sent and returned in upper case (NEW, HIGH, P1, MONITORING …). Lower-case values return 400.
State Machine
Incidents go through the following statuses:
NEW → ACKNOWLEDGED → IN_PROGRESS → [ON_HOLD] → MITIGATED → RESOLVED → PENDING_CLOSURE → CLOSED Status descriptions: • NEW = Newly reported, not yet acknowledged • ACKNOWLEDGED = Acknowledged, SLA response timer stopped • IN_PROGRESS = In progress, SLA resolution timer running • ON_HOLD = On hold (SLA timer paused) • MITIGATED = Workaround active, disruption bypassed • RESOLVED = Resolved, root cause eliminated • PENDING_CLOSURE = Closure requested, waiting for approval • CLOSED = Closed & archived
Allowed Transitions
| From Status | To Status | Condition / permission |
|---|---|---|
| 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 requires workaroundAvailable=true OR currentMitigation |
| RESOLVED | ON_HOLD · PENDING_CLOSURE · CLOSED | RESOLVED requires resolutionCode + rootCauseShort; PENDING_CLOSURE requires incidents.requestClosure |
| PENDING_CLOSURE | RESOLVED · CLOSED | Approvers' decision via the Approvals API (changeStatus is not enough) |
| CLOSED | ACKNOWLEDGED | the only reopen path — incidents.reopen |
ON_HOLD: Reachable from all open statuses and from RESOLVED. From CLOSED the only way back is a reopen (incidents.reopen); from PENDING_CLOSURE only a rejection by the approvers (back to RESOLVED).
Priority Matrix (Impact × Urgency)
Priority is calculated server-side from impact and urgency — on creation and whenever one of the two fields changes:
| Impact ↓ / Urgency → | LOW | MEDIUM | HIGH |
|---|---|---|---|
| HIGH | P2 | P1 | P1 |
| MEDIUM | P3 | P2 | P1 |
| LOW | P4 | P3 | P2 |
Major Incident: majorIncident is a deliberate toggle, not an automatism: the flag does NOT follow the calculated priority but is set explicitly and requires incidents.declareMajor (a critical action — setting it triggers a NOC alert and stakeholder notifications). It cannot be toggled on a closed incident (422 MAJOR_TOGGLE_ON_CLOSED).
Create Incident
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
}
Required Fields
title(5–200 characters)description(20–5000 characters)businessImpact(10–1000 characters)impact·urgency— LOW | MEDIUM | HIGHcategoryId— ID of a dynamic incident categoryaffectedServices— at least one entry, at most 50 (400 AFFECTED_SERVICES_REQUIRED on an empty list)
Optional Fields
detectionSource— MONITORING | USER | INTERNAL | THIRD_PARTY (default INTERNAL)scope(max. 500 characters) ·assignedToId·assignedGroupIdisSecurityRelevant·isDataBreach·majorIncident(each defaults to false)ignoreSubstitution— deliberate assignment to an absent person (skips the substitute redirect)idempotencyKey(UUID) — a repeated call with the same key returns the existing incident instead of creating a second one
Reporter (reporterId): For a signed-in user the server pins the reporter to the caller; a value sent in the body is ignored. That way nobody can create incidents under someone else's name. Only API-key calls must supply reporterId — without it the server answers 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"
}
Update Incident
Request
PATCH /api/incidents/:id
{
"status": "ACKNOWLEDGED",
"currentMitigation": "Failover to secondary database activated",
"nextUpdateETA": "2026-01-27T15:00:00Z",
"version": 3
}
Field Groups and Their Permissions
The base permission is incidents.editAll or incidents.editOwn (reporter or assignee). On top of that every field group requires its own permission — being allowed to edit does not yet allow assigning or escalating:
| Fields | Additional permission |
|---|---|
title, description, businessImpact, scope, categoryId, affectedServices, workaroundAvailable, currentMitigation, nextUpdateETA, isSecurityRelevant, PIR-fields | — (base edit is enough) |
status (operational) | incidents.changeStatus |
status: PENDING_CLOSURE | incidents.requestClosure |
status CLOSED/RESOLVED from PENDING_CLOSURE | approvers' decision (changeStatus is not enough) |
status: ACKNOWLEDGED from CLOSED (reopen) | incidents.reopen |
impact, urgency | incidents.changePriority |
majorIncident | incidents.declareMajor |
assignedToId, assignedGroupId | incidents.assign |
affectedDataSubjects | incidents.acknowledgeDataBreach |
Closure rules check the state after the change: Priority, the PIR checkbox and the data-breach flag are applied first and only then checked against the closure rules. Sending {impact:HIGH, urgency:HIGH, status:PENDING_CLOSURE} in one call therefore correctly lands in the PIR requirement of the new P1 — instead of closing past the old priority. The status itself stays the old one for the transition check: a transition is always evaluated from the current status.
Response: incident + warnings
The response is the updated incident. If there are advisories, it additionally carries a warnings array of codes (the display language is the client's decision, not the server's):
{
"id": "clx...",
"status": "PENDING_CLOSURE",
"version": 4,
"warnings": [
{ "code": "OPEN_LINKED_TICKETS", "params": { "count": 3 } }
]
}
| Code | Meaning |
|---|---|
OPEN_LINKED_TICKETS | On resolve/close there are still open linked tickets (params.count) |
CLOSURE_APPROVAL_NO_APPROVERS | Closure was requested but the configured approval group has no member — the incident stays in PENDING_CLOSURE |
CLOSURE_APPROVAL_INIT_FAILED | The approval round could not be created |
Optimistic Locking: If version is sent and does not match the server state, the server answers 409 VERSION_CONFLICT (with expectedVersion and currentVersion in the details). Every successful mutation increments version.
Closure Workflow
Whether a closure needs approval is decided by the closure policy of Unified Approvals (configurable per priority, with group and strategy). If no approval is required the incident is closed directly; otherwise PENDING_CLOSURE starts an approval round.
Step 1: Request Closure
PATCH /api/incidents/:id
{
"status": "PENDING_CLOSURE",
"closureComment": "Incident has been resolved. PIR is complete.",
"closureChecklist": {
"pirCompleted": true,
"relationshipsVerified": true,
"communicationDone": true
}
}
Approvers come from the configured approval group and are assigned automatically — they are not named in the request. For P1/P2 pirCompleted must already be true, otherwise 422 PIR_REQUIRED.
Step 2: Decide
POST /api/approvals/:id/decide
{
"decision": true,
"comment": "Approved. All documentation complete."
}
Once the round is complete according to the configured strategy, the system sets the incident to CLOSED. A rejection returns it to RESOLVED (with closureRejectionReason) and clears the approval round so a new request starts fresh. The approval state lives in the incident's detail payload (field approvers). Approvals API
No approver, no closure: If approval is mandatory but no approver is assigned, the incident stays in PENDING_CLOSURE. The requester receives the CLOSURE_APPROVAL_NO_APPROVERS warning, and an audit entry is written so the missing configuration is noticed.
Reopen
POST /api/incidents/:id/reopen
{
"reasonCode": "SYMPTOM_RETURNED",
"note": "Same outage pattern reappeared after 20 minutes.",
"version": 7
}
Reopening requires a dedicated permission (incidents.reopen) and is only possible from CLOSED. If the incident was closed as a duplicate it stays locked (400 REOPEN_RESOLUTION_LOCKED). The policy additionally checks window, maximum count and reason requirement; incidents.reopenOverride bypasses window and limit, never the reason or approval requirement.
The response comes in two shapes:
// 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": "..." }
The reopen approval runs through a dedicated group (type INCIDENT_REOPEN) and its own approver permission incidents.approveReopen — separate from the closure approval, so that "may close" and "may reopen" can be different groups of people. If there is no approver the reopen is refused (400 REOPEN_NO_APPROVERS). After sign-off the system sets the incident to ACKNOWLEDGED and resets the resolution and closure fields, so a renewed closure runs through a fresh approval round. Reopen & Lifecycle
List & Filters
GET /api/incidents?f.status=in:NEW,ACKNOWLEDGED&f.priority=P1&page=1&per=20&sort=detectedAt:desc
A filter has the form f.<field>=<operator>:<value>; without an operator prefix equality applies (f.priority=P1). Multi-value operators take a comma list (in:A,B), isNull and isNotNull stand without a value. Sorting uses sort=<field>:asc|desc.
RBAC-filtered (viewAll/viewOwn). Response:
{
"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 | Description |
|---|---|
f.status, f.priority, f.impact, f.urgency | Enum filters (upper case; operators eq/neq/in/notIn) |
f.categoryId, f.assignedToId, f.assignedGroupId, f.reporterId | Assignment filters (eq/in/isNull/isNotNull) |
f.majorIncident, f.isSecurityRelevant, f.isDataBreach | Flag filters (true/false) |
f.detectedAt, f.createdAt, f.updatedAt, f.resolvedAt | Time filters (gt/gte/lt/lte/between/relative) |
f.number, f.title | Text filters (eq/contains/startsWith) |
q | Search across number, title, description, business impact |
page / per / sort | Paging (per defaults to 25, max 200; default sort updatedAt descending) |
deleted=1 | Trash: ONLY deleted incidents (requires incidents.viewDeleted) |
includeDeleted=true | Mixed list incl. deleted ones (requires incidents.viewDeleted) |
Without a view permission: 403. Anyone holding neither incidents.viewAll nor viewOwn gets 403 on the list. GET /incidents/stats returns zero values in that case.
Delete & Trash
DELETE /api/incidents/:id → 204 No Content
POST /api/incidents/:id/restore → 204 No Content
Deletion is a soft-delete and a critical action (incidents.delete, audited). Restoring requires incidents.restore (likewise critical) plus incidents.viewDeleted. Both paths also check visibility of the incident: whoever may not see it gets 404, so its existence is not revealed.
SLA on delete and restore: While an open incident is attached to a ticket, that ticket's SLA clock is paused. Deleting the incident restarts those clocks; the links remain. If the incident comes back open on restore, the pauses are re-applied. The incident's own SLA tracking is ended on delete and set up freshly on restore, so the monitor does not immediately escalate it over deadlines that expired in the meantime.
Assign Incident
Request
PATCH /api/incidents/:id/assign
{
"assignedToId": "clx...",
"ignoreSubstitution": false
}
assignedToId: null clears the assignment. The assignee must hold incidents.assignable; if absent, the system redirects to their configured substitute — ignoreSubstitution: true deliberately assigns to the absent person anyway. Group assignment runs through PATCH /api/incidents/:id (field assignedGroupId, same permission 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 states whether the assignment actually changed — with a substitute redirect the target can differ from what was requested.
Post-Incident Review (PIR)
PIR is mandatory for P1 and P2 and must be completed before the closure request. The PIR fields live under pir in the response but are written flat via PATCH:
{
"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) — sets/clears pirCompletedAt automaticallypirReopenReason— reason when withdrawing a completed PIR (lands in the timeline and the audit trail)rcaTimeline,lessonsLearned,longTermActions(max. 2000 characters each),isoControlReference(max. 200)
PIR and evidence are coupled: As long as mandatory evidence checklist items are open, the PIR cannot be completed (422 PIR_REQUIRED_EVIDENCE_UNCHECKED, with count and labels). Conversely a completed PIR freezes the checklist (409 EVIDENCE_CHECKLIST_FROZEN) — withdraw the PIR first, then change the evidence.
Evidence Checklists
The checklist is created per incident from a template — the template of the incident category or, if none is configured there, the global default. Creating and changing the checklist requires the same permission as editing the incident.
# 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
}
- Items from the template (source: TEMPLATE) cannot be removed (400
EVIDENCE_TEMPLATE_ITEM_IMMUTABLE) - Unchecking an item requires a reason (400
EVIDENCE_UNCHECK_REASON_REQUIRED) checkedAt/checkedByare set by the server only- version is optional; if sent, an outdated state answers 409 — otherwise two people working in parallel silently overwrite each other
- If neither the category nor the global default carries a template, initialize answers 422
EVIDENCE_TEMPLATE_MISSING
Duplicate Detection
An incident can be marked as a duplicate of another one on resolution. The resolution code drives this: if the code requires a link (e.g. DUPLICATE), duplicateOfId is mandatory.
{
"status": "RESOLVED",
"resolutionCode": "DUPLICATE",
"rootCauseShort": "Same root cause as INC-2026-000100",
"duplicateOfId": "clx-master-incident-id"
}
- An incident cannot be a duplicate of itself (422
DUPLICATE_SELF) - Chains are excluded: if the target is itself a duplicate, the error points to the original (422
DUPLICATE_CHAIN) - An incident closed this way stays locked — it cannot be reopened, only closed forward
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 requires either workaroundAvailable = true or text in currentMitigation (422 MITIGATION_REQUIRED) — the status should not be set without a documented workaround. nextUpdateETA is the promised next status update; if it passes without one, a notification is sent.
Timeline & Comments
Every change writes a timeline entry. The entries come with the incident's detail payload (field activities, newest first); the list does not include them.
{
"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"
}
]
}
Add a Comment
POST /api/incidents/:id/activity → 201
{
"details": "Vendor confirmed a firmware bug; patch expected tonight."
}
details is mandatory (1–1000 characters), data optional. A closed incident does not accept comments (400 INCIDENT_ALREADY_CLOSED) — the history stays readable but cannot be extended.
Unified Timeline
GET /api/incidents/:id/unified-timeline?limit=50&offset=0
Merges the incident's entries with those of linked tickets and problems into one chronological list ({timeline, total, hasMore}, limit 1–200, default 50). Every foreign entry is checked individually: tickets and problems the caller may not see never appear.
Linked Entities in the Payload
Links appear in the payload as ID arrays; the linking endpoints provide the content. The only embedded objects are the problem references (status display in the sidebar) — as slim references without titles:
{
"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
}
Full data via the linking endpoints: Titles and customer data of linked records come from the linking endpoints, which check the caller's visibility per entry. The incident payload therefore contains IDs only; duplicateOf names only the number and status of the master incident, the problem reference only number, status and priority. Entity Linking API
Ticket Suggestions & Impact Tree
GET /:id/suggest-tickets— suggests open tickets of the last 48 hours matching the affected services and not yet linked. Because this picker shows ticket titles and customers, it additionally requires incidents.linkToTickets, and every candidate is checked individually against the ticket visibility. Response:{suggestions, total}GET /:id/impact-tree— shows the linked tickets with their SLA state before closure, and how many SLA clocks the closure will restart (summary.slasToResume). Rows without read permission appear as accessible: false instead of vanishing — the scope stays visible, the content does not.
Bulk Message on 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 }
Reaches the customers of all linked tickets plus their CC participants (deduplicated by email address); every affected ticket receives a timeline entry with the wording. Only possible on major incidents (400 BULK_MESSAGE_NOT_MAJOR). message 10–5000, subject optional 5–200 characters.
Permission plus visibility: incidents.bulkMessage is a global permission. On top, the caller must be allowed to see this incident (otherwise 403). Recipients of deleted tickets are not addressed.
Escalate a Ticket into an Incident
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
}
Creates the incident, links it to the ticket, carries over the customer reference and writes a timeline entry on both sides; the ticket customer is informed about the escalation. An already closed ticket cannot be escalated.
Security & Data Breach
isSecurityRelevant(boolean) — incident affects information securityisDataBreach(boolean) — GDPR-relevant data breach; setting it starts the DPO approval rundsbNotifiedAt·dsbAcknowledgedAt·affectedDataSubjects— timestamps and scope of the notification
Three hard rules: (1) The flag cannot be withdrawn through normal editing (403 DATA_BREACH_CLEARING_BLOCKED) — only the DPO can reject it in the approval workflow, keeping the GDPR trail complete. (2) It cannot be set on a closed incident (422 DATA_BREACH_FLAG_ON_CLOSED): the 72-hour deadline is only monitored on open incidents and would otherwise expire unnoticed — reopen first, then flag. (3) As long as the DPO sign-off is not complete under the configured strategy, RESOLVED, PENDING_CLOSURE and CLOSED are blocked (422 DATA_BREACH_ACK_REQUIRED).
GDPR: Breach Details by the DPO
The data protection officer records the scope via a dedicated endpoint. incidents.acknowledgeDataBreach is sufficient — a general edit permission is not required (and conversely not sufficient on its own).
PATCH /api/incidents/:id/data-breach-details
// Request
{ "affectedDataSubjects": 1500 }
// Response
{ "id": "clx...", "number": "INC-2026-000123", "affectedDataSubjects": 1500, "version": 8 }
If the incident is not flagged as a data breach, the endpoint answers 422 NOT_A_DATA_BREACH. The assessment comments of the DPO approvers are visible in the incident payload only to holders of incidents.acknowledgeDataBreach (and to the respective approver themselves); everyone else sees the approvers and their decision, but not the comment text.
Category Management
Incident categories are managed dynamically via a dedicated API. Each category can carry an evidence checklist template.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/incidents/categories | List as {data} (?includeInactive=true also shows deactivated ones) |
POST | /api/incidents/categories | Create category → 201 |
PUT | /api/incidents/categories/:id | Update category |
DELETE | /api/incidents/categories/:id | Delete category → 204 |
Reading is allowed for anyone who sees incidents (viewOwn ‖ viewAll), manages categories (incidents.manageCategories) or maintains SLA policies (settings.editSLA — the policy dialog offers the category assignment). Writing requires incidents.manageCategories. A category still attached to incidents cannot be deleted (409 CATEGORY_IN_USE, with incidentCount) — deactivate it instead.
SLA Management
Incidents are tracked by the central SLA system — the same one serving tickets and problems. On creation the matching SLA policy for INCIDENT is selected (category-specific, otherwise default) and its target times for the calculated priority are applied. Target times are configurable in the SLA policy.
The shipped default policy "Default Incident SLA" sets per priority:
| Priority | Response (MTTA) | Resolution (MTTR) |
|---|---|---|
| P1 (Critical) | 15 minutes | 1 hour |
| P2 (High) | 30 minutes | 4 hours |
| P3 (Medium) | 2 hours | 24 hours |
| P4 (Low) | 8 hours | 48 hours |
Important: These are the starting values of an editable policy, not hard system limits — every installation can define its own targets, business hours and escalation levels. If the policy has business hours attached, only business time counts (holidays included), otherwise 24/7. A status in pauseOnStatus (default ON_HOLD) pauses the clock. SLA Management API
Response SLA & Timestamps
For incidents the ITIL semantics apply: assignment/acknowledgement fulfils the response SLA (unlike tickets, which require a public agent reply). Any status from ACKNOWLEDGED onwards counts as the reaction — not only the transition exactly into it. Jumping from NEW straight to IN_PROGRESS is a real reaction; the response deadline is thereby met and does not escalate further.
acknowledgedAt– Stops response timerinProgressAt– Starts resolution timermitigatedAt– Workaround activeresolvedAt– Stops resolution timerclosedAt– Finally closedonHoldEnteredAt·onHoldExitedAt·pausedTotalSec– Pause times
Fulfilment, deadlines, escalation level and paused time live in the incident's SLA tracking (field slaTracking in the detail payload); historical MTTA/MTTR metrics and attainment rates come from GET /api/sla/report?entityType=INCIDENT (requires incidents.viewAll). A priority change recalculates the deadlines.
Error Handling
| Error-Code | HTTP | Meaning |
|---|---|---|
INVALID_STATUS_TRANSITION | 422 | Transition not allowed by the matrix (details name from/to and reason) |
MITIGATION_REQUIRED | 422 | MITIGATED without a workaround or mitigation text |
RESOLUTION_REQUIRED | 422 | RESOLVED without resolutionCode + rootCauseShort |
INVALID_RESOLUTION_CODE | 400 | Resolution code not in the configuration |
DUPLICATE_ID_REQUIRED · DUPLICATE_SELF · DUPLICATE_CHAIN | 422 | Duplicate link missing, points to itself or to another duplicate |
MASTER_NOT_FOUND | 404 | Referenced master incident does not exist |
PIR_REQUIRED | 422 | Closure request on P1/P2 without a completed PIR |
PIR_REQUIRED_EVIDENCE_UNCHECKED | 422 | PIR completion with open mandatory evidence |
APPROVAL_REQUIRED | 422 | CLOSED without a complete approval |
DATA_BREACH_ACK_REQUIRED | 422 | Resolving/closing before the DPO sign-off |
DATA_BREACH_FLAG_ON_CLOSED · MAJOR_TOGGLE_ON_CLOSED | 422 | Data breach flag or major toggle on a closed incident |
DATA_BREACH_CLEARING_BLOCKED | 403 | Withdrawing the data breach flag (only via the DPO workflow) |
NOT_A_DATA_BREACH | 422 | Breach details on an incident without the flag |
EVIDENCE_TEMPLATE_MISSING | 422 | No checklist template configured for the category or globally |
EVIDENCE_CHECKLIST_FROZEN | 409 | Checklist frozen while the PIR is completed |
EVIDENCE_CHECKLIST_NOT_INITIALIZED · EVIDENCE_TEMPLATE_ITEM_IMMUTABLE · EVIDENCE_UNCHECK_REASON_REQUIRED | 400 | Checklist not created, template item removed, unchecked without a reason |
VERSION_CONFLICT | 409 | State changed in the meantime (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 (source, lock, approval, window, limit, reason) |
BULK_MESSAGE_NOT_MAJOR | 400 | Bulk message on a non-major incident |
REPORTER_ID_REQUIRED · AFFECTED_SERVICES_REQUIRED | 400 | Mandatory data on creation |
AGENT_GROUP_NOT_FOUND · AGENT_GROUP_INACTIVE · AGENT_GROUP_ENTITY_TYPE_MISMATCH | 404 / 400 | Target group missing, archived or does not support the INCIDENT type |
INCIDENT_ALREADY_CLOSED | 400 | Comment on a closed incident |
INCIDENT_NOT_FOUND | 404 | Unknown — or not visible to the caller (delete/restore) |
{
"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/role model: Permissions & RBAC
- ✓ Priority matrix (Impact × Urgency)
- ✓ Response SLA already met by acknowledgement
- ✓ Closure approval instead of simply closing
- ✓ PIR mandatory for P1/P2
- ✓ Major incident flag + bulk message
- ✓ Workaround tracking
Critical permissions: For delete, restore, declareMajor, approveClosure, approveReopen and acknowledgeDataBreach, revoking the permission takes effect immediately, including for users who are already signed in.
Code Examples
Complete 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 use the Unified Attachment System for 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: See Attachments & File Settings API for zero-trust virus scan, file settings and retention policies.
Problems API →
Learn more about the Problems API
Entity Linking API →
Link incidents with tickets/problems/changes/assets
Reopen & Lifecycle →
Reopen with approval, incidents.reopen, INCIDENT_REOPENED