Predict.aiDocs
Goals

Alerts

Conditions on the goal's forecast — or on the event flow itself — with a live preview before you save.

An alert is a condition on the goal's forecast — warn me if the forecast drops below 10,000 at any point over the horizon. Conditions are evaluated after every champion forecast; when one triggers, it fires on the goal's realtime channel as an alerts.evaluated event and through your notification channels.

Alerts can also watch the event flow instead of the forecast — an important event just hit, the goal entered shock mode, a stream went quiet — see event rules.

The condition shape

{
  "metric": "forecast_value",
  "scope": "any",
  "operator": "lt",
  "threshold": 10000
}
FieldMeaning
metric requiredforecast_value, forecast_change_pct, uncertainty_pct, crossing, or probability — a fleet metric (member_score, top_decile_entries, rank_jump, fleet_median_score) — or an event rule (event_fired, shock_entered, event_quiet).
scope requiredWhich part of the horizon: any, last, min, max, or mean. Ignored by fleet and event metrics.
operator requiredgt, gte, lt, or lte. Event rules fix this to gte.
threshold requiredA number (0–100 for probability — percent likelihood).
streamEvent rules only — the event stream to watch.

The metric set follows the goal type: forecast_value, forecast_change_pct, uncertainty_pct, and crossing apply to level goals (forecast_value, forecast_distribution); trend and anomaly goals serve a probability, so only probability applies; and fleet goals (rank_by_risk, rank_by_growth) use the leaderboard-shaped set. The three event rules apply to every goal type. Using the wrong set returns a 400 that names the right one.

Create an alert

curl -X POST "$API_BASE/v1/goals/$GOAL_ID/alerts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sales dip warning",
    "condition": { "metric": "forecast_value", "scope": "any", "operator": "lt", "threshold": 10000 },
    "severity": "warning",
    "cooldown_minutes": 240,
    "channels": ["email", "slack"]
  }'
{
  "data": {
    "alert": {
      "alert_id": "alr_9F2C41D8A0B1C2D3E4F5A6",
      "goal_id": "b7e9c2d4-…",
      "name": "Sales dip warning",
      "enabled": true,
      "severity": "warning",
      "condition": { "metric": "forecast_value", "scope": "any", "operator": "lt", "threshold": 10000.0 },
      "cooldown_minutes": 240,
      "channels": ["email", "slack"],
      "recipients": [],
      "created_at": "2026-07-15T15:00:00+00:00",
      "last_evaluated_at": null,
      "last_triggered_at": null,
      "trigger_count": 0,
      "last_result": null
    }
  }
}

Request body

FieldMeaning
name requiredDisplay name for the alert.
condition requiredThe condition shape above.
severity optionalwarning (default) or critical.
cooldown_minutes optionalMinimum time between firings. Defaults to 240; 0 re-alerts on every evaluation; capped at one week.
channels optionalFrom in_app, email, websocket, slack, teams, pagerduty, webhook. null falls back to each recipient's notification preferences.
recipients optionalUser IDs. Adding people other than yourself requires an organization workspace, and they must be active members.

Errors

StatusWhyExample message
400Missing or invalid field"name is required" · "condition.metric must be one of forecast_value, forecast_change_pct, uncertainty_pct, crossing, probability, member_score, top_decile_entries, rank_jump, fleet_median_score, event_fired, shock_entered, event_quiet" · "severity must be one of warning, critical"
400Metric doesn't fit the goal type"condition.metric 'forecast_value' does not apply to a trend goal (it serves a probability); use one of: probability"
400Cap reached"Alert limit reached (25 per goal)"
404Goal doesn't exist"Goal not found"

List alerts

curl "$API_BASE/v1/goals/$GOAL_ID/alerts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID"
{
  "data": {
    "items": [
      {
        "alert_id": "alr_9F2C41D8A0B1C2D3E4F5A6",
        "name": "Sales dip warning",
        "enabled": true,
        "trigger_count": 3,
        "last_triggered_at": "2026-07-14T06:00:19+00:00",
        "last_result": { "triggered": false, "observed": 12480.1, "at": "2026-07-15T06:00:12+00:00" },
        "…": "…"
      }
    ],
    "has_forecast": true,
    "goal_type": "forecast_value",
    "metrics": ["forecast_value", "forecast_change_pct", "uncertainty_pct", "crossing", "event_fired", "shock_entered", "event_quiet"],
    "event_streams": ["press_mentions", "promotions"]
  }
}

metrics is the valid metric set for this goal's type, has_forecast tells you whether there's anything to evaluate against yet, and event_streams lists the workspace's streams for event rules.

Update & delete

Updates are partial — send only what changes:

curl -X PUT "$API_BASE/v1/goals/$GOAL_ID/alerts/$ALERT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
{ "data": { "alert": { "alert_id": "alr_9F2C…", "enabled": false, "…": "…" } } }
curl -X DELETE "$API_BASE/v1/goals/$GOAL_ID/alerts/$ALERT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID"
{ "data": { "deleted": "alr_9F2C41D8A0B1C2D3E4F5A6" } }

