---
title: Incidents
description: List incidents with filters, read one, acknowledge, resolve, mark as a false positive or retroactively as maintenance, and get period statistics (MTTR, MTBF, most unstable monitors).
canonical: https://watchfor.io/docs/api/incidents
---

# Incidents

List incidents with filters, read one, acknowledge, resolve, mark as a false positive or retroactively as maintenance, and get period statistics (MTTR, MTBF, most unstable monitors).

An **incident** is a confirmed problem with a lifecycle
`firing → acknowledged → resolved`. Incidents are opened by the alerting
pipeline when an [alert rule](/docs/api/alert-rules) holds across the
required checks and locations — you cannot create one through the API,
but you can drive its lifecycle.

| Method | Path | Scope | operationId |
| --- | --- | --- | --- |
| GET | `/v1/incidents` | read | `listIncidents` |
| GET | `/v1/incidents/{id}` | read | `getIncident` |
| POST | `/v1/incidents/{id}/acknowledge` | write | `acknowledgeIncident` |
| POST | `/v1/incidents/{id}/resolve` | write | `resolveIncident` |
| POST | `/v1/incidents/{id}/exclude` | write | `excludeIncidentFromUptime` |
| POST | `/v1/incidents/{id}/include` | write | `includeIncidentInUptime` |
| POST | `/v1/incidents/{id}/mark-maintenance` | write | `markIncidentMaintenance` |
| GET | `/v1/incidents/stats` | read | `getIncidentStats` |

The incident object:

```json
{
  "id": 19351,
  "object": "incident",
  "status": "firing",
  "severity": "critical",
  "message": "Received an unexpected HTTP status code (expected: 2xx, 3xx)",
  "rule": {
    "name": "Unexpected Status Code",
    "metric": "status_code",
    "operator": "not_in_groups",
    "value": "[\"2xx\", \"3xx\"]"
  },
  "monitor": { "id": "0b0f6a41-…", "name": "Marketing site" },
  "started_at": "2026-08-24T10:14:59.000Z",
  "acknowledged_at": null,
  "resolved_at": null,
  "duration_seconds": null,
  "during_maintenance": false,
  "excluded_from_uptime": false,
  "exclusion_reason": null,
  "excluded_by": null,
  "excluded_at": null
}
```

