Uptimeify Docs
Incident management

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.

QueryDescription
includeArchivedtrue or 1 also lists archived causes. Default false.
pageDefault 1.
perPageDefault 50, at most 200.

POST /api/im/causes creates a cause and answers 201 with it.

FieldTypeRequiredDescription
namestringYes1 to 200 characters, unique per organization regardless of case (409 causeNameTaken).
descriptionstring or nullNoAt 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"
}
FieldDescription
archivedAtnull 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).

FieldTypeDescription
summarystring or nullAt most 10000 characters. Required to complete.
impactstring or nullAt most 10000 characters.
rootCausestring or nullRoot cause in your words, at most 10000 characters. Required to complete.
causeIdnumber or nullA cause from the catalog, not archived. Required to complete.
lessonsLearnedstring or nullAt 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"
    }
  ]
}
FieldDescription
incidentIdnull once the incident was deleted (after 12 months).
originalIncidentIdThe incident this postmortem belongs to. Stays after the incident is deleted.
incidentTitle, incidentSeverity, incidentTriggeredAt, incidentResolvedAtCopy of the incident data, refreshed on completion.
statusdraft or completed.
createdBy, completedByUser ID, null when written with an API or agent token.
actions[].isOverduetrue 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.

QueryDescription
statusdraft or completed.
pageDefault 1.
perPageDefault 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.

FieldTypeRequiredDescription
titlestringYes1 to 500 characters.
ownerUserIdstring or nullNoA user of your organization (400 postmortemActionOwnerInvalid).
dueOnstring or nullNoCalendar 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:

FieldDescription
notFollowedUpResolved 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.
openActionsOpen actions with isOverdue, earliest due first.
repeatedCausesCauses 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.
dueQuarterThe 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-up

200 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.

FieldTypeRequiredDescription
quarterstringYesCalendar quarter in your organization's time zone, YYYY-QN such as 2026-Q3, 2023-Q1 at the earliest.
notestring or nullNoAt 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

See Postmortem and follow-up error codes.

On this page