Alerts that watch the forecast
Put a condition on the future — get told the moment the forecast crosses it, on the channels you choose.
Dashboards answer questions you remember to ask. An alert watches the forecast for you: warn me if projected sales dip below 10,000 at any point over the horizon. Conditions are evaluated after every champion serve, and firings deliver through notifications — Slack, email, your own webhook, or a live stream your app listens to.
You'll need: a goal with a live champion — the one from A goal on autopilot.
1. Dry-run the condition first
Preview answers "would this trigger right now?" against the champion's latest forecast — free, and it saves you from alerts that fire instantly or never:
from predictai import PredictAI
client = PredictAI(token="pa_live_...", workspace_id="ws_...")
preview = client.goals.post_goals_by_goal_id_alerts_preview(GOAL_ID, json={
"condition": {"metric": "forecast_value", "scope": "any", "operator": "lt", "threshold": 10000},
})["data"]
print(preview["condition_label"])
print("would trigger now:", preview["triggered"], "-", preview["detail"])import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_...", workspaceId: "ws_..." });
const { data: preview } = await client.goals.postGoalsByGoalIdAlertsPreview(GOAL_ID, {
json: {
condition: { metric: "forecast_value", scope: "any", operator: "lt", threshold: 10000 },
},
});
console.log(preview.condition_label);
console.log("would trigger now:", preview.triggered, "-", preview.detail);forecast below 10000 $ at any point over the horizon
would trigger now: False - Lowest forecast point is 12480.1 — above the 10000 threshold.Conditions compose from four parts: a metric (forecast_value,
forecast_change_pct, uncertainty_pct, crossing — or probability
for trend/anomaly goals), a scope over the horizon (any, last,
min, max, mean), an operator, and a threshold.
2. Point a channel at your systems
in_app, email, and websocket are built in. For Slack, Teams,
PagerDuty, or your own HTTPS endpoint, register a
channel endpoint once — then test-fire
it before trusting it:
channel = client.notifications.post_notifications_channels(json={
"type": "slack",
"label": "Ops Slack",
"config": {"url": "https://hooks.slack.com/services/T000/B000/XXXX"},
})["data"]["channel"]
test = client.notifications.post_notifications_channels_by_channel_id_test(channel["channel_id"])
print("test delivery:", test["data"])const { data } = await client.notifications.postNotificationsChannels({
json: {
type: "slack",
label: "Ops Slack",
config: { url: "https://hooks.slack.com/services/T000/B000/XXXX" },
},
});
const test = await client.notifications.postNotificationsChannelsByChannelIdTest(
data.channel.channel_id,
);
console.log("test delivery:", test.data);3. Create the alert
alert = client.goals.post_goals_by_goal_id_alerts(GOAL_ID, json={
"name": "Sales dip warning",
"condition": {"metric": "forecast_value", "scope": "any", "operator": "lt", "threshold": 10000},
"severity": "warning",
"cooldown_minutes": 240,
"channels": ["email", "slack"],
})["data"]["alert"]
print("alert:", alert["alert_id"], "enabled:", alert["enabled"])const { data: created } = await client.goals.postGoalsByGoalIdAlerts(GOAL_ID, {
json: {
name: "Sales dip warning",
condition: { metric: "forecast_value", scope: "any", operator: "lt", threshold: 10000 },
severity: "warning",
cooldown_minutes: 240,
channels: ["email", "slack"],
},
});
console.log("alert:", created.alert.alert_id, "enabled:", created.alert.enabled);From now on, every time the champion serves a forecast the condition is
evaluated. A firing (outside its 4-hour cooldown) publishes
alerts.evaluated on the goal's realtime channel and
delivers through the alert's channels with the human-readable condition
label and the observed value.
4. Listen live
For your own app, the notification stream pushes every feed item over Server-Sent Events the moment it lands:
import json
response = client.notifications.get_notifications_stream(stream=True)
for line in response.iter_lines():
if line.startswith(b"data: "):
item = json.loads(line[6:])
if item.get("type") == "notification":
print(f'[{item["severity"]}] {item["title"]}: {item["body"]}')const response = await client.notifications.getNotificationsStream({ raw: true });
const reader = response.body!.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
for (const line of value.split("\n")) {
if (!line.startsWith("data: ")) continue;
const item = JSON.parse(line.slice(6));
if (item.type === "notification") {
console.log(`[${item.severity}] ${item.title}: ${item.body}`);
}
}
}[warning] Alert: Sales dip warning: forecast below 10000 $ at any point over the horizon — observed 9,842.3.Keep it honest
Every alert keeps its own audit trail — last_evaluated_at,
last_triggered_at, trigger_count, and last_result on the
list route — so you can see at a
glance whether a quiet alert is quiet because things are fine or because
it never evaluates. Tune thresholds with preview (free), silence one with
{"enabled": false}, and cap noise with cooldown_minutes.
That closes the loop this example series opened: data flows in as signals, models turn it into forecasts, goals keep the forecasts honest and explained, and alerts turn them into action — without anyone watching a dashboard.
What-if scenarios
Move a driver, replay an event, and watch the forecast respond — counterfactuals served by the live champion.
An anomaly goal, end to end
Upload a CSV in one call, create an anomaly goal, watch everything happen live over WebSockets, and wire alerts — the whole platform in one sitting.