Unknown alert IDs return 404 with "Alert not found" on update.

Preview a condition

Dry-run a condition against the champion's latest forecast — "would this trigger right now?" — before saving. Free:

curl -X POST "$API_BASE/v1/goals/$GOAL_ID/alerts/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{ "condition": { "metric": "forecast_value", "scope": "any", "operator": "lt", "threshold": 10000 } }'
{
  "data": {
    "available": true,
    "triggered": false,
    "observed": 12480.1,
    "detail": "Lowest forecast point is 12480.1 — above the 10000 threshold.",
    "condition_label": "forecast below 10000 $ at any point over the horizon",
    "forecast": {
      "at": "2026-07-15T06:00:12+00:00",
      "label": "daily_sales",
      "anchor": 12841.5,
      "path": [13102.4, 13350.9, "…"],
      "p10": [12480.1, "…"],
      "p90": [13724.9, "…"]
    },
    "unit": "$",
    "goal_type": "forecast_value"
  }
}

If there's no forecast yet, you get 200 with available: false and a reason ("No forecast yet — deploy a champion and let it serve once.").

Event rules

Three metrics watch the event flow rather than the forecast path. They use the same CRUD endpoints, severities, cooldowns, and channels, apply to every goal type, and are evaluated by their own hooks — the moment the thing happens, not on the next champion serve:

MetricFires whenEvaluatedthresholdstream
event_firedAn event on stream scores importance ≥ thresholdSeconds after the event is scored0–100 (percent importance)Required
shock_enteredThe goal flips into shock modeAt shock entryUnused (send 0)Optional — restrict to one stream
event_quietstream has had no event for ≥ threshold hoursPeriodic sweep (a few minutes' granularity)≥ 1 (hours)Required

scope and operator are fixed (any / gte) — "at or above" is the only reading that makes sense for all three. event_quiet is the dead-man's-switch for pipelines that should produce events: a webhook that went silent, a scraper that stopped scraping.

curl -X POST "$API_BASE/v1/goals/$GOAL_ID/alerts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Major press event",
    "condition": { "metric": "event_fired", "stream": "press_mentions", "operator": "gte", "scope": "any", "threshold": 70 },
    "severity": "critical",
    "channels": ["slack"]
  }'

Validation errors name the problem: a missing stream returns "condition.stream is required for event rules"; an event_fired threshold outside 0–100 returns "condition.threshold must be 0–100 (percent importance)"; an event_quiet threshold under 1 returns "condition.threshold must be at least 1 (hours without an event)".

Notes that differ from forecast alerts:

  • Scheduled events don't fire event_fired at recording time — the rule evaluates when the event actually occurs.
  • Preview works, but reports where the watched quantity stands now instead of a forecast frame: the response carries event_rule: true and a detail like "latest event scored importance 45% vs threshold 70%" or "goal is currently in 'normal' state" — no forecast object.
  • The alert list response's metrics array always includes the three event metrics, and the list endpoint also returns event_streams — the workspace's stream keys — so builders can offer a picker.

Fleet goals

Fleet goals (rank_by_risk, rank_by_growth) use the same CRUD endpoints, cooldowns, and channels, but their conditions read the reduced leaderboard, evaluated once per completed scoring run rather than per champion serve:

MetricObserved valueThreshold
member_scoreThe extreme member's score in the operator's direction, as a percent (0.93 → 93)Percent
top_decile_entriesMembers that entered the top decile this run (new to the board, or previously ranked below it)Non-negative count
rank_jumpThe biggest single-run rank climb, in positionsNon-negative count
fleet_median_scoreThe fleet's median score, as a percent — catches whole-population driftPercent

Notes that differ from single-signal alerts:

  • scope is ignored — there is no horizon path to scope over; send "any".
  • A triggered condition delivers one digest naming the offending members (capped, worst first), not one notification per member. Cooldowns apply as usual, and delivery is idempotent per rule per run.
  • Preview doesn't apply: fleet goals have no champion forecast, so the endpoint returns available: false. The condition_label in the alert document still renders the human sentence (e.g. "any member's failure risk above 90%").
  • alerts.evaluated still fires on the goal's realtime channel after each run's evaluation, with the alert IDs that triggered.
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/alerts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pump failure watch",
    "condition": { "metric": "member_score", "scope": "any", "operator": "gte", "threshold": 90 },
    "severity": "critical",
    "channels": ["pagerduty"]
  }'

When an alert fires

Forecast conditions are evaluated after every champion serve; fleet conditions once per scoring run; event rules the moment their event happens. A triggered alert (outside its cooldown) does two things:

  • publishes alerts.evaluated on the goal's goal:{goal_id} realtime channel, with the alert IDs that fired — so a dashboard or bot reacts within the same second;
  • delivers through the alert's channels (or each recipient's preferences) via Notifications, with the human-readable condition label and the observed value.

The alert itself records last_evaluated_at, last_triggered_at, trigger_count, and last_result, so the list view is an audit trail.

On this page