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
- Sign in. attn shares your pastebin.ca account — there is nothing new to register. Sign in and open your dashboard →
- Connect a destination first. Pair a Telegram chat or enable browser notifications so your test has somewhere to land.
- Mint an API key with the
attn:notifyscope. Copy it immediately — the secret is shown once. - 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
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 $formNo 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)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" }),
});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)\"}"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"}'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
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 for the server config and full tool list.
How it works
- 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.
REST API
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.
Full machine-readable spec: openapi.json.
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 for ready-to-paste examples in PowerShell, Python, and Node.js too.
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_..."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 }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: ******"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 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. |
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 }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 and manage up to 10 channels from your 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, 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.
Agents & MCP
attn speaks the Model Context Protocol 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.
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/mcpOAuth & security
- 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.