# attn.ca — full documentation


---

---
title: "attn.ca — developer guide"
source: "https://attn.ca/docs"
---
Developer guide

# Trigger your own alerts from anything

Call attn from a cron job, a deploy pipeline, a shell script, or an AI agent. Sign in, connect a destination, mint a key, and POST.

[Quick start](#quickstart) [Recipes](#recipes) [How it works](#concepts) [REST API](#rest) [Channels](#channels) [Agents & MCP](#mcp) [OAuth & security](#oauth)

## Quick start

From zero to your first alert

1. **Sign in.** attn shares your pastebin.ca account — there is nothing new to register. [Sign in and open your dashboard →](/auth/login?return_to=/dashboard)
2. **Connect a destination first.** Pair a Telegram chat or enable browser notifications so your test has somewhere to land.
3. **Mint an API key** with the `attn:notify` scope. Copy it immediately — the secret is shown once.
4. **Send your first notification** with the call below.

```
curl -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: Bearer pbca_live_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello from attn","body":"Your first notification worked."}'
```

Expect `202 Accepted` with the notification id. Every verified device you own gets pinged.

```
{
  "id": "ntfy_3kQ…",
  "status": "accepted",
  "duplicate": false,
  "channels": 2,
  "suppressed": false
}
```

## Recipes

Windows — PowerShell

No curl quoting to fight with cmd.exe or PowerShell’s own escaping rules: `-Form` submits a hashtable as `multipart/form-data` for you.

```
$headers = @{ Authorization = "******" }
$form = @{ title = "Backup failed"; body = "disk full on $(hostname)" }
Invoke-RestMethod -Method Post -Uri "https://attn.ca/api/v1/notify" -Headers $headers -Form $form
```

Python — any OS, standard library only

No `pip install` needed — `urllib` ships with Python. Set the content type explicitly; unlike some HTTP libraries, `urllib` won’t infer it for you.

```
import urllib.request
from urllib.parse import urlencode

data = urlencode({"title": "Backup failed", "body": "disk full"}).encode()
req = urllib.request.Request(
    "https://attn.ca/api/v1/notify",
    data=data,
    headers={
        "Authorization": "******",
        "Content-Type": "application/x-www-form-urlencoded",
    },
)
urllib.request.urlopen(req)
```

Node.js — any OS, no dependencies (Node 18+)

Built-in `fetch` — passing a `URLSearchParams` as the body sets the right content type automatically.

```
await fetch("https://attn.ca/api/v1/notify", {
  method: "POST",
  headers: { Authorization: "******" },
  body: new URLSearchParams({ title: "Backup failed", body: "disk full" }),
});
```

Cron — only notify on failure

The `||` means the curl only runs if the job before it exits non-zero — a healthy night stays silent. Swap `******` for your own key.

```
# crontab -e
0 2 * * * /usr/local/bin/nightly-backup.sh || curl -fsS -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: ******" -H "Content-Type: application/json" \
  -d "{\"title\":\"Backup failed\",\"body\":\"$(hostname) at $(date -Iseconds)\"}"
```

GitHub Actions — notify on workflow failure

Add as a final step with `if: failure()`. Store the key as a repo secret and reference it in the Authorization header.

```
- name: Notify attn on failure
  if: failure()
  run: |
    curl -X POST https://attn.ca/api/v1/notify \
      -H "Authorization: ******" \
      -H "Content-Type: application/json" \
      -d '{"title":"CI failed","body":"workflow failed"}'
```

systemd — OnFailure= hook for any unit

A reusable notifier unit, wired up with systemd’s own failure-handling hook — no polling, no wrapper script around the real job.

```
# /etc/systemd/system/attn-notify@.service
[Unit]
Description=Ping attn.ca when %i fails

[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: ******" \
  -d '{"title":"Unit failed","body":"%i failed on %H"}'
```

Then point any unit you want watched at it: `OnFailure=attn-notify@%n.service`

Agents — Claude Code, Codex, and friends

Point an MCP-capable agent at attn and it can pair its own destination and send you a ping when it needs you — see [Agents & MCP](#mcp) for the server config and full tool list.

## How it works

The whole model in five nouns

- **Account** — your pastebin.ca identity, shared across the family. No separate signup.
- **Channel** — a destination *you own and verify*: Telegram, a browser (Web Push), or SMS. You can pair several; every verified one receives.
- **API key** — an audience-bound bearer token (shape `pbca_live_…`) scoped to attn and to this site only.
- **Scope** — what a key may do: `attn:notify` (send), `attn:read` (read activity/status), `attn:channels` (manage channels), `attn:delete` (redact settled notification history).
- **Fan-out** — one accepted notification is delivered to all your verified channels, retried per channel, at-least-once.

Self-notify onlyA notification can only reach channels the caller has paired and verified. There is no recipient field and no way to message anyone else.

## REST API

Authenticate

Send your key as a bearer token. Keys are issued by the pastebin.ca identity authority, audience-bound to `https://attn.ca`. Mint one from your [dashboard](/dashboard).

Full machine-readable spec: [openapi.json](/api/v1/openapi.json).

POST /api/v1/notify — send a notification

Scope `attn:notify`. Body (JSON or form): `title`, `body`, `priority` (`low`|`normal`|`high`, display-only), and an optional `dedup_key` for safe retries. At least one of `title`/`body` is required. Limits: title 256 characters, body 4000, dedup key 200; rate limit 60/minute and 1000/day per account. Dedup keys are account-scoped.

| Status | Meaning |
| --- | --- |
| `202` | Accepted; returns the notification `id`. A repeated `dedup_key` returns the original id without resending. |
| `400` | Missing title and body. |
| `401` | Missing or invalid key. |
| `403` | Key lacks the `attn:notify` scope, or is bound to another site. |
| `429` | Rate limited. |

```
curl -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: Bearer pbca_live_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"Backup done","body":"42 GB archived","dedup_key":"backup-2024-06-01"}'
```

Prefer not to hand-write JSON in a shell script? Send the same fields form-encoded instead — no quoting to get right, no `Content-Type` header to set (this is curl’s own default for `-d`):

```
curl -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: Bearer pbca_live_..." \
  -d "title=Backup done" \
  -d "body=42 GB archived" \
  -d "dedup_key=backup-2024-06-01"
```

Query-string parameters are deliberately not accepted for this endpoint — title/body would then be logged in plain sight everywhere a URL is (server/proxy access logs, browser history), which a personal alert pipe should not do to its own message content. See [Recipes](#recipes) for ready-to-paste examples in PowerShell, Python, and Node.js too.

GET /api/v1/notifications — read recent activity

Scope `attn:read`. Returns your recent notifications, per-channel delivery status, and summary stats. `limit` defaults to 25 and is capped at 100.

```
curl https://attn.ca/api/v1/notifications \
  -H "Authorization: Bearer pbca_live_..."
```

GET /api/v1/dnd — check Do Not Disturb

Scope `attn:read`. Reports whether delivery is currently silenced and until when. Enabling or clearing DND is a signed-in dashboard action (bounded to 8 hours; it always expires on its own).

```
{ "until": 1717200000000, "active": true }
```

GET /api/v1/credit — SMS credit balance & activity

Scope `attn:read`. Your remaining SMS credit (in segments, plus an informational USD estimate) and the last 20 ledger movements (grants, charges, refunds, deposits).

```
curl https://attn.ca/api/v1/credit \
  -H "Authorization: ******"
```

POST /api/v1/billing/checkout — buy SMS credit

Scope `attn:channels` for API-key callers; signed-in dashboard sessions are also accepted. Body `{ "amount_cents": number }` ($5–$500, 20 segments per dollar) creates a hosted Stripe Checkout session and returns its URL to redirect to. Credit lands automatically once Stripe confirms payment — see the [Billing tab](/dashboard#billing) to buy credit interactively.

| Status | Meaning |
| --- | --- |
| `200` | Returns `{ "checkout_url": string }` to redirect the browser to. |
| `400` | Amount outside the allowed range. |
| `429` | Rate limited. |
| `503` | Stripe is not configured on this environment. |

SMS segment cap — GET/POST /api/v1/sms/settings

Scope `attn:read` (GET) / `attn:channels` (POST). Caps how many segments a single SMS may split into (1–3, default 3) — longer text is truncated rather than costing extra segments. POST body `{ "sms_max_segments": number | null }` (`null` resets to the platform default).

```
{ "sms_max_segments": 1, "platform_max_segments": 3, "is_default": false }
```

Notification history — settings and deletion

`GET /api/v1/notification-settings` uses `attn:read`. Retention defaults to 90 days and can be set in the dashboard to 7, 30, 90, 365 days. `DELETE /api/v1/notifications` uses `attn:delete` and redacts settled title/body/dedup content; delivery outcomes and SMS billing metadata remain. In-flight notifications are left unchanged and can be deleted after they settle.

## Channels

Pair destinations you own

Pair and manage up to 10 channels from your [dashboard](/dashboard) or with an `attn:channels` API key:

- **Telegram** — open the bot deep-link and press *Start*. The chat is verified on the bot’s reply.
- **Web Push** — click *Enable browser notifications* and accept the permission prompt. Pair each browser/device you want pinged.
- **SMS** preview — verify a US/Canada (+1) mobile you own with a one-time code. SMS runs on prepaid credit (in segments): buy it yourself from the [Billing tab](/dashboard#billing), or an operator can grant it; you are never auto-charged.

REST routes are documented in OpenAPI: list/delete channels, Telegram pair + owner-scoped completion status, Web Push registration/public key, and SMS start/verify. Only verified channels you own ever receive. Agents can pair Telegram (`pair_telegram`), verify SMS (`start_sms_verification` / `verify_sms`), and check SMS credit (`get_credit`) via MCP tools.

On iPhone &amp; iPadAdd attn.ca to your Home Screen first — iOS only delivers Web Push to installed web apps.

## Agents & MCP

A first-class Model Context Protocol server

attn speaks the [Model Context Protocol](https://modelcontextprotocol.io) so AI agents can send you alerts and manage your channels. Point an MCP client at the endpoint below and authenticate with an attn API key (bearer) or an OAuth token from the pastebin.ca authority. The tools a client may call depend on the scopes of the key it presents.

| Tool | Scope | What it does |
| --- | --- | --- |
| `whoami` | none | Echo account id, scopes, audience, and token kind. |
| `send_notification` | `attn:notify` | Send yourself a notification to every verified channel. |
| `list_channels` | `attn:read` | List the caller's channels (no secret config). |
| `list_notifications` | `attn:read` | List the caller's recent notifications. |
| `pair_telegram` | `attn:channels` | Start Telegram pairing; returns a deep-link. |
| `delete_channel` | `attn:channels` | Remove one of the caller's channels by id. |
| `delete_notification_history` | `attn:delete` | Redact settled notification content while retaining delivery and billing metadata. |
| `get_credit` | `attn:read` | Show remaining SMS credit (segments) and recent activity. |
| `start_sms_verification` | `attn:channels` | Text a one-time code to an SMS number you own. |
| `verify_sms` | `attn:channels` | Confirm the SMS code and verify the channel. |
| `get_sms_settings` | `attn:read` | Show your SMS segment cap (max parts a single SMS may split into). |
| `set_sms_max_segments` | `attn:channels` | Set (or clear) your SMS segment cap, 1-3. |

Supported scopes: `attn:notify`, `attn:read`, `attn:channels`, `attn:delete`.

Connect a client

Most MCP clients take a server URL (and an optional bearer header). Example client config:

```
{
  "mcpServers": {
    "attn": {
      "url": "https://attn.ca/mcp",
      "headers": { "Authorization": "Bearer pbca_live_…" }
    }
  }
}
```

Discovery metadata is published for clients that auto-configure:

```
https://attn.ca/.well-known/agent.json
https://attn.ca/.well-known/oauth-protected-resource/mcp
```

## OAuth & security

Audience-bound keys, OAuth, and DPoP

- **Audience-bound.** A key is scoped to one site. An attn key only works on `attn.ca`; it is rejected everywhere else.
- **OAuth for agents.** The MCP endpoint is an OAuth protected resource. Tokens are issued by the pastebin.ca authority and may be DPoP-bound (sender-constrained) so a stolen token can’t be replayed.
- **Revoke instantly.** Revoke a key from your dashboard and anything using it stops immediately.


---

---
title: "attn.ca — agents & MCP"
source: "https://attn.ca/docs/mcp"
---
Developer guide — for agents

# Wire attn into your agent

A Model Context Protocol server and OAuth flow built for AI agents, plus the REST API underneath it. Sign in, connect a destination, mint a key, and call a tool.

[Agents & MCP](#mcp) [OAuth & security](#oauth) [Quick start](#quickstart) [Recipes](#recipes) [How it works](#concepts) [REST API](#rest) [Channels](#channels)

## Agents & MCP

A first-class Model Context Protocol server

attn speaks the [Model Context Protocol](https://modelcontextprotocol.io) so AI agents can send you alerts and manage your channels. Point an MCP client at the endpoint below and authenticate with an attn API key (bearer) or an OAuth token from the pastebin.ca authority. The tools a client may call depend on the scopes of the key it presents.

| Tool | Scope | What it does |
| --- | --- | --- |
| `whoami` | none | Echo account id, scopes, audience, and token kind. |
| `send_notification` | `attn:notify` | Send yourself a notification to every verified channel. |
| `list_channels` | `attn:read` | List the caller's channels (no secret config). |
| `list_notifications` | `attn:read` | List the caller's recent notifications. |
| `pair_telegram` | `attn:channels` | Start Telegram pairing; returns a deep-link. |
| `delete_channel` | `attn:channels` | Remove one of the caller's channels by id. |
| `delete_notification_history` | `attn:delete` | Redact settled notification content while retaining delivery and billing metadata. |
| `get_credit` | `attn:read` | Show remaining SMS credit (segments) and recent activity. |
| `start_sms_verification` | `attn:channels` | Text a one-time code to an SMS number you own. |
| `verify_sms` | `attn:channels` | Confirm the SMS code and verify the channel. |
| `get_sms_settings` | `attn:read` | Show your SMS segment cap (max parts a single SMS may split into). |
| `set_sms_max_segments` | `attn:channels` | Set (or clear) your SMS segment cap, 1-3. |

Supported scopes: `attn:notify`, `attn:read`, `attn:channels`, `attn:delete`.

Connect a client

Most MCP clients take a server URL (and an optional bearer header). Example client config:

```
{
  "mcpServers": {
    "attn": {
      "url": "https://attn.ca/mcp",
      "headers": { "Authorization": "Bearer pbca_live_…" }
    }
  }
}
```

Discovery metadata is published for clients that auto-configure:

```
https://attn.ca/.well-known/agent.json
https://attn.ca/.well-known/oauth-protected-resource/mcp
```

## OAuth & security

Audience-bound keys, OAuth, and DPoP

- **Audience-bound.** A key is scoped to one site. An attn key only works on `attn.ca`; it is rejected everywhere else.
- **OAuth for agents.** The MCP endpoint is an OAuth protected resource. Tokens are issued by the pastebin.ca authority and may be DPoP-bound (sender-constrained) so a stolen token can’t be replayed.
- **Revoke instantly.** Revoke a key from your dashboard and anything using it stops immediately.

## Quick start

From zero to your first alert

1. **Sign in.** attn shares your pastebin.ca account — there is nothing new to register. [Sign in and open your dashboard →](/auth/login?return_to=/dashboard)
2. **Connect a destination first.** Pair a Telegram chat or enable browser notifications so your test has somewhere to land.
3. **Mint an API key** with the `attn:notify` scope. Copy it immediately — the secret is shown once.
4. **Send your first notification** with the call below.

```
curl -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: Bearer pbca_live_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"Hello from attn","body":"Your first notification worked."}'
```

Expect `202 Accepted` with the notification id. Every verified device you own gets pinged.

```
{
  "id": "ntfy_3kQ…",
  "status": "accepted",
  "duplicate": false,
  "channels": 2,
  "suppressed": false
}
```

## Recipes

Windows — PowerShell

No curl quoting to fight with cmd.exe or PowerShell’s own escaping rules: `-Form` submits a hashtable as `multipart/form-data` for you.

```
$headers = @{ Authorization = "******" }
$form = @{ title = "Backup failed"; body = "disk full on $(hostname)" }
Invoke-RestMethod -Method Post -Uri "https://attn.ca/api/v1/notify" -Headers $headers -Form $form
```

Python — any OS, standard library only

No `pip install` needed — `urllib` ships with Python. Set the content type explicitly; unlike some HTTP libraries, `urllib` won’t infer it for you.

```
import urllib.request
from urllib.parse import urlencode

data = urlencode({"title": "Backup failed", "body": "disk full"}).encode()
req = urllib.request.Request(
    "https://attn.ca/api/v1/notify",
    data=data,
    headers={
        "Authorization": "******",
        "Content-Type": "application/x-www-form-urlencoded",
    },
)
urllib.request.urlopen(req)
```

Node.js — any OS, no dependencies (Node 18+)

Built-in `fetch` — passing a `URLSearchParams` as the body sets the right content type automatically.

```
await fetch("https://attn.ca/api/v1/notify", {
  method: "POST",
  headers: { Authorization: "******" },
  body: new URLSearchParams({ title: "Backup failed", body: "disk full" }),
});
```

Cron — only notify on failure

The `||` means the curl only runs if the job before it exits non-zero — a healthy night stays silent. Swap `******` for your own key.

```
# crontab -e
0 2 * * * /usr/local/bin/nightly-backup.sh || curl -fsS -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: ******" -H "Content-Type: application/json" \
  -d "{\"title\":\"Backup failed\",\"body\":\"$(hostname) at $(date -Iseconds)\"}"
```

GitHub Actions — notify on workflow failure

Add as a final step with `if: failure()`. Store the key as a repo secret and reference it in the Authorization header.

```
- name: Notify attn on failure
  if: failure()
  run: |
    curl -X POST https://attn.ca/api/v1/notify \
      -H "Authorization: ******" \
      -H "Content-Type: application/json" \
      -d '{"title":"CI failed","body":"workflow failed"}'
```

systemd — OnFailure= hook for any unit

A reusable notifier unit, wired up with systemd’s own failure-handling hook — no polling, no wrapper script around the real job.

```
# /etc/systemd/system/attn-notify@.service
[Unit]
Description=Ping attn.ca when %i fails

[Service]
Type=oneshot
ExecStart=/usr/bin/curl -fsS -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: ******" \
  -d '{"title":"Unit failed","body":"%i failed on %H"}'
```

Then point any unit you want watched at it: `OnFailure=attn-notify@%n.service`

Agents — Claude Code, Codex, and friends

Point an MCP-capable agent at attn and it can pair its own destination and send you a ping when it needs you — see [Agents & MCP](#mcp) for the server config and full tool list.

## How it works

The whole model in five nouns

- **Account** — your pastebin.ca identity, shared across the family. No separate signup.
- **Channel** — a destination *you own and verify*: Telegram, a browser (Web Push), or SMS. You can pair several; every verified one receives.
- **API key** — an audience-bound bearer token (shape `pbca_live_…`) scoped to attn and to this site only.
- **Scope** — what a key may do: `attn:notify` (send), `attn:read` (read activity/status), `attn:channels` (manage channels), `attn:delete` (redact settled notification history).
- **Fan-out** — one accepted notification is delivered to all your verified channels, retried per channel, at-least-once.

Self-notify onlyA notification can only reach channels the caller has paired and verified. There is no recipient field and no way to message anyone else.

## REST API

Authenticate

Send your key as a bearer token. Keys are issued by the pastebin.ca identity authority, audience-bound to `https://attn.ca`. Mint one from your [dashboard](/dashboard).

Full machine-readable spec: [openapi.json](/api/v1/openapi.json).

POST /api/v1/notify — send a notification

Scope `attn:notify`. Body (JSON or form): `title`, `body`, `priority` (`low`|`normal`|`high`, display-only), and an optional `dedup_key` for safe retries. At least one of `title`/`body` is required. Limits: title 256 characters, body 4000, dedup key 200; rate limit 60/minute and 1000/day per account. Dedup keys are account-scoped.

| Status | Meaning |
| --- | --- |
| `202` | Accepted; returns the notification `id`. A repeated `dedup_key` returns the original id without resending. |
| `400` | Missing title and body. |
| `401` | Missing or invalid key. |
| `403` | Key lacks the `attn:notify` scope, or is bound to another site. |
| `429` | Rate limited. |

```
curl -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: Bearer pbca_live_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"Backup done","body":"42 GB archived","dedup_key":"backup-2024-06-01"}'
```

Prefer not to hand-write JSON in a shell script? Send the same fields form-encoded instead — no quoting to get right, no `Content-Type` header to set (this is curl’s own default for `-d`):

```
curl -X POST https://attn.ca/api/v1/notify \
  -H "Authorization: Bearer pbca_live_..." \
  -d "title=Backup done" \
  -d "body=42 GB archived" \
  -d "dedup_key=backup-2024-06-01"
```

Query-string parameters are deliberately not accepted for this endpoint — title/body would then be logged in plain sight everywhere a URL is (server/proxy access logs, browser history), which a personal alert pipe should not do to its own message content. See [Recipes](#recipes) for ready-to-paste examples in PowerShell, Python, and Node.js too.

GET /api/v1/notifications — read recent activity

Scope `attn:read`. Returns your recent notifications, per-channel delivery status, and summary stats. `limit` defaults to 25 and is capped at 100.

```
curl https://attn.ca/api/v1/notifications \
  -H "Authorization: Bearer pbca_live_..."
```

GET /api/v1/dnd — check Do Not Disturb

Scope `attn:read`. Reports whether delivery is currently silenced and until when. Enabling or clearing DND is a signed-in dashboard action (bounded to 8 hours; it always expires on its own).

```
{ "until": 1717200000000, "active": true }
```

GET /api/v1/credit — SMS credit balance & activity

Scope `attn:read`. Your remaining SMS credit (in segments, plus an informational USD estimate) and the last 20 ledger movements (grants, charges, refunds, deposits).

```
curl https://attn.ca/api/v1/credit \
  -H "Authorization: ******"
```

POST /api/v1/billing/checkout — buy SMS credit

Scope `attn:channels` for API-key callers; signed-in dashboard sessions are also accepted. Body `{ "amount_cents": number }` ($5–$500, 20 segments per dollar) creates a hosted Stripe Checkout session and returns its URL to redirect to. Credit lands automatically once Stripe confirms payment — see the [Billing tab](/dashboard#billing) to buy credit interactively.

| Status | Meaning |
| --- | --- |
| `200` | Returns `{ "checkout_url": string }` to redirect the browser to. |
| `400` | Amount outside the allowed range. |
| `429` | Rate limited. |
| `503` | Stripe is not configured on this environment. |

SMS segment cap — GET/POST /api/v1/sms/settings

Scope `attn:read` (GET) / `attn:channels` (POST). Caps how many segments a single SMS may split into (1–3, default 3) — longer text is truncated rather than costing extra segments. POST body `{ "sms_max_segments": number | null }` (`null` resets to the platform default).

```
{ "sms_max_segments": 1, "platform_max_segments": 3, "is_default": false }
```

Notification history — settings and deletion

`GET /api/v1/notification-settings` uses `attn:read`. Retention defaults to 90 days and can be set in the dashboard to 7, 30, 90, 365 days. `DELETE /api/v1/notifications` uses `attn:delete` and redacts settled title/body/dedup content; delivery outcomes and SMS billing metadata remain. In-flight notifications are left unchanged and can be deleted after they settle.

## Channels

Pair destinations you own

Pair and manage up to 10 channels from your [dashboard](/dashboard) or with an `attn:channels` API key:

- **Telegram** — open the bot deep-link and press *Start*. The chat is verified on the bot’s reply.
- **Web Push** — click *Enable browser notifications* and accept the permission prompt. Pair each browser/device you want pinged.
- **SMS** preview — verify a US/Canada (+1) mobile you own with a one-time code. SMS runs on prepaid credit (in segments): buy it yourself from the [Billing tab](/dashboard#billing), or an operator can grant it; you are never auto-charged.

REST routes are documented in OpenAPI: list/delete channels, Telegram pair + owner-scoped completion status, Web Push registration/public key, and SMS start/verify. Only verified channels you own ever receive. Agents can pair Telegram (`pair_telegram`), verify SMS (`start_sms_verification` / `verify_sms`), and check SMS credit (`get_credit`) via MCP tools.

On iPhone &amp; iPadAdd attn.ca to your Home Screen first — iOS only delivers Web Push to installed web apps.

