Alerts
One feed of every alert Mythic raises across your clients, rules that route them, and the Slack and webhook channels they are delivered to, on /client/v1/alerts with an agency key.
Overview
Mythic already watches your clients: event contracts, short link uptime, data syncs, incrementality experiments and usage. The Alerts API puts everything those detectors find in one feed and lets you decide where it goes.
| Route | What it does |
|---|---|
GET /alerts/events | The feed across every client, newest first |
GET /alerts/events/{id} | One alert and every delivery attempt made for it |
PATCH /alerts/events/{id} | Resolve an alert by hand |
/alerts/rules | Which sources, for which clients, at what severity, go to which channels |
/alerts/channels | Slack incoming webhooks and signed HTTPS webhooks |
POST /alerts/channels/{id}/test | Send a test message now |
The same operations are MCP tools: list_alerts, get_alert,
resolve_alert, list_alert_rules, create_alert_rule, update_alert_rule,
delete_alert_rule, list_alert_channels, create_alert_channel,
update_alert_channel, delete_alert_channel and test_alert_channel.
Base URL
https://mythic-analytics.gulp.workers.dev/client/v1/alerts
Authentication
Agency key (ak_), or an agency-wide mcp_ key with alerts:read or
alerts:write. A location secret key (sk_) gets 403 agency_required.
This surface serves no CORS headers. Call it from your server, never from browser JavaScript.
Sources
| Source | Raised when | Severity | Checked |
|---|---|---|---|
contracts | Events violate their contract | warning | Daily, ~08:00 UTC |
contracts | The client sent no events all day | critical | Daily, ~08:00 UTC |
uptime | A short link destination fails 2 checks in a row | critical | Every 5 minutes |
airbyte | A sync of a connection Mythic manages fails | warning | On each sync |
drift | An incrementality experiment has new outlier days in its test window | info | Daily |
quota | The agency passes 80% of a monthly event quota (agency-level, no client) | info | Daily |
quota | The agency reaches 100% of a monthly event quota | warning | Daily |
airbyte covers connections created through Mythic. A connection built
directly in Airbyte has no Mythic record, so its syncs raise nothing.
Each detector keeps its own threshold. A rule never re-decides whether something is broken; it only decides who hears about it.
The contracts and drift checks run for a client when either its own
webhook is set (/settings/contract-alerts, or alert_webhook_url on the
experiment) or your agency has an enabled rule that routes that source. Those
per-feature webhooks keep working unchanged.
How an alert moves
One condition is one alert. It opens once and notifies the channels your
rules select. The detector reporting it again only increases occurrences.
If the condition changes shape (a different set of failing contracts, a new
outlier day, a higher severity) it notifies again as changed. When the
detector clears it, it resolves and notifies rules with
notify_on_resolve. If the condition comes back later, a new alert opens.
An alert also resolves when its detector stops watching the subject: the link is disabled or deleted, the client's contracts are removed, the experiment ends, the Airbyte connection is deleted, or the agency has no clients left. Without that, nothing would ever report the recovery.
Resolving by hand (PATCH /alerts/events/{id}) sends nothing.
Set up Slack in two calls
Create the channel
curl -X POST "https://mythic-analytics.gulp.workers.dev/client/v1/alerts/channels" \
-H "Authorization: Bearer $AK" -H "Content-Type: application/json" \
-d '{"kind":"slack","target":"https://hooks.slack.com/services/T000/B000/XXXX","label":"#client-alerts"}'
Route every client's alerts to it
curl -X POST "https://mythic-analytics.gulp.workers.dev/client/v1/alerts/rules" \
-H "Authorization: Bearer $AK" -H "Content-Type: application/json" \
-d '{"name":"Everything to Slack","channel_ids":["<channel id>"]}'
Empty sources and location_ids mean every source and every client,
including clients you add later. min_severity defaults to info, so this
rule receives every alert. Set "min_severity":"warning" to skip drift and
the 80% quota notice.
Webhook deliveries
A webhook channel receives a POST for each transition:
{
"type": "alert.opened",
"alert": {
"id": "5f0c…",
"source": "uptime",
"fingerprint": "uptime:loc_abc123:spring-sale",
"location_id": "loc_abc123",
"location_name": "Rockwell Supply",
"severity": "critical",
"title": "Short link /spring-sale is down",
"detail": "https://rockwellsupply.com/sale failed 2 checks in a row (HTTP 404).",
"payload": { "slug": "spring-sale", "status_code": 404 },
"status": "open",
"occurrences": 1
},
"sent_at": "2026-09-24T14:02:11.000Z"
}
type is alert.opened, alert.changed, alert.resolved or alert.test.
A test message has alert.source set to test. Every delivery carries
X-Mythic-Event and X-Mythic-Signature: sha256=<hex>, the HMAC-SHA256 of the
raw body with the channel's signing secret. That secret is returned once, when
the channel is created.
One alert can send alert.changed several times, once per change, so
alert.id plus type does not identify a message. Mythic does not retry, so
there are no duplicates to drop.
Delivery is one attempt per transition with a 10-second timeout, recorded on
the alert (GET /alerts/events/{id}). There is no retry queue. After 20
consecutive failures a channel is parked (active: false); set active: true
to resume it.
Limits
50 rules and 20 channels per agency, 10 channels per rule, 120 requests per
minute. Channel targets must be https:// and are shown back as the origin
only (https://hooks.slack.com/***), because many webhook urls carry their
credential in the path. Use label to tell channels apart. Email delivery is
not available yet.