Forecast
Read the goal's live forecast and track record, force a refresh, and see the pressure behind it.
Once a champion is deployed, the goal has a forecast — refreshed on the goal's cadence while data keeps arriving.
Get the forecast
curl "$API_BASE/v1/goals/$GOAL_ID/forecast" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.get_goals_by_goal_id_forecast(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsByGoalIdForecast(GOAL_ID);
{
"data": {
"goal_id": "b7e9c2d4-…",
"goal_name": "Daily sales",
"goal_type": "forecast_value",
"target_key": "daily_sales",
"interval_seconds": 86400,
"horizon_bins": 14,
"champion_pipeline_id": "9f2c41d8-…",
"shock_state": "normal",
"shock_stream": null,
"forecast": {
"at": "2026-07-15T06:00:12+00:00",
"inference_uid": "3f1a…",
"training_id": "t_5b2e…",
"interval_seconds": 86400,
"horizon_bins": 14,
"serving_mode": "forecast",
"unit": "$",
"point": [13102.4, 13350.9, 13571.2, "…"],
"quantiles": {
"0.1": [12480.1, 12633.7, "…"],
"0.9": [13724.9, 14068.2, "…"]
},
"history": {
"values": [12841.5, 12933.0, "…"],
"step_at": ["2026-07-13T00:00:00+00:00", "2026-07-14T00:00:00+00:00", "…"]
},
"step_at": ["2026-07-16T00:00:00+00:00", "2026-07-17T00:00:00+00:00", "…"],
"model_quality": {
"accuracy_score": 91.4,
"skill_score": 34.2,
"score_kind": "smape"
}
},
"track_record": {
"horizon_accuracy": [{ "step": 1, "accuracy": 94.1 }, "…"],
"interval_coverage": 0.87
}
}
}Response fields
| Field | Meaning |
|---|---|
forecast.point / forecast.step_at | The chart-ready path: one value and one timestamp per horizon step. |
forecast.quantiles | Uncertainty bands keyed by level, when the champion produces them. |
forecast.history | An inlined snapshot of recent actuals, for context to the left of the forecast. |
forecast.serving_mode | How to read the numbers — forecast for level goals, a probability in [0, 1] for trend and anomaly goals. |
forecast.model_quality | The champion's held-out accuracy_score and skill_score (value over a naive baseline). |
track_record | The nightly-aggregated honesty check: how past forecasts actually scored per horizon step, and how often actuals landed inside the quantile band (interval_coverage). |
shock_state / shock_stream | Flips from normal when a shock protocol is active — see shock analogues. |
forecast is null until a champion exists; if a champion is live but
hasn't been invoked yet, this read serves one on demand so it's never
empty. shock_state flips from normal when a shock protocol is active —
see shock analogues below.
This endpoint is a rich studio read. For production integrations, call
the inference goal alias —
POST /v1/inference/goals/{goal_id}/infer-segment — which always routes
to the current champion, so your URL survives champion swaps and
re-deployments.
Force a fresh forecast
The GET only serves on demand when no forecast exists. To re-infer on the newest data even if the stream hasn't advanced:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/forecast/rerun" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_forecast_rerun(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdForecastRerun(GOAL_ID);
The response is the same shape as the GET, plus "reran": true. Returns
409 with "No deployed model to run — this goal has no champion yet."
until a champion is live.
Why this forecast: drivers-now
The validated graph projected as current pressure — each driver's latest value, its move over its own validated lag, and the direction it's pushing the target. Computed deterministically from stored edge evidence:
curl "$API_BASE/v1/goals/$GOAL_ID/drivers-now" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.get_goals_by_goal_id_drivers_now(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsByGoalIdDriversNow(GOAL_ID);
{
"data": {
"drivers": [
{
"key": "ad_spend",
"kind": "signal",
"edge_id": "7d3f…",
"lifecycle": "validated",
"direction": "positive",
"lag_bins": 3,
"lag_seconds": 259200,
"lift": 0.18,
"confidence": 0.94,
"impact": 0.61,
"tier": "validated",
"current_value": 8420.0,
"value_at_lag": 7630.0,
"change_pct": 10.35,
"pressure": "upward",
"narrative": "ad_spend rose 10.4% over its 3-step lead → upward pressure now",
"unit": "$",
"simulatable": true
}
],
"predictive_drivers": [
{ "key": "competitor_price", "tier": "suggestive", "confidence": 0.71, "impact": 0.33, "…": "…" }
],
"as_of": "2026-07-15T12:00:03+00:00",
"interval_seconds": 86400,
"target_unit": "$",
"scenario_examples": { "examples": ["What if ad spend rises 10%?"] }
}
}Response fields
| Field | Meaning |
|---|---|
drivers | The validated tier — every entry is a relationship that survived the full discovery funnel. |
predictive_drivers | The wider suggestive set the model can also learn from, with the same confidence-vs-impact axes. |
drivers[].direction / pressure | Which way the driver pushes the target, and which way it's pushing right now given its latest move. |
drivers[].lag_bins / lag_seconds | The validated lead time — how far ahead this driver moves before the target follows. |
drivers[].current_value / value_at_lag / change_pct | The driver's latest value, its value one lag ago, and the move between them. |
drivers[].confidence / impact / lift | How sure the platform is, how much the target responds, and the measured predictive lift. |
drivers[].simulatable | true means you can move this driver in Scenarios. |
scenario_examples | Ready-made what-if prompts for this goal. |
Shock analogues
When one of the goal's connected event streams spikes, the goal enters a shock state and the platform attaches an analogue fan: in similar past events, here's how the target moved within 24 hours.
curl "$API_BASE/v1/goals/$GOAL_ID/shock-analogues" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID"from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.get_goals_by_goal_id_shock_analogues(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsByGoalIdShockAnalogues(GOAL_ID);
{
"data": {
"goal_id": "b7e9c2d4-…",
"shock_state": "shocked",
"entry": {
"entry_id": "sh_20b4…",
"stream": "supply_disruptions",
"entered_at": "2026-07-15T04:12:00+00:00",
"exited_at": null,
"analogues": [
{ "occurred_at": "2026-03-02T…", "target_move_pct": -6.2, "similarity": 0.91 }
]
},
"analogues": [
{ "occurred_at": "2026-03-02T…", "target_move_pct": -6.2, "similarity": 0.91 }
]
}
}entry and analogues are null when the goal has never shocked. The
shock state also shows on the forecast read
(shock_state, shock_stream) so consumers can caveat the headline number.