| Field | Meaning |
| --- | --- |
| `id` | Integer incident id (not a UUID) |
| `status` | `firing`, `acknowledged` or `resolved` |
| `severity` | `critical` or `warning` |
| `message` | Human-readable explanation of what failed, derived from the rule that fired |
| `rule` | The alert rule **as it was when the incident fired** (name, metric, operator, value) — frozen to that version, so later edits to the rule do not rewrite history. `null` only for very old incidents without a snapshot |
| `monitor` | The monitor's current id and name, or `null` if it was deleted |
| `duration_seconds` | Time from `started_at` to `resolved_at`; `null` while open |
| `root_cause` | Expected vs detected in words — `title`, `summary`, `expected`, `detected` (the observed value when it opened, e.g. `HTTP Status Code = 404`), `metric_failure`. Page integrity monitors in browser mode add `screenshots` on single-incident responses: `[{ page_url, url, taken_at, expires_in }]`, the pages the opening check photographed, each `url` a signed link valid for `expires_in` seconds (fetch the incident again for a fresh one) |
| `trigger` | The fire-time snapshot: `metrics` the probe reported (`http.status_code`, `http.response_time_ms`, …), an optional `response` excerpt (status, headers, `body_snippet`, plus `page_title` and `visible_text` — the readable part of an HTML body — and, from newer probes, `final_url` and `redirect_chain`), the rule `expression`, `failure_threshold`, `min_failed_probes`, the `prober` that fired and the `confirmations` from other probers. Sensitive keys are redacted |
| `fire_count`, `flapping` | How many evaluations fired, and whether the monitor flapped |
| `timeline` | Only on `GET /v1/incidents/{id}`: events in order — `at`, `type`, `source`, `detail` (the payload without the fire-time snapshot, which is `trigger`); the resolved event carries an exact `duration_seconds` |
| `during_maintenance` | `true` when the incident's active span overlapped a [maintenance window](/docs/api/maintenance-windows) for its monitor — the dashboard's *Maintenance* badge. Planned work: it did not notify and does not dent uptime |
| `excluded_from_uptime` | `true` when the incident was marked as a false positive: it stays on record but is left out of uptime, SLA, reports and the public status page — see [Mark an incident as a false positive](#mark-an-incident-as-a-false-positive) |
| `exclusion_reason` | The optional reason given when it was marked; `null` otherwise |
| `excluded_by` | Who marked it — the member, or the API key when done through the API; `null` when not excluded |
| `excluded_at` | When it was marked; `null` when not excluded |

Incidents only open after multi-location confirmation — a single failed
check is **Degraded**, not an incident. See
[how incidents work](/docs/alerting/incidents).

## List incidents

`GET /v1/incidents` — newest first, cursor-paginated.

| Query | Meaning |
| --- | --- |
| `status` | `firing`, `acknowledged` or `resolved` |
| `severity` | `warning` or `critical` |
| `monitor_id` | Only this monitor's incidents (unknown id → `404`, not an empty list) |
| `since` / `until` | ISO 8601 bounds on `started_at` (`since` inclusive, `until` exclusive) |
| `limit` | 1–100, default 50; anything else returns `400` |
| `cursor` | The previous page's `next_cursor` |
| `sort` | Not supported — returns `400`; the list is always newest first |

```bash
curl "https://watchfor.io/api/v1/incidents?status=firing" \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

```json
{
  "object": "list",
  "data": [ { "id": 19351, "object": "incident", "…": "…" } ],
  "total": 128,
  "has_more": true,
  "next_cursor": "MTkzNTE"
}
```

`total` counts all incidents matching the filters. Note that "active"
means `firing` **or** `acknowledged` — query both to see everything
still open.

## Get an incident

`GET /v1/incidents/{id}` — a non-integer id returns `400`; an id from
another organization returns `404`.

```bash
curl https://watchfor.io/api/v1/incidents/19351 \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

## Acknowledge an incident

`POST /v1/incidents/{id}/acknowledge` — requires `write` scope. Marks
the incident as being handled: repeat reminders stop and on-call
escalation halts at acknowledgement.

```bash
curl -X POST https://watchfor.io/api/v1/incidents/19351/acknowledge \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

Returns `200` with the **full incident object** — the same shape as
`GET`, including `rule`, `monitor`, `message` and `during_maintenance` —
with `status: "acknowledged"` and `acknowledged_at` set. Every action
below returns that same object. Idempotent: acknowledging an
already-acknowledged incident returns `200` with the current object. Acknowledging a
*resolved* incident is an invalid transition — `409 conflict`.

## Resolve an incident

`POST /v1/incidents/{id}/resolve` — requires `write` scope. Manually
closes the incident and resets the alerting engine's state for that
rule, so the next genuine failure opens a fresh incident (if the
underlying problem persists, it will re-fire).

Resolution is processed **asynchronously** by the alerting pipeline, so
a successful request returns **`202 Accepted`** with the full incident
object in its *current* state — poll `GET /v1/incidents/{id}` until
`status` is `resolved` (usually within seconds). Resolving an already-resolved
incident is an idempotent no-op returning `200`.

```bash
curl -X POST https://watchfor.io/api/v1/incidents/19351/resolve \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

## Mark an incident as maintenance

`POST /v1/incidents/{id}/mark-maintenance` — requires `write` scope.
Retroactively reclassifies a **resolved** incident's time range as
maintenance: the range is excluded from uptime/SLA numbers and the
incident shows the "Maintenance" badge in the dashboard. Use it when a
deploy caused an outage that should not count against your SLA.

```bash
curl -X POST https://watchfor.io/api/v1/incidents/19351/mark-maintenance \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

```json
{
  "object": "maintenance_marking",
  "incident_id": 19351,
  "maintenance_window": { "id": "19a612d1-…", "object": "maintenance_window", "name": "Retroactive maintenance — incident #19351", "…": "…" }
}
```

- Returns `201` on the first marking and `200` with the existing marking
  on repeats (idempotent).
- Implemented as a disabled, one-off [maintenance window](/docs/api/maintenance-windows)
  covering exactly the outage. To undo, `DELETE` that window.
- Marking a `firing` or `acknowledged` incident returns `409` — resolve
  it first.
- For an incident that was never real downtime — not planned work — use
  [mark as a false positive](#mark-an-incident-as-a-false-positive)
  instead; it also removes the incident from the status page.

## Mark an incident as a false positive

`POST /v1/incidents/{id}/exclude` — requires `write` scope. Marks a
**resolved** incident as a false positive: it stays on record (timeline,
internal notes, post-mortem, activity log) with a "False positive" badge,
but is excluded from uptime, SLA and reports (incident counts, MTTR,
downtime) and removed from the public status page — its auto-published
copy is deleted. Use it for an incident that was never real downtime: a
network problem on the path from the probes that made it through
confirmation, a firewall that briefly blocked them, a threshold set too
tight. For planned work that did cause real downtime, use
[mark as maintenance](#mark-an-incident-as-maintenance) instead.

The body is optional: `{ "reason": "…" }` (up to 500 characters) is stored
as `exclusion_reason` and shown on the incident. A body over 10 KB is
`413 payload_too_large`.

```bash
curl -X POST https://watchfor.io/api/v1/incidents/19351/exclude \
  -H "Authorization: Bearer wf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Packet loss on the probe side; origin was healthy" }'
