Incident API
For your own developers: CI/CD, internal monitoring, an incident bot
Incident API capability
On this page14
Authentication
Generate a key: Dashboard → Account → Access. The full key is shown exactly once, in that response; it is never displayed again anywhere in the dashboard afterwards. If you lose it, revoke it and create a new one.
Every request:
Authorization: Bearer ibk_live_<...>A missing/malformed header, an unknown or revoked key, or too many requests on one key (see Rate limiting below) all fail the request before it reaches any endpoint logic.
A key belongs to the one status page it was created under. It can never read or write another customer's data, the same isolation the dashboard already gives a signed-in user.
Base URL
https://inbrief.sh/api/v1Try a request
Every endpoint below, against this page's own API, with a key you paste in. Nothing is stored: the key stays in this tab and is sent only to the request you run.
Reading status
Current status is public, so reading it needs no key. The Public status API covers the summary and the incident ledger, with their full response shapes and the caching rule. The monitor endpoints below need a key because they return what the public page leaves out: each monitor's id, type and check settings.
POST/incidents
Create an incident. Scheduled maintenance is planned in the dashboard, not through this API.
curl -X POST https://inbrief.sh/api/v1/incidents \
-H "Authorization: Bearer ibk_live_<your key>" \
-H "Content-Type: application/json" \
-d '{
"title": "Elevated error rates",
"summary": "We are investigating reports of elevated error rates on the API.",
"monitorSlugs": [
"api"
],
"startedAt": "2026-08-15T09:00:00Z"
}'const response = await fetch('https://inbrief.sh/api/v1/incidents', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.INBRIEF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"title": "Elevated error rates",
"summary": "We are investigating reports of elevated error rates on the API.",
"monitorSlugs": [
"api"
],
"startedAt": "2026-08-15T09:00:00Z"
}),
});
if (!response.ok) throw new Error(`InBrief responded ${response.status}`);
const incident = await response.json();| Field | Required | Notes |
|---|---|---|
type | no | "incident", the default. "maintenance" is refused with a 400. |
title | yes | In your status page's default language (see Localisation below). |
summary | yes | Same. |
monitorSlugs | no | Array of your monitors' slugs (dashboard → Monitors). Slugs are stored as sent and are not checked against your monitors, so check the spelling. At most 50 are kept, and a slug longer than 40 characters is dropped. |
startedAt | yes | ISO-8601, e.g. 2026-08-15T09:00:00Z. |
resolvedAt | no | ISO-8601. Only set this on create if the incident is already over. |
Response: 201 with the created incident:
{
"id": "inc_example_02",
"type": "incident",
"title": "Elevated error rates",
"summary": "We are investigating reports of elevated error rates on the API.",
"monitorSlugs": [
"api"
],
"startedAt": "2026-08-15T09:00:00.000Z",
"resolvedAt": null
}PATCH/incidents/{id}
Update or resolve an existing incident. This is a partial update: only fields present in the request body change. Anything omitted (including every other language's already-translated text) is left exactly as it was.
Resolve an ongoing incident:
curl -X PATCH https://inbrief.sh/api/v1/incidents/{id} \
-H "Authorization: Bearer ibk_live_<your key>" \
-H "Content-Type: application/json" \
-d '{
"resolvedAt": "2026-08-15T10:30:00Z"
}'const response = await fetch('https://inbrief.sh/api/v1/incidents/{id}', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.INBRIEF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"resolvedAt": "2026-08-15T10:30:00Z"
}),
});
if (!response.ok) throw new Error(`InBrief responded ${response.status}`);
const incident = await response.json();Reopen one:
{
"resolvedAt": null
}Same fields as POST /incidents above, all optional here. title/summary
cannot be set to an empty string. A scheduled maintenance entry cannot be edited through this API, and an
incident cannot be changed into one: both return 400. Response: 200 with the incident's full current state and the
notify value that was applied:
{
"id": "inc_example_02",
"type": "incident",
"title": "Elevated error rates",
"summary": "We are investigating reports of elevated error rates on the API.",
"monitorSlugs": [
"api"
],
"startedAt": "2026-08-15T09:00:00.000Z",
"resolvedAt": "2026-08-15T10:30:00.000Z",
"notify": true
}Editing without telling anyone
PATCH accepts one extra field, notify. Omit it and the update is announced as usual,
with subscriber email and webhook delivery. Send false and the incident is updated silently, with no subscriber email
and no webhook delivery:
{
"summary": "Fixed a typo in the previous wording.",
"notify": false
}Use it for corrections and backfills. Translation still runs either way, so every other language picks up the
edit whether or not it was announced. Any value other than true or false is a
400.
POST/incidents/{id}/updates
Append an update to an incident. This is the endpoint to call repeatedly as an incident develops: it adds to the timeline rather than replacing anything, so the earlier wording survives and readers can see how it progressed.
curl -X POST https://inbrief.sh/api/v1/incidents/{id}/updates \
-H "Authorization: Bearer ibk_live_<your key>" \
-H "Content-Type: application/json" \
-d '{
"status": "identified",
"body": "A configuration change at our payment provider was the cause. We are rolling it back."
}'const response = await fetch('https://inbrief.sh/api/v1/incidents/{id}/updates', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.INBRIEF_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"status": "identified",
"body": "A configuration change at our payment provider was the cause. We are rolling it back."
}),
});
if (!response.ok) throw new Error(`InBrief responded ${response.status}`);
const update = await response.json();| Field | Required | Notes |
|---|---|---|
body | yes | The update text, in your status page's default language. |
status | no | investigating (default), identified,
monitoring or resolved. Anything else is a 400. |
notify | no | Defaults to true. false posts the update
without emailing subscribers or delivering an outbound incident webhook. |
Response: 201:
{
"id": "update_example_02",
"incidentId": "inc_example_02",
"status": "identified",
"body": "A configuration change at our payment provider was the cause. We are rolling it back.",
"notify": true,
"resolvedIncident": false
}Posting
"status": "resolved" also closes the incident if it is still open, and
resolvedIncident says whether that happened; it never moves an end time you already set.
Updates are append-only: there is no endpoint to edit or delete one. The record of what was said at a time is
not something to revise; correcting the current account of events is what PATCH /incidents/:id is
for. Every update is translated into your page's other languages by the same AI pass an incident gets, and
appears on the public page's timeline and in GET /api/public/{slug}/incidents under the incident's
updates array.
Monitors
Read the monitors on your status page, so a script can find a monitor's id or slug before it references one. These endpoints only read: create, update, pause and delete come later. The same key works here and on the incident endpoints.
GET/monitors
List every monitor on the page, oldest first, 50 at a time.
curl https://inbrief.sh/api/v1/monitors?limit=50 \
-H "Authorization: Bearer ibk_live_<your key>"const response = await fetch('https://inbrief.sh/api/v1/monitors?limit=50', {
headers: {
Authorization: `Bearer ${process.env.INBRIEF_API_KEY}`,
},
});
if (!response.ok) throw new Error(`InBrief responded ${response.status}`);
const page = await response.json();| Query parameter | Required | Notes |
|---|---|---|
limit | no | Monitors per page, a whole number from 1 to 100.
Defaults to 50. Anything else is a 400. |
afterId | no | The nextAfterId from the previous page. |
Response: 200. nextAfterId is null on the last page. Monitors are
ordered by id, so one created while you are paging appears on a later page and never shifts the ones you
have already read:
{
"monitors": [
{
"id": "mon_example_01",
"slug": "checkout",
"name": "Checkout",
"names": {
"en": "Checkout",
"fr": "Paiement"
},
"type": "http",
"groupKey": "payments",
"groupName": "Payments",
"sortOrder": 0,
"active": true,
"monitoringEnabled": true,
"state": "up",
"downSince": null,
"lastCheckedAt": "2026-08-20T21:02:09.028Z",
"checkIntervalSeconds": 60,
"autoIncident": true
},
{
"id": "mon_example_02",
"slug": "nightly-export",
"name": "Nightly export",
"names": {
"en": "Nightly export"
},
"type": "heartbeat",
"groupKey": null,
"groupName": null,
"sortOrder": 1,
"active": true,
"monitoringEnabled": null,
"state": "down",
"downSince": "2026-08-20T03:15:00.000Z",
"lastCheckedAt": "2026-08-20T03:15:00.000Z",
"checkIntervalSeconds": null,
"autoIncident": false
}
],
"nextAfterId": null
}| Field | Notes |
|---|---|
id | Stable. Use it with GET /monitors/{id}. |
slug | What monitorSlugs on an incident refers to. |
name, names | name is in your page's default language,
or the slug if that language has no name. names holds every language that has one. |
type | http, tcp, dns, ping,
smtp, websocket or heartbeat. |
groupKey, groupName | The service group it is shown under, or
null. |
active | Whether the monitor is switched on in the dashboard. |
monitoringEnabled | Whether it is being checked now. A monitor can be switched on
and still paused by your plan, which shows here as false. Always null for a
heartbeat monitor, because your job reports to us rather than us checking it. |
state | up, down or unknown, as last
recorded. Read it together with monitoringEnabled: a paused monitor keeps its last
state. |
downSince | When the current outage started, or null. |
lastCheckedAt | When the monitor was last checked. For a heartbeat monitor, when a signal last arrived or a deadline was last missed. |
checkIntervalSeconds | How often checks run under your current plan, which can be
slower than the interval set in the dashboard. null for a heartbeat monitor. |
autoIncident | Whether an outage opens an incident automatically. |
Probe targets, request headers and stored credentials are never returned.
GET/monitors/{id}
Read one monitor by id.
curl https://inbrief.sh/api/v1/monitors/{id} \
-H "Authorization: Bearer ibk_live_<your key>"const response = await fetch('https://inbrief.sh/api/v1/monitors/{id}', {
headers: {
Authorization: `Bearer ${process.env.INBRIEF_API_KEY}`,
},
});
if (!response.ok) throw new Error(`InBrief responded ${response.status}`);
const monitor = await response.json();Response: 200 with one monitor, in the same shape as an entry in the list:
{
"id": "mon_example_01",
"slug": "checkout",
"name": "Checkout",
"names": {
"en": "Checkout",
"fr": "Paiement"
},
"type": "http",
"groupKey": "payments",
"groupName": "Payments",
"sortOrder": 0,
"active": true,
"monitoringEnabled": true,
"state": "up",
"downSince": null,
"lastCheckedAt": "2026-08-20T21:02:09.028Z",
"checkIntervalSeconds": 60,
"autoIncident": true
}A monitor on another status page answers 404, the same as one that does not exist.
Localisation
title/summary/body are always read and written in your status page's default language,
since an integration is a system, not a person choosing a language preference. Every other active language
is still filled in automatically when multilingual publishing is enabled, and any translation already on
the incident is preserved by a PATCH that does not
touch title/summary.
Side effects
An incident created or updated through the API triggers exactly what a dashboard edit would: AI
translation, subscriber emails, and outbound webhook deliveries. Nothing
behaves differently because the caller was a script instead of a person, except that you can opt out of the
announcement with notify: false on PATCH or when appending an update.
Errors
Every error response is {"error": "<message>"} with a matching HTTP status:
400 (bad input), 401 (missing/invalid/revoked key), 403 (the key has
the wrong scope or the capability is unavailable), 404 (no such incident or monitor, or the id belongs to another status page),
429 (rate limited).
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "Invalid or revoked API key."
}Rate limiting
Each key is limited independently of any account-wide limit, so one compromised or overused key cannot be
used to hammer this API. A key may make 60 requests in any
rolling hour across the incident and monitor endpoints, and every request counts, reads included,
whether or not it succeeds. The next one is answered with
429 until the oldest request in the hour ages out, so an integration that posts every minute
reaches the limit after an hour.
This API does not currently accept a client Idempotency-Key
header. Reconcile an uncertain create before retrying it so a timeout does not publish a duplicate.