# Subscriptions - alert vs report

A **subscription** is the link between one monitor and one contact. Creating a contact doesn't alert anyone on
its own, and neither does creating a monitor - a subscription has to exist between the two, and it decides which
events actually get delivered.

## What it is and when to use it

Think of it as a grid: monitors on one axis, contacts on the other, and at each intersection a set of events
that contact hears about for that monitor. There are two independent kinds of subscription on that same pair -
**alert** (event-driven) and **report** (scheduled) - and a contact can hold both at once.

## Event types

An **alert** subscription can fire on:

- **Down** - the monitor just failed a [confirmed check](/monitors/down-detection/) and a new incident opened.
- **Up** - the monitor recovered and the incident closed.
- **Still-down** (`repeatedlyDown`) - the outage is continuing. Instead of one Down alert and then silence for
  the whole outage, a still-down subscription fires again on every confirmed still-down check - roughly once
  per monitoring interval - for as long as the monitor stays down, so a long outage doesn't go quiet. The
  contact's [alert delay](/alerts/escalation/) only controls when the *first* Down alert reaches that contact,
  not how often still-down repeats.

A **report** subscription is scheduled rather than event-driven, and delivers a periodic uptime summary
regardless of whether anything went wrong: **Daily**, **Weekly** or **Monthly** in the app's own editor.
The API additionally recognizes **Quarterly** and **Yearly** as distinct frequencies, but the app doesn't offer
them as separate choices - picking Monthly in the UI actually subscribes the contact to Monthly, Quarterly and
Yearly together (so nothing is missed if a longer-period report ever gets scheduled). **Reports can only be
delivered to `email` contacts** - subscribing an SMS, voice, chat or webhook contact to a report is refused by
the API (`422 unsupported_report_channel`), not silently ignored.

## Settings reference

| Setting | API field (v2) | Type / allowed values | Default | Plan limits | What it does for you |
|---|---|---|---|---|---|
| Alert types | `alertTypes` (on the `PUT` body) | Subset of `up`, `down`, `repeatedlyDown` | None (no subscription exists until written) | - | Which events this pair fires on - **replaces** the set for that pair, it's not a diff |
| Report frequencies | `frequencies` (on the `PUT` body) | Subset of `daily`, `weekly`, `monthly`, `quarterly`, `yearly` | None | Email contacts only | Which scheduled reports this pair produces - also replaces the whole set |

There's no separate "enabled" flag - a pair with an empty event set simply has no subscription row, and setting
one writes it; setting an empty set is the same as unsubscribing that pair (`DELETE`).

## Where to manage subscriptions in the app

- On a **monitor**, open its editor and use the **Alert Subscriptions** / **Report Subscriptions** groups to
  see and change which contacts are subscribed to it.
- On a **contact**, use [subscribe monitors to a contact](/alerts/subscribe-monitors/) to see and change which
  monitors it's subscribed to, including bulk actions.
- To subscribe several contacts to a monitor at once, use a [contact group](/alerts/contact-groups/).

## Do it with the API or MCP

Every subscription is addressable from **either** side - by monitor or by contact - and both doors write the
same underlying pair, so it doesn't matter which one a client uses.

Set (replace) the alert types for one monitor/contact pair:

```
PUT /monitor/{monitorId}/alert/{contactId}
{ "alertTypes": ["down", "up"] }
```

```
PUT /monitor/{monitorId}/report/{contactId}
{ "frequencies": ["weekly"] }
```

The contact-side mirror is identical: `PUT /contact/{contactId}/alert/{monitorId}` /
`.../report/{monitorId}`. Remove one pair with `DELETE` on the same path, or every alert subscription a monitor
(or contact) has with `DELETE /monitor/{monitorId}/alert` (no `{contactId}`) - a `DELETE` on a pair that has no
subscription answers `404`, never a silent no-op success.

For many pairs at once, use the diff door instead of looping `PUT`:

```
POST /alert/bulk
{
  "create": [ { "monitorIds": ["<id1>", "<id2>"], "contactIds": ["<cid>"], "alertTypes": ["down"] } ],
  "delete": [ { "monitorIds": ["<id3>"], "contactIds": ["<cid>"], "alertTypes": ["up"] } ],
  "allMonitors": false
}
```

`allMonitors`/`allContacts` let a create/delete entry mean "every monitor/contact I own" instead of an explicit
list, without enumerating them (and without counting toward the row cap below). `POST /report/bulk` is the
identical shape with `frequencies` instead of `alertTypes`. Both return `{created, deleted, unchanged, pairs}`.

To read the current state: `GET /monitor/{monitorId}/alert` (every contact subscribed to that monitor, with its
alert-type set) and `GET /contact/{contactId}/alert` (every monitor that contact hears about) - and the `report`
twins. `GET /alert` and `GET /report` list the account's subscriptions flat, unfiltered by either side.

MCP tools: `subscribe_contact` (pass `alertTypes` and/or `frequencies` as comma-separated strings, e.g.
`alertTypes="down,up"`; each replaces that leg for the pair), `unsubscribe_contact` (`kind: alert|report|both`),
`list_subscriptions`. There is no curated tool for `POST /alert/bulk` or `POST /report/bulk`: call them through
`api_request` with `confirmed=true` - the MCP server refuses every `POST`/`PATCH`/`PUT` on a `/bulk` path without it
and sends nothing (these two doors have no dry run). Show the user the pairs first, then send:

```
api_request(method="POST", path="/alert/bulk", confirmed=true,
            bodyJson="{\"create\":[{\"monitorIds\":[\"M1\",\"M2\"],\"contactIds\":[\"C1\"],\"alertTypes\":[\"down\",\"up\"]}]}")
```

Or repeat `subscribe_contact` once per monitor and contact pair.

## What happens next

A subscription takes effect immediately - the very next confirmed Down/Up/still-down on that monitor is
evaluated against every subscribed contact's own alert delay, active hours and grouping settings. Nothing is
retroactive: subscribing mid-outage doesn't deliver a Down alert for an outage that already started, but it
does mean the contact joins the still-down/escalation cycle from that point on, and the eventual Up alert will
reach it (since it "already knew" about the outage).

## Limits and gotchas

- **An unconfirmed or overlimited contact is silently dropped from delivery**, even with a subscription in
  place - see [what a contact is](/alerts/contacts/) for the confirmation gate.
- **`422 unsupported_report_channel`** - trying to subscribe a non-email contact to reports.
- **`422 validation_failed`** (reason `conflict`) - the same monitor/contact pair appears in both `create` and
  `delete` of one bulk call.
- The bulk diff door caps a request at **2,000 enumerated rows** (monitors x contacts x types); using
  `allMonitors`/`allContacts` for a whole-account wildcard doesn't count against that cap.
- `PUT` always **replaces** the event set for a pair - to add one event without disturbing the others, read the
  current set first and send the union back.

## Related

- [What a contact is](/alerts/contacts/)
- [Contact groups](/alerts/contact-groups/)
- [Subscribe monitors to a contact](/alerts/subscribe-monitors/)
- [Alert delays & escalation](/alerts/escalation/)
