Skip to documentation
InBrief

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:

Example
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

Request
https://inbrief.sh/api/v1

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

Loading the request runner…

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();
FieldRequiredNotes
typeno"incident", the default. "maintenance" is refused with a 400.
titleyesIn your status page's default language (see Localisation below).
summaryyesSame.
monitorSlugsnoArray 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.
startedAtyesISO-8601, e.g. 2026-08-15T09:00:00Z.
resolvedAtnoISO-8601. Only set this on create if the incident is already over.

Response: 201 with the created incident:

Response
{
  "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:

Response
{
  "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:

Response
{
  "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:

Response
{
  "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();
FieldRequiredNotes
bodyyesThe update text, in your status page's default language.
statusnoinvestigating (default), identified, monitoring or resolved. Anything else is a 400.
notifynoDefaults to true. false posts the update without emailing subscribers or delivering an outbound incident webhook.

Response: 201:

Response
{
  "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 parameterRequiredNotes
limitnoMonitors per page, a whole number from 1 to 100. Defaults to 50. Anything else is a 400.
afterIdnoThe 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:

Response
{
  "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
}
FieldNotes
idStable. Use it with GET /monitors/{id}.
slugWhat monitorSlugs on an incident refers to.
name, namesname is in your page's default language, or the slug if that language has no name. names holds every language that has one.
typehttp, tcp, dns, ping, smtp, websocket or heartbeat.
groupKey, groupNameThe service group it is shown under, or null.
activeWhether the monitor is switched on in the dashboard.
monitoringEnabledWhether 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.
stateup, down or unknown, as last recorded. Read it together with monitoringEnabled: a paused monitor keeps its last state.
downSinceWhen the current outage started, or null.
lastCheckedAtWhen the monitor was last checked. For a heartbeat monitor, when a signal last arrived or a deadline was last missed.
checkIntervalSecondsHow often checks run under your current plan, which can be slower than the interval set in the dashboard. null for a heartbeat monitor.
autoIncidentWhether 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:

Response
{
  "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).

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