Loading…
Loading…
Everything the dashboard and the MCP server can do is an endpoint under https://api.changelog.observer/v1. Responses are JSON; lists come back as { data: [...] }.
Send an API key as a bearer token on every request. Keys are created under Connect an agent or with POST /v1/keys, shown once, and stored only as a hash.
curl -H "Authorization: Bearer co_live_…" https://api.changelog.observer/v1/whoami
A key belongs to one workspace and may be limited to one project. Every call is scoped to what the key can see; a project id from another workspace behaves as if it did not exist.
| Scope | Grants | On a default key |
|---|---|---|
project:read | Read projects, dependencies, sources, rules, destinations, deliveries, the inbox and actions. | yes |
project:write | Create and change projects and dependencies, reconcile, annotate incidents, record feedback, manage sender rules and actions, create keys with fewer scopes, escalate incidents. | yes |
events:read | Read events, incidents, maintenance, summaries and analytics. | yes |
rules:write | Create, change and delete alert rules. | no, grant deliberately |
destinations:write | Add, change and remove destinations, including ticket systems and their credentials. | no, grant deliberately |
workspace:admin | Members and invitations. Only an owner's workspace-wide key can hold it. | no, grant deliberately |
A key can create other keys only with scopes it holds itself.
Per key: 120 requests a minute, of which 30 may be writes (anything but GET). Over the limit, the API answers 429 with Retry-After. Usage meters (dependencies, inbound mail, events, webhook deliveries, stored mail) are per workspace and per calendar month; GET /v1/usage shows them. At a cap, events are still stored and only urgent upstream incidents are delivered until the month rolls over.
Errors are JSON with an error code and, where it helps, a message or validation issues:
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid_body, invalid_query, invalid_cursor | The request did not validate; issues names each field. |
| 401 | unauthorized | No key, an unknown key, or a revoked one. |
| 403 | forbidden | The key lacks the scope; scope says which. |
| 404 | not_found | Not there, or not yours to see. |
| 409 / 422 | various | A rule of the product refused it, e.g. customer impact without evidence; the message says why. |
| 429 | rate_limited | Slow down; retry_after_sec says for how long. |
| 502 | escalation_failed | A ticket system refused the push; the message carries its status and the first words of its answer. |
Event and incident lists take limit (up to 100) and return next_cursor when there is more; pass it back as cursor. Cursors are keyset, so a page stays stable while new rows arrive. q searches titles (and summaries or components) with web-search syntax: quoted phrases, a leading minus to exclude. Filters, search and the cursor combine.
What the current key may do, and keys that create keys.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/whoami | any | The calling key: its id, workspace, project (when scoped) and scopes. |
| GET | /v1/keys | any | The workspace's keys: names, prefixes, scopes, when each was last used. Never the key itself. |
| POST | /v1/keys | project:write | Create a key with at most the caller's scopes. The raw key is in the response once. Body: name, scopes[], optional project_id to limit the key to one project. |
| DELETE | /v1/keys/{keyId} | project:write | Revoke a key. Revoked keys stay listed for the audit. |
| GET | /v1/usage | any | This month's meters against the plan's limits, and whether a cap is reached. |
What can be monitored without any setup.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/catalog/vendors | any | Every catalogued vendor with the package names and detection hints that identify it, whether the platform holds an email subscription for it, and whether a status connector exists. |
A project is an application, a service in a monorepo, or an area of a system: it owns its dependency list, inbound address, rules and destinations.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/projects | project:read | The projects the key can see, each with its inbound email address. |
| POST | /v1/projects | project:write | Create a project. Body: name, optional slug and environments[] (default production). |
| GET | /v1/projects/{id} | project:read | One project with its settings: environments, quiet hours, pause state, digest schedule, raw-mail retention. |
| PATCH | /v1/projects/{id} | project:write | Change settings. Body fields: name, environments, quiet_hours {start, end, timezone, critical_override} or null, paused (true/false), digest_schedule {frequency, hour, timezone, weekday} or null, retain_raw_mail. |
| DELETE | /v1/projects/{id} | project:write | Archive the project: its address stops routing, sources pause, data is purged after 30 days. |
| POST | /v1/projects/{id}/inbound/rotate | project:write | Issue a new inbound address; the old one stops routing at once. |
| PUT | /v1/projects/{id}/reconcile | project:write | Send the project's whole dependency inventory. The response says what was added, kept, and proposed for removal; removals are never applied on their own. Body: dependencies[] with name, external_ref (e.g. npm:stripe), type, importance, environments, evidence. |
Each dependency maps to a catalogued vendor (automatic sources) or to custom sources you add.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/projects/{id}/dependencies | project:read | The dependencies with their sources, mute state and preferences. |
| POST | /v1/projects/{id}/dependencies | project:write | Add one dependency by hand. Body: name, optional vendor_id, external_ref, type, importance, environments. |
| GET | /v1/projects/{id}/dependencies/{depId} | project:read | One dependency, with its preferences object whole. |
| PATCH | /v1/projects/{id}/dependencies/{depId} | project:write | Mute it (muted_until), set importance or environments, or replace its preferences. preferences: {ignore_categories[], ignore_sources[], ignore_senders[], min_severity}. Send the object whole. |
| DELETE | /v1/projects/{id}/dependencies/{depId} | project:write | Archive the dependency; its subscriptions end. |
| GET | /v1/projects/{id}/dependencies/{depId}/preference-suggestions | project:read | What the record supports ignoring for this dependency, with the evidence (feedback, suppressed mail, volume). |
| GET | /v1/projects/{id}/dependencies/{depId}/sources | project:read | The dependency's automatic and custom sources and their health. |
| POST | /v1/projects/{id}/dependencies/{depId}/sources | project:write | Add a custom source from a URL; the platform probes it and works out the format. Body: url. The response names the source type found (status page, RSS, Atom, GitHub releases) or why it could not be used. |
| DELETE | /v1/projects/{id}/dependencies/{depId}/sources/{sourceId} | project:write | Remove a custom source. |
The normalised stream. Lists are newest first and keyset paged with `cursor`.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/projects/{id}/events | events:read | One project's events. Query: since (ISO time), family (status | changes), category, min_severity, q (search: quoted phrases, -word to exclude), include_suppressed, limit (≤100), cursor. |
| GET | /v1/workspace/events | events:read | Events across every project the key can see. The same query parameters, plus project_id to narrow. |
| GET | /v1/events/{id} | events:read | One event with its provenance and the full decision: classification inputs, priority rule, duplicate and preference verdicts, sender trust, content warnings, and routing (which rules matched, what was delivered where). |
| POST | /v1/events/{id}/feedback | project:write | Record a verdict: useful, irrelevant, duplicate or misclassified (with suggested_category). One per event and key, replaced on repeat; the event itself never changes. For a misclassified email event the response offers a sender rule to create. |
Timelines with two lifecycles: `upstream` (the vendor's) and `ours` (what you decided).
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/projects/{id}/incidents | events:read | One project's incident timelines, newest update first. Query: upstream_status, our_status, min_severity, dependency_id, since, open_for_us (default true), q, limit, cursor. |
| GET | /v1/workspace/incidents | events:read | Timelines across every project; project_id narrows. |
| GET | /v1/incidents/{id} | events:read | One timeline with its updates (each with the upstream status it carried), its escalations, and scheduled_for / scheduled_until when it is a maintenance window. |
| PATCH | /v1/incidents/{id} | project:write | Record our side: our_status, customer_impact, impact_note, muted_until, acknowledge. customer_impact confirmed or suspected is refused without an impact_note that names the evidence. |
| POST | /v1/projects/{id}/incidents/bulk | project:write | The same patch over many timelines. Body: ids[], action (acknowledge | affected | cleared | not_observed | mute_24h | mute_7d). |
| GET | /v1/incidents/{id}/summary | events:read | A ticket-ready summary: title, body and the dashboard link. Query: format text (default markdown). |
| POST | /v1/incidents/{id}/escalate | project:write | Create a ticket or status-page incident at one of the project's ticket-system destinations. One per incident and destination; a repeat returns the existing ticket. Body: destination_id, optional format. Errors: not_a_ticket_system, no_credential, destination_disabled, escalation_failed (the vendor refused; status and the first words of its answer). |
| GET | /v1/projects/{id}/maintenance | events:read | Upcoming maintenance windows for the project, fourteen days ahead by default. Query: from, to. |
| GET | /v1/workspace/maintenance | events:read | The same across the workspace; project_id narrows. |
What arrived at the project's inbound address, what needs a person, and how to teach the inbox.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/projects/{id}/inbox | project:read | Inbound messages with sender, subject, authentication results, classification, trust and any content warning. Query: status (e.g. quarantined, suppressed), limit, cursor. |
| POST | /v1/projects/{id}/inbox/{messageId}/release | project:write | Release a quarantined message: it is classified and may become an event. |
| POST | /v1/projects/{id}/inbox/{messageId}/trust | project:write | Trust the message's sender from now on (a sender rule), and release it. |
| GET | /v1/projects/{id}/sender-rules | project:read | The project's sender rules: trust, classify or suppress by sender pattern. |
| POST | /v1/projects/{id}/sender-rules | project:write | Add a sender rule. Body: sender_pattern (an address or @domain), action (trust | classify | suppress), category for classify. |
| DELETE | /v1/projects/{id}/sender-rules/{ruleId} | project:write | Remove a sender rule. |
| GET | /v1/projects/{id}/actions | project:read | Things waiting on a person: subscription confirmations, unknown senders, proposed removals. Query: status (open | resolved). |
| POST | /v1/projects/{id}/actions/{actionId}/confirm | project:write | Have the platform follow a subscription confirmation link, if the project allows it and the mail was verified for the vendor's own domain and the link is on an allowed host. |
| POST | /v1/projects/{id}/actions/{actionId}/resolve | project:write | Mark an action done or dismissed. |
Rules route events to destinations; deliveries are the record of every attempt.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/projects/{id}/rules | project:read | The project's rules, and the defaults in force when none is enabled. |
| POST | /v1/projects/{id}/rules | rules:write | Add a rule. Body: name, match {categories[], min_severity, min_priority, dependency_ids[], source_types[], environments[], exclude_duplicates}, mode (immediate | digest | suppress), destination_ids[], enabled. |
| PATCH | /v1/projects/{id}/rules/{ruleId} | rules:write | Change a rule; match replaces the whole match object. |
| DELETE | /v1/projects/{id}/rules/{ruleId} | rules:write | Remove a rule. |
| GET | /v1/projects/{id}/destinations | project:read | Destinations: email, webhook, jira, linear, github_issues, statuspage. Secrets are never returned; credential_set_at says a ticket system has one. |
| POST | /v1/projects/{id}/destinations | destinations:write | Add a destination. A webhook's signing secret is in the response once; a ticket system's credential is sealed and never returned. Body: type, name, config (email {email}; webhook {url}; jira {site, email, project_key, issue_type}; linear {team_id}; github_issues {repo, labels[]}; statuspage {page_id, status, deliver_notifications}), credential for ticket systems. |
| PATCH | /v1/projects/{id}/destinations/{destId} | destinations:write | Rename, enable or disable, or replace a ticket system's credential (write-only). |
| DELETE | /v1/projects/{id}/destinations/{destId} | destinations:write | Remove a destination and its delivery history. |
| GET | /v1/projects/{id}/deliveries | project:read | Delivery attempts with status, attempt count, response code and error. Query: status (pending | delivered | failed | exhausted), kind (event | digest), limit. |
| POST | /v1/projects/{id}/deliveries/{attemptId}/retry | project:write | Re-queue a failed or exhausted attempt. |
Members, invitations and health. Member endpoints need an owner's workspace-wide key with workspace:admin.
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/workspace/analytics | events:read | Operational analytics: source coverage, ingestion and delivery lag, delivery success, signal quality, event volume and time to first event, each with its definition. Query: window (7 | 30, default 30), project_id. |
| GET | /v1/workspace/members | workspace:admin | Members and their roles. |
| PATCH | /v1/workspace/members/{userId} | workspace:admin | Change a member's role. A workspace always keeps at least one owner. |
| DELETE | /v1/workspace/members/{userId} | workspace:admin | Remove a member. |
| GET | /v1/workspace/invites | workspace:admin | Pending invitations. |
| POST | /v1/workspace/invites | workspace:admin | Invite by email; the platform sends the invitation and the response carries the accept link (seven days, one use). Body: email, role (owner | member). |
| DELETE | /v1/workspace/invites/{id} | workspace:admin | Revoke an invitation. |
An event looks like this (abridged). The same shape travels in webhooks.
{
"id": "evt_…", "project_id": "prj_…",
"dependency": { "id": "dep_…", "name": "stripe", "environment": ["production"] },
"category": "incident",
"title": "Elevated error rates on the Payments API",
"summary": "We are investigating…",
"source": { "type": "status_page", "url": "https://status.stripe.com/incidents/…", "published_at": "…", "first_seen_at": "…" },
"severity": { "upstream": "critical", "platform_priority": "urgent", "customer_impact": "unknown", "confidence": 0.95 },
"delivery": { "webhook_eligible": true, "digest_eligible": false },
"provenance": { "ingestion_method": "status_api", "parser_version": "statuspage-v1", "duplicate_of": null, "warnings": [] }
}An incident carries upstream and ours as separate objects, open_for_us computed from ours alone, its events (the updates) and its escalations. See Incidents and tickets.