Incidents
Declare, acknowledge, resolve, and list incidents from your own system, and keep an external IMS in sync with Ionhour.
The incident endpoints let an external system declare and manage Ionhour incidents. They require an ionh_ key scoped to the mcp:incidents resource (or an unrestricted key). All routes are under https://api.ionhour.com/api/v1.
| Method | Path | Min role | Purpose |
|---|---|---|---|
POST | /v1/incidents | member | Declare a manual incident. |
POST | /v1/incidents/:id/acknowledge | member | Acknowledge an incident. |
POST | /v1/incidents/:id/resolve | member | Resolve an incident. |
GET | /v1/incidents | viewer | List incidents (paginated, filterable). |
GET | /v1/incidents/:id | viewer | Fetch one incident. |
State machine
An incident's state is one of ACTIVE, MONITORING, or RESOLVED.
ACTIVE ───────────────► RESOLVED
│ ▲
└──► MONITORING ─────────┘What the Integration API can do:
- Declare creates an incident in
ACTIVE. - Acknowledge records who acknowledged it and when. It does not change
state— the incident staysACTIVE, now withacknowledgedAtandacknowledgedByset. - Resolve moves the incident to
RESOLVED.
MONITORING is a valid state you may observe on incidents (and filter by), but the Integration API has no endpoint to move an incident into it. That transition is driven by Ionhour's own lifecycle and the dashboard.
Acknowledge and resolve are idempotent. Acknowledging an already-acknowledged (or resolved) incident, or resolving an already-resolved one, returns 200 with the incident's current state and does not create a duplicate action.
Declare an incident
POST /v1/incidents
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | Yes | Max 255 chars. |
description | string | No | Max 2000 chars. Rendered as rich HTML on the incident, so it is sanitized on the server. |
severity | string | No | One of P1, P2, P3, P4. Defaults to P1. |
checkId | number | No | Link the incident to a check. |
jobId | number | No | Link the incident to a job. |
dependencyId | number | No | Link the incident to a dependency. |
Provide at most one of checkId, jobId, or dependencyId — supplying more than one returns 400 VALIDATION_FAILED. The linked monitor must belong to the key's workspace, or you get 404 NOT_FOUND.
You cannot declare an incident against an Ionhour-managed dependency (one sourced from the provider catalog). Those return 403 DEPENDENCY_PROVIDER_MANAGED.
curl -X POST https://api.ionhour.com/api/v1/incidents \
-H "Authorization: Bearer ionh_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"title": "Checkout latency spike",
"description": "p99 above 3s in eu-west",
"severity": "P2",
"checkId": 1234
}'const res = await fetch('https://api.ionhour.com/api/v1/incidents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.IONHOUR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Checkout latency spike',
description: 'p99 above 3s in eu-west',
severity: 'P2',
checkId: 1234,
}),
});
if (!res.ok) throw new Error(`Ionhour ${res.status}: ${await res.text()}`);
const incident = await res.json();
console.log(incident.id, incident.state);import os, requests
res = requests.post(
"https://api.ionhour.com/api/v1/incidents",
headers={"Authorization": f"Bearer {os.environ['IONHOUR_API_KEY']}"},
json={
"title": "Checkout latency spike",
"description": "p99 above 3s in eu-west",
"severity": "P2",
"checkId": 1234,
},
timeout=10,
)
res.raise_for_status()
incident = res.json()
print(incident["id"], incident["state"])A successful call returns the incident:
{
"id": 5567,
"publicId": "INC-5567",
"title": "Checkout latency spike",
"state": "ACTIVE",
"reason": "MANUAL",
"severity": "P2",
"checkId": 1234,
"monitorType": null,
"monitorId": null,
"dependencyId": null,
"dependencyName": null,
"startedAt": "2026-07-10T14:03:11.000Z",
"acknowledgedAt": null,
"acknowledgedBy": null,
"resolvedAt": null
}Acknowledge
POST /v1/incidents/:id/acknowledge — takes no body.
curl -X POST https://api.ionhour.com/api/v1/incidents/5567/acknowledge \
-H "Authorization: Bearer ionh_your_api_key"The response is the incident with acknowledgedAt and acknowledgedBy populated; state remains ACTIVE.
Resolve
POST /v1/incidents/:id/resolve
| Field | Type | Required | Notes |
|---|---|---|---|
resolutionNote | string | No | Max 2000 chars. Added to the incident timeline. |
curl -X POST https://api.ionhour.com/api/v1/incidents/5567/resolve \
-H "Authorization: Bearer ionh_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "resolutionNote": "Rolled back deploy #4821" }'List and fetch
GET /v1/incidents supports these query parameters:
| Param | Type | Notes |
|---|---|---|
state | string | ACTIVE, MONITORING, or RESOLVED. |
checkId | number | Only incidents linked to this check. |
from | ISO-8601 | Incidents started on or after this time. |
to | ISO-8601 | Incidents started on or before this time. |
page | number | Page number (default 1). |
limit | number | Page size (default 25, max 100). |
curl -H "Authorization: Bearer ionh_your_api_key" \
"https://api.ionhour.com/api/v1/incidents?state=ACTIVE&limit=50"The response is the standard paginated envelope ({ items, total, page, itemCount, pageCount, limit }), newest first.
GET /v1/incidents/:id returns a single incident with a few extra fields — summary (the rich-HTML description), recoveredAt, acknowledgedByUser, and a nested check ({ id, name }) when one is linked.
IMS sync patterns
A typical incident-management integration keeps its own records in step with Ionhour:
- Your system is the source of truth. When your platform opens an incident,
POST /v1/incidentsto mirror it into Ionhour; store the returnedid. On acknowledge/resolve in your tool, call the matching/acknowledgeand/resolveroutes. Because those routes are idempotent, retries and duplicate webhooks from your side are safe. - Ionhour is the source of truth. Receive Ionhour's own incidents through a Webhook alert channel (
incident.created/incident.resolved), open the corresponding record in your IMS, and reconcile periodically withGET /v1/incidents?state=ACTIVE. - Reconciliation. Poll
GET /v1/incidentswith afromwindow to catch anything a missed webhook left behind; match onidto stay convergent.