```

Returns `200` with the full incident object (`excluded_from_uptime: true`,
and `exclusion_reason`, `excluded_by`, `excluded_at` set). Marking an
already-excluded incident again returns `200` and refreshes the reason and
timestamp. A `firing` or `acknowledged` incident returns `409 conflict` —
resolve it first. Supports `Idempotency-Key`.

## Undo a false-positive mark

`POST /v1/incidents/{id}/include` — requires `write` scope. The incident
counts as downtime again in uptime, SLA and reports, and the four
exclusion fields go back to `false` / `null`. The status-page copy is
**not** re-created (incidents are only auto-published while they fire) —
add a status-page incident by hand if customers should see it. Idempotent:
including an incident that is not excluded returns `200` with the current
object.

```bash
curl -X POST https://watchfor.io/api/v1/incidents/19351/include \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

## Incident statistics

`GET /v1/incidents/stats?period=24h|7d|30d|90d` (default `7d`) —
aggregates for reporting and wallboards: totals by severity, MTTR (mean
time to resolve, over incidents that resolved), MTBF (the mean gap
between consecutive incident starts), per-bucket counts (hourly for
`24h`, daily otherwise; bucket labels are UTC) and the five most unstable
monitors. Every figure leaves out incidents
[marked as false positives](#mark-an-incident-as-a-false-positive), the
same way the dashboard counters and reports do. For the live "what's on fire right
now" snapshot use [`GET /v1/summary`](/docs/api/summary); for the full
period-over-period report use [`GET /v1/reports`](/docs/api/reports).

```bash
curl "https://watchfor.io/api/v1/incidents/stats?period=30d" \
  -H "Authorization: Bearer wf_live_YOUR_KEY"
```

```json
{
  "object": "incident_stats",
  "period": "30d",
  "total": 12,
  "critical": 9,
  "warning": 3,
  "mttr_seconds": 840,
  "mtbf_seconds": 216000,
  "counts": [ { "bucket": "2026-08-01", "count": 2 }, { "…": "…" } ],
  "top_unstable_monitors": [
    { "id": "de5c56f7-…", "name": "www.example.com", "incident_count": 5 }
  ]
}
```

`mttr_seconds` is `null` when nothing resolved in the period;
`mtbf_seconds` is `null` with fewer than two incidents (one incident has
no gap to measure). Deleted monitors are excluded from
`top_unstable_monitors`.

## Also via

MCP tools `list_incidents`, `get_incident`, `acknowledge_incident`,
`resolve_incident`, `set_incident_uptime_exclusion`, `get_incident_stats`;
A2A skills
`diagnose-incidents`, `incident-report`, `list-incidents`,
`manage-incident`, `reliability-report`; CLI
`watchfor incidents --status firing`. Team-only
[internal notes](/docs/alerting/internal-notes) and
[post-mortems](/docs/alerting/postmortems) are dashboard features.

---

Canonical page: https://watchfor.io/docs/api/incidents · All docs: https://watchfor.io/docs · Site guide: https://watchfor.io/llms.txt
