OverviewAlerts

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.

RouteWhat it does
GET /alerts/eventsThe 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/rulesWhich sources, for which clients, at what severity, go to which channels
/alerts/channelsSlack incoming webhooks and signed HTTPS webhooks
POST /alerts/channels/{id}/testSend 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

SourceRaised whenSeverityChecked
contractsEvents violate their contractwarningDaily, ~08:00 UTC
contractsThe client sent no events all daycriticalDaily, ~08:00 UTC
uptimeA short link destination fails 2 checks in a rowcriticalEvery 5 minutes
airbyteA sync of a connection Mythic manages failswarningOn each sync
driftAn incrementality experiment has new outlier days in its test windowinfoDaily
quotaThe agency passes 80% of a monthly event quota (agency-level, no client)infoDaily
quotaThe agency reaches 100% of a monthly event quotawarningDaily

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.