Postmortems and Follow-up
Record the root cause, lessons learned and actions after an incident, keep a catalog of causes, and track follow-up.
GET /api/im/causes · POST /api/im/causes · PATCH /api/im/causes/:id
GET /api/im/incidents/:id/postmortem · PUT /api/im/incidents/:id/postmortem · POST /api/im/incidents/:id/postmortem/complete · POST /api/im/incidents/:id/postmortem/reopen
GET /api/im/postmortems · GET /api/im/postmortems/:id · POST /api/im/postmortems/:id/actions · PATCH /api/im/postmortems/:id/actions/:actionId · DELETE /api/im/postmortems/:id/actions/:actionId
GET /api/im/follow-up · GET /api/im/quarterly-reviews · POST /api/im/quarterly-reviews
After an incident is resolved you can write one postmortem for it: summary, impact, root cause, a cause from your organization's catalog, lessons learned and actions. The catalog cause is what makes repeated incidents countable: with NIS-2 switched on, causes that appear in at least two completed postmortems within six months show up as repeated (Art. 4 Implementing Regulation (EU) 2024/2690). Uptimeify only counts. Whether a repetition is significant is your decision, through a report case.
A postmortem keeps a copy of the incident data. When the incident is deleted after 12 months, incidentId becomes null and the postmortem stays for three years after completion.
:id under /incidents is the numeric ID of an IM incident. :id under /postmortems and /causes and :actionId are a publicId or a numeric id.
Authentication
Postmortems and actions work with a session, an organization-wide API token or an agent token, like the other incident endpoints. Reading needs access to the incident; writing needs membership of the incident's team or the admin role (403 imTeamMembershipRequired). Creating or changing causes and reopening a completed postmortem need a session of an organization admin or a reporting officer (403 nis2OfficerRequired). Postmortems of deleted incidents are only visible to them as well. An incident, postmortem or cause of another organization answers 404, the same as a missing one.
Causes
The catalog is shared by every postmortem of your organization. Reading works with a session or a token; creating and changing causes needs a signed-in session of an organization admin or a reporting officer. API and agent tokens get 403 nis2OfficerRequired there, so the requests below run from the dashboard session and are shown without a token.
GET /api/im/causes answers { items, total, page, perPage }, sorted by name.
| Query | Description |
|---|---|
includeArchived | true or 1 also lists archived causes. Default false. |
page | Default 1. |
perPage | Default 50, at most 200. |
POST /api/im/causes creates a cause and answers 201 with it.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | 1 to 200 characters, unique per organization regardless of case (409 causeNameTaken). |
description | string or null | No | At most 2000 characters. |
PATCH /api/im/causes/:id changes name, description or archived (true archives, false restores) and answers the cause. A cause of another organization or an unknown one answers 404 causeNotFound, a name already in use 409 causeNameTaken. Causes are never deleted: archived causes stay on old postmortems and still count for repeated incidents.
POST /api/im/causes
Content-Type: application/json
{ "name": "Expired TLS certificate" }201 Created
{
"id": 3,
"publicId": "0b7f1c52-6a0e-4c4f-9d1e-2f7c9a3b8e11",
"name": "Expired TLS certificate",
"description": null,
"archivedAt": null,
"createdAt": "2026-10-22T09:00:00.000Z",
"updatedAt": "2026-10-22T09:00:00.000Z"
}| Field | Description |
|---|---|
archivedAt | null while active. Archived causes are hidden from the picker, stay on old postmortems and still count for repeated incidents. |
Postmortem of an incident
GET /api/im/incidents/:id/postmortem answers 404 postmortemNotFound until one exists. An incident of another organization or an unknown one answers 404 imIncidentNotFound.
PUT /api/im/incidents/:id/postmortem creates the postmortem on the first call and afterwards merges the fields you send (null clears, empty text becomes null). Unknown fields or wrong types answer 400 invalidRequestBody. The incident must be resolved (409 postmortemIncidentNotResolved). A completed postmortem accepts no content changes (409 postmortemCompleted). causeId must be a cause of your organization (404 causeNotFound); an archived cause can stay on the postmortem but cannot be newly chosen (409 causeArchived).
| Field | Type | Description |
|---|---|---|
summary | string or null | At most 10000 characters. Required to complete. |
impact | string or null | At most 10000 characters. |
rootCause | string or null | Root cause in your words, at most 10000 characters. Required to complete. |
causeId | number or null | A cause from the catalog, not archived. Required to complete. |
lessonsLearned | string or null | At most 10000 characters. May stay empty. |
POST /api/im/incidents/:id/postmortem/complete completes it and refreshes the copy of the incident data. It answers 400 postmortemIncomplete without summary or rootCause, 400 postmortemCauseRequired without causeId, 409 postmortemCompleted when it is already completed and 409 postmortemIncidentNotResolved when the incident is no longer resolved.
POST /api/im/incidents/:id/postmortem/reopen turns it back into a draft. It needs a session of an organization admin or a reporting officer (403 nis2OfficerRequired), answers 404 postmortemNotFound when there is no postmortem yet, and a postmortem whose incident was deleted cannot be reopened (409 postmortemOrphaned). Reopening a draft changes nothing. Both endpoints answer the whole postmortem.
curl -X PUT "$BASE_URL/api/im/incidents/42/postmortem" \
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
-d '{ "summary": "Status page unreachable for 47 minutes", "rootCause": "Certificate expired", "causeId": 3 }'200 OK
{
"id": 5,
"publicId": "6f0c1c1e-3c55-4b55-9d7e-1f2a3b4c5d6e",
"organizationId": 7,
"incidentId": 42,
"originalIncidentId": 42,
"incidentTitle": "Status page unreachable",
"incidentSeverity": "sev2",
"incidentTriggeredAt": "2026-10-20T08:00:00.000Z",
"incidentResolvedAt": "2026-10-20T08:47:00.000Z",
"summary": "Status page unreachable for 47 minutes",
"impact": null,
"rootCause": "Certificate expired",
"causeId": 3,
"causeName": "Expired TLS certificate",
"lessonsLearned": null,
"status": "draft",
"completedBy": null,
"completedAt": null,
"createdBy": null,
"createdAt": "2026-10-22T09:05:00.000Z",
"updatedAt": "2026-10-22T09:05:00.000Z",
"actions": [
{
"id": 11,
"publicId": "a3c1e9d2-7b4f-4e0a-8c5d-1f2e3a4b5c6d",
"title": "Monitor certificate expiry",
"ownerUserId": "u_8f2c",
"ownerName": "Jana Weber",
"dueOn": "2026-11-30",
"status": "open",
"doneAt": null,
"isOverdue": false,
"createdAt": "2026-10-22T09:06:00.000Z"
}
]
}| Field | Description |
|---|---|
incidentId | null once the incident was deleted (after 12 months). |
originalIncidentId | The incident this postmortem belongs to. Stays after the incident is deleted. |
incidentTitle, incidentSeverity, incidentTriggeredAt, incidentResolvedAt | Copy of the incident data, refreshed on completion. |
status | draft or completed. |
createdBy, completedBy | User ID, null when written with an API or agent token. |
actions[].isOverdue | true when the action is open and dueOn is before today in your organization's time zone. |
Postmortems by ID
GET /api/im/postmortems lists postmortems, newest incident first, in the form { items, total, page, perPage }. Postmortems of deleted incidents are only listed for organization admins and reporting officers.
| Query | Description |
|---|---|
status | draft or completed. |
page | Default 1. |
perPage | Default 50, at most 200. |
Each entry has id, publicId, incidentId, originalIncidentId, incidentTitle, incidentSeverity, incidentTriggeredAt, status, causeId, causeName, completedAt and updatedAt.
GET /api/im/postmortems/:id returns one postmortem in the same shape as above, also after its incident was deleted (then only for organization admins and reporting officers). Unknown, foreign or not visible: 404 postmortemNotFound.
Actions
Actions can be added and changed also after the postmortem is completed. Writing needs the same rights as writing the postmortem.
POST /api/im/postmortems/:id/actions answers 201 with the action.
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | 1 to 500 characters. |
ownerUserId | string or null | No | A user of your organization (400 postmortemActionOwnerInvalid). |
dueOn | string or null | No | Calendar date YYYY-MM-DD, not a timestamp (400 invalidRequestBody). Open actions after this day are isOverdue. |
PATCH /api/im/postmortems/:id/actions/:actionId changes title, ownerUserId, dueOn or status (open, done, dropped) and answers the action. done sets doneAt. DELETE /api/im/postmortems/:id/actions/:actionId answers 204 without a body; a second DELETE answers 404 postmortemActionNotFound, as does an action of another postmortem.
curl -X POST "$BASE_URL/api/im/postmortems/5/actions" \
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
-d '{ "title": "Monitor certificate expiry", "dueOn": "2026-11-30" }'201 Created with the action in the shape of actions[] above.
Follow-up (NIS-2)
GET /api/im/follow-up needs NIS-2 switched on (403 nis2NotEnabled) and a signed-in session of an organization admin or reporting officer. API and agent tokens and other users get 403 nis2OfficerRequired, so the requests in this section run from the dashboard session and are shown without a token. It returns four things at once:
| Field | Description |
|---|---|
notFollowedUp | Resolved incidents that need a postmortem (severity at or above postmortemMinSeverity, or a linked report case) and have no completed one. Test and merged incidents never count. postmortemStatus is none or draft. |
openActions | Open actions with isOverdue, earliest due first. |
repeatedCauses | Causes with at least two completed postmortems whose incidents started within the last six calendar months, with the incidents (also deleted ones, from the postmortem's copy; then incidentId is null). Merged incidents do not count. |
dueQuarter | The previous quarter while it is not signed off, otherwise null. |
Quarters and due dates use the time zone from the NIS-2 settings (default Europe/Berlin), returned as timezone together with postmortemMinSeverity and the time of calculation asOf.
GET /api/im/follow-up200 OK
{
"asOf": "2026-10-22T09:00:00.000Z",
"timezone": "Europe/Berlin",
"postmortemMinSeverity": "sev2",
"dueQuarter": "2026-Q3",
"notFollowedUp": [
{ "incidentId": 812, "title": "Checkout returns 502", "severity": "sev1", "triggeredAt": "2026-10-20T07:12:00.000Z", "resolvedAt": "2026-10-20T08:01:00.000Z", "hasReportCase": true, "postmortemStatus": "draft" }
],
"openActions": [
{ "id": 17, "publicId": "5d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6", "title": "Monitor certificate expiry", "ownerUserId": "u_8f2c", "ownerName": "Jana Weber", "dueOn": "2026-10-15", "isOverdue": true, "postmortemPublicId": "6f0c1c1e-3c55-4b55-9d7e-1f2a3b4c5d6e", "incidentId": 790, "incidentTitle": "TLS handshake fails" }
],
"repeatedCauses": [
{
"causeId": 3,
"causePublicId": "0b7f1c52-6a0e-4c4f-9d1e-2f7c9a3b8e11",
"causeName": "Expired TLS certificate",
"causeArchivedAt": null,
"incidentCount": 2,
"incidents": [
{ "postmortemPublicId": "6f0c1c1e-3c55-4b55-9d7e-1f2a3b4c5d6e", "incidentId": 790, "originalIncidentId": 790, "incidentTitle": "TLS handshake fails", "incidentTriggeredAt": "2026-09-14T22:40:00.000Z" },
{ "postmortemPublicId": "9a8b7c6d-1e2f-4a3b-8c4d-5e6f7a8b9c0d", "incidentId": null, "originalIncidentId": 655, "incidentTitle": "API certificate expired", "incidentTriggeredAt": "2026-05-02T06:05:00.000Z" }
]
}
]
}Quarterly review (NIS-2)
Same access as the follow-up: NIS-2 switched on (403 nis2NotEnabled), session of an organization admin or reporting officer (403 nis2OfficerRequired).
POST /api/im/quarterly-reviews with { "quarter": "2026-Q3", "note": "Checked with the ISMS team" } signs off a quarter that has ended (409 quarterlyReviewNotDue before) and answers 201. The earliest quarter is 2023-Q1 (NIS-2 Directive in force since 16 January 2023); an earlier one answers 400 invalidRequestBody. Each quarter can be signed off once (409 quarterlyReviewExists), also later. The response carries a snapshot frozen at sign-off: repeated causes as of the end of the quarter, open and overdue actions (count and publicIds) and incidents not followed up at the time of signing. Sign-offs cannot be changed or deleted and are kept for three years. At the start of each quarter the reporting officers (or, without officers, the organization admins) get one email that the review is due.
| Field | Type | Required | Description |
|---|---|---|---|
quarter | string | Yes | Calendar quarter in your organization's time zone, YYYY-QN such as 2026-Q3, 2023-Q1 at the earliest. |
note | string or null | No | At most 5000 characters. Empty text becomes null. |
Another format, a quarter before 2023-Q1, a note that is too long or an unknown field answers 400 invalidRequestBody.
POST /api/im/quarterly-reviews
Content-Type: application/json
{ "quarter": "2026-Q3", "note": "Checked with the ISMS team" }201 Created
{
"id": 1,
"publicId": "2c9e4f1a-8b3d-4e6f-a1c2-3d4e5f6a7b8c",
"quarter": "2026-Q3",
"reviewedBy": "u_8f2c",
"reviewerName": "Jana Weber",
"reviewedAt": "2026-10-02T08:30:00.000Z",
"note": "Checked with the ISMS team",
"snapshot": {
"asOf": "2026-09-30T21:59:59.999Z",
"repeatedCauses": [{ "causeId": 3, "causeName": "Expired TLS certificate", "incidentCount": 2, "postmortemPublicIds": ["6f0c1c1e-3c55-4b55-9d7e-1f2a3b4c5d6e", "9a8b7c6d-1e2f-4a3b-8c4d-5e6f7a8b9c0d"] }],
"openActionCount": 1,
"openActionPublicIds": ["5d1e2f3a-4b5c-4d6e-8f70-81a2b3c4d5e6"],
"overdueActionCount": 0,
"overdueActionPublicIds": [],
"notFollowedUpIncidentIds": []
}
}GET /api/im/quarterly-reviews?page=1&perPage=50 lists the sign-offs, newest quarter first, as { items, total, page, perPage } (perPage at most 200). Each item has the shape above.
Common errors
Report Cases (NIS-2)
Track an NIS-2 report case: criteria, the reportable decision, awareness time, deadlines, report drafts and the log of what you submitted in the BSI portal.
Analytics
Incident volume, MTTA and MTTR per day, the alerts-to-incidents noise ratio and on-call load per user for a date range, in one call.