incident.io CLI
Invoke the incidentio binary. Source of truth: paymog/incidentio-cli.
Unlike blacksmith's cookie-replay, incident.io has a real public API keyed by Bearer
tokens. incidentio speaks it directly — no browser, no cookies. Commands are
generated from incident.io's official OpenAPI specs (one per resource tag), so every
documented endpoint is available. The API base is https://api.incident.io.
Auth (required before any command)
A Bearer API key. Create one at Settings → API keys (app.incident.io/settings/api-keys);
when creating it you choose its scopes (e.g. incidents.create), and the scope set is fixed
after creation.
Resolution order: --api-key <key> flag → $INCIDENT_API_KEY → stored credential. For a
one-off or CI, export INCIDENT_API_KEY and skip auth set.
A 401/403 means the key is invalid, revoked, or lacks the scope the endpoint needs
(e.g. calling incidents create with a read-only key). Re-check the key's scopes in the
dashboard.
Usage
Commands are two tokens: <resource> <verb>. Output is pretty-printed JSON — pipe to
jq, or pass --raw for the unformatted response.
Flags
Bracket query filters
List endpoints use bracket keys (Rails-style). Pass them verbatim to --query; repeat for
multi-value filters. incident.io's list filters are powerful but the documented param names
appear in incidentio list <resource> output.
Dates are ISO-8601 UTC (2026-06-04T00:00:00.000Z). For "now" compute it:
date -u +%FT%T.000Z (macOS) or date -u -d '30 days ago' +%FT%T.000Z (GNU).
Command surface
Run incidentio list for the authoritative set (~324 commands: ~179 public Bearer-API
commands across 53 resources, plus ~145 internal/dashboard commands marked 🍪, and the raw
escape hatch). Grouped
highlights (GET unless noted):
Incidents
Actions, follow-ups, attachments
Alerts & alert sources
Escalations & on-call schedules
Catalog (service catalog as code)
Config (custom fields, severities, types, roles, statuses, timestamps)
Status pages
Notes: creating/branding a page and defining its components/layout is internal-only (cookie
session); the public Bearer API only lists/shows and publishes incidents/maintenance. Public-page
components are page-native objects (create them, then place them with status-page-structures),
not a custom field (that model is for internal pages). theme is light|dark. Team plan allows
one public page (a second create returns 422 exceeded your allowance). Logo/favicon/brand
color are uploads done in the dashboard.
For catalog-backed parent pages: split_by_catalog_type_id and split_by_component_attribute_id
identify which catalog type backs the sub-pages and which attribute on that type points to
components; each sub_pages entry maps a catalog entry to a sub-page slug.
Users, teams, API keys, workflows, secrets
Housekeeping
Dashboard / internal API (🍪 — needs a browser session)
These hit app.incident.io/api/*, which rejects API keys ("Cannot use API keys to
authenticate to internal APIs"). They replay a logged-in browser session. Import one first:
Then commands marked 🍪 in list work. Highlights — things the public API can't do:
A 401 "No authorization material" on a 🍪 command means the session cookie isn't being
recognized (wrong/expired) — re-import a fresh Copy-as-cURL. Org id resolves --org →
$INCIDENT_ORG_ID → stored.
Raw requests & reverse-engineering new endpoints
Not every endpoint is codified. incidentio raw <METHOD> </path> hits any endpoint with your
stored creds — the fast path for probing and reverse-engineering internal routes:
Auth is inferred (/api/* → cookie, otherwise bearer); override with --auth cookie|bearer.
Inline any IDs directly in the path (raw does no :param substitution).
Codify a new endpoint (recipe):
- Probe with an empty/partial body:
incidentio raw POST /api/<thing> --body-json '{}'. - Read the
422 validation_error— thesource.field/ message names the required fields; retry with an intentionally invalid enum value to learn allowed values. - Add a
Commandtosrc/commands/manual-internal.ts(it's merged at load time and survives HAR regeneration), thenbun run build.
Recipes
Validate your API key
Open incidents, newest first
Page through a list (cursor pagination)
list responses include a pagination_meta.after cursor; pass it back as --query after=<cursor>:
Declare an incident
Need the severity/status IDs first? incidentio severities list / incidentio incident-statuses list.
Which verbs does a resource have?
Create a catalog entry with component relations (🍪)
Update an alert route to bind a custom field (🍪)
Derive Affected Components from alert Service via catalog navigation expression (🍪)
A navigation expression lets an alert route auto-populate a catalog-backed custom field by navigating from an alert attribute (e.g. Service) through a catalog relationship (e.g. Components).
Notes:
root_referencepoints at the alert source attribute that holds a Service catalog entry.operations[0].navigate.referenceis the catalog attribute name on the Service type that holds its component entries (verify viaincidentio catalog-types show --id <service-type-id>).- The expression
referencevalue becomes the key inexpressions["<reference>"]binding. - The Affected Components custom field must be a catalog-backed
multi_selectcreated viacustom-fields create-catalog-backed(dashboard internal API) targeting the Component catalog type.
Secrets, signed webhooks, alert-triggered workflows
Opt a policy into private incidents (🍪)
Regenerate the command catalog
Commands live in src/commands/generated.ts, generated from incident.io's per-tag OpenAPI
specs (fetched live from docs.incident.io):
The generator discovers the REST tag set from docs.incident.io/llms.txt, fetches each
/openapi/tags/<tag>.json, derives a <resource> <verb> name per operation, and resolves
collisions (e.g. catalog-types update-type vs update-type-schema; heartbeat ping vs
ping-post). After regenerating, rebuild (bun run build).
Dashboard/internal commands are generated from captured browser HAR(s) via bun run codegen:har <a.har> [b.har ...]. It harvests GET and write endpoints (POST→create, PUT/PATCH→update,
DELETE→delete), templates ULID/Slack IDs to :param, drops anything the public Bearer API already
covers, and overwrites generated-internal.ts — so pass every HAR you want represented in a
single invocation (e.g. app.incident.io.har app.incident.io2.har app.incident.io3.har). Hand-verified
internal endpoints that appear in no HAR (e.g. status-page create/update/components/structures) live in
src/commands/manual-internal.ts and are merged at load time, so they survive regeneration.
Common issues
not authenticated
No key found via flag, env, or store. Run incidentio auth set <key> or export INCIDENT_API_KEY=<key>.
HTTP 401 / HTTP 403 (public/Bearer commands)
The key is invalid/expired, or lacks the scope the endpoint requires (e.g. a read-only key calling a write verb). Check the key's scopes in Settings → API keys; scopes are fixed at creation — rotate or create a new key if you need more.
needs a browser session / 401 "No authorization material" (🍪 dashboard commands)
Dashboard commands (app.incident.io/api/*) reject API keys. They need a logged-in browser
session: re-import via incidentio auth import <curl> (Copy-as-cURL from app.incident.io),
and ensure the org id is set (auth set-org or --org). HARs from Chrome/Brave usually
strip cookies — use Copy-as-cURL.
HTTP 422 with a validation message
The body/query is the wrong shape (missing required field, bad enum, wrong type). The error
body names the offending source.field — fix the --body-json/--query/--set value.
HTTP 429
Rate limit (default 1200 req/min/key). The error body includes rate_limit.retry_after.
Back off and retry; don't hammer.
unknown command
Commands are <resource> <verb>. Run incidentio list <resource> to see the exact verbs.
If you typed just the resource, the CLI suggests its verbs. CRUD verbs are collapsed
(incidents list/create/show/update/delete); non-CRUD actions keep their name
(incidents edit, escalations cancel-escalation, catalog-entries bulk-update-entries).
Missing endpoint
The endpoint isn't in the generated catalog. Re-run bun run codegen to pick up newly
published incident.io endpoints, then rebuild.


