Scenarios
What-if simulations over the discovered graph — move a driver, inject an event, and see the forecast respond.
A scenario asks the goal's champion a counterfactual: what does the forecast look like if a driver moves, or an event fires? The intervention is applied to the serving data, derived features are recomputed, and the champion re-infers — so the answer is the model's real response, not a rule of thumb.
Scenario runs are charged upfront and refunded if the run fails. Results are persisted — saved scenarios are free to view.
Run a scenario
A spec needs at least one driver override or one injected event:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/scenarios" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Ad spend +10%",
"overrides": [
{ "key": "ad_spend", "mode": "pct", "amount": 10, "shape": "step" }
],
"events": []
}'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_scenarios(GOAL_ID, json={
"name": "Ad spend +10%",
"overrides": [
{
"key": "ad_spend",
"mode": "pct",
"amount": 10,
"shape": "step",
},
],
"events": [],
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdScenarios(GOAL_ID, {
json: {
name: "Ad spend +10%",
overrides: [
{
key: "ad_spend",
mode: "pct",
amount: 10,
shape: "step",
},
],
events: [],
},
});
{
"data": {
"scenario_id": "d2f7…",
"name": "Ad spend +10%",
"spec": { "overrides": [{ "key": "ad_spend", "mode": "pct", "amount": 10.0, "shape": "step" }], "events": [] },
"mechanisms": ["override"],
"baseline": { "point": [13102.4, 13350.9, "…"] },
"fan": { "point": [13298.7, 13617.5, "…"] },
"delta": [196.3, 266.6, "…"],
"applied_overrides": [{ "key": "ad_spend", "mode": "pct", "amount": 10.0 }],
"attribution": [{ "key": "ad_spend", "share": 1.0 }],
"chains": [],
"events": [],
"caveats": [
"model response under intervention — a validated predictive relationship, not a causal guarantee"
],
"warnings": [],
"snapshot": {
"window_end": "2026-07-15T00:00:00+00:00",
"champion_pipeline_id": "9f2c41d8-…",
"horizon_bins": 14,
"interval_seconds": 86400,
"target_unit": "$"
},
"credits_charged": 5,
"created_at": "2026-07-15T12:30:00+00:00"
}
}The 201 body is the persisted scenario: the baseline forecast, the
counterfactual fan (with quantile bands when the champion produces
them), the per-step delta, and the provenance — which overrides applied,
which multi-hop chains carried an upstream move, and honest caveats.
Request body
| Field | Meaning |
|---|---|
overrides required | Driver moves — at least one override or one event. Up to a handful per scenario. |
events optional | Injected occurrences on event streams. |
name optional | Display name for the saved scenario. |
Each overrides[] entry:
| Field | Meaning |
|---|---|
key required | The driver to move — must be flagged simulatable in drivers-now. |
mode required | pct (percent), abs (absolute), or sigma (standard deviations). |
amount required | How much, in the chosen mode's units. |
shape optional | step (immediate, the default) or ramp (gradual). |
Each events[] entry:
| Field | Meaning |
|---|---|
stream required | The event stream to inject on. |
magnitude optional | Size versus a typical event, 0.1–5. Defaults to 1. |
direction optional | 1 or -1. The response replays the stream's measured response curve. |
Errors
| Status | Why | Example message |
|---|---|---|
400 | Empty or invalid spec (validated before charging) | "Scenario needs at least one driver override or one injected event" · "Unknown override mode 'x' (pct | abs | sigma)" |
400 | No champion yet | "This goal has no live forecast model yet — run discovery first, then come back to simulate" |
400 | Overriding a derived feature | "'ad_spend_lag3' is a feature derived from 'ad_spend'. Override 'ad_spend' instead — …" |
402 | Not enough credits | error.code insufficient_credits, error.details.credits_needed 5 |
404 | Goal doesn't exist | "Goal not found" |
Only base signals flagged simulatable in
drivers-now can be
overridden — derived features are recomputed automatically.
Parse natural language
Turn a plain-English what-if into a structured spec. Parsing returns the spec for you to review or edit — it does not run the scenario:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/scenarios/parse" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "text": "What if ad spend rises 10% and we get a supply disruption?" }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_scenarios_parse(GOAL_ID, json={
"text": "What if ad spend rises 10% and we get a supply disruption?",
})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdScenariosParse(GOAL_ID, {
json: {
text: "What if ad spend rises 10% and we get a supply disruption?",
},
});
{
"data": {
"spec": {
"name": "Ad spend +10% with supply disruption",
"overrides": [{ "key": "ad_spend", "mode": "pct", "amount": 10.0, "shape": "step" }],
"events": [{ "stream": "supply_disruptions", "magnitude": 1.0, "direction": 1 }]
},
"explanation": "Mapped 'ad spend' to the ad_spend driver and the disruption to your supply_disruptions stream.",
"unmapped": [],
"warnings": []
}
}| Field | Meaning |
|---|---|
text required | The plain-English what-if, max 1000 characters. |
Anything that couldn't be mapped to a known driver lands in unmapped.
Over-long descriptions return 400
("Description is too long (max 1000 chars)"); an empty body returns
"Describe a what-if to parse". Charged per parse; refunded if parsing
fails.
Explain a scenario
A short written report on why the simulation moved, with the validated edges and measured event curves behind each claim. The numbers are computed deterministically from stored evidence; the report only phrases them:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/scenarios/$SCENARIO_ID/explain" \
-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_scenarios_by_scenario_id_explain(GOAL_ID, SCENARIO_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdScenariosByScenarioIdExplain(GOAL_ID, SCENARIO_ID);
{
"data": {
"scenario_id": "d2f7…",
"report": {
"summary": "The +10% ad spend move lifts the 14-day forecast by ~1.9%, arriving on the driver's 3-step lag.",
"provenance": [{ "edge_id": "7d3f…", "claim": "ad_spend → daily_sales, lag 3, lift 0.18" }]
}
}
}The report is cached on the scenario — the first generation is charged,
repeat calls return "cached": true for free. Returns 404
("Scenario not found") if the scenario doesn't belong to this goal.
Sensitivity (tornado) analysis
Probe every simulatable driver with a symmetric ±X% step and rank by end-of-horizon impact — which knobs matter most, in one call:
curl -X POST "$API_BASE/v1/goals/$GOAL_ID/scenarios/sensitivity" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Workspace-Id: $WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{ "magnitude_pct": 10 }'from predictai import PredictAI
client = PredictAI(token="pa_live_…", workspace_id="ws_…")
data = client.goals.post_goals_by_goal_id_scenarios_sensitivity(GOAL_ID, json={"magnitude_pct": 10})
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.postGoalsByGoalIdScenariosSensitivity(GOAL_ID, { json: { magnitude_pct: 10 } });
{
"data": {
"sensitivity_id": "a90c…",
"magnitude_pct": 10.0,
"baseline_end": 14210.55,
"results": [
{ "key": "ad_spend", "lifecycle": "validated", "lift": 0.18, "span_bins": 14, "up_delta": 271.4, "down_delta": -244.9, "unit": "$" },
{ "key": "shipping_rates", "lifecycle": "weakening", "lift": 0.09, "span_bins": 14, "up_delta": -118.2, "down_delta": 105.7, "unit": "$" }
],
"warnings": [],
"caveats": ["model response under intervention — …"],
"snapshot": { "champion_pipeline_id": "9f2c41d8-…", "horizon_bins": 14, "…": "…" },
"credits_charged": 12,
"created_at": "2026-07-15T13:02:00+00:00"
}
}| Field | Meaning |
|---|---|
keys optional | Restrict the probe to specific drivers. Default: the goal's evidence-backed drivers, strongest first. |
magnitude_pct optional | The symmetric step size. Defaults to 10, clamped to 0.5–50. |
It's the priciest scenario variant — up to two champion inferences per driver — and the result is persisted, so re-viewing is free:
curl "$API_BASE/v1/goals/$GOAL_ID/scenarios/sensitivity" \
-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_scenarios_sensitivity(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsByGoalIdScenariosSensitivity(GOAL_ID);
{ "data": { "sensitivity": { "sensitivity_id": "a90c…", "results": ["…"] } } }sensitivity is null if none has run yet.
List & delete
curl "$API_BASE/v1/goals/$GOAL_ID/scenarios" \
-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_scenarios(GOAL_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.getGoalsByGoalIdScenarios(GOAL_ID);
{ "data": { "scenarios": [{ "scenario_id": "d2f7…", "name": "Ad spend +10%", "…": "…" }] } }Saved scenarios are full documents (newest first) — rerunnable and comparable. Delete one by its ID (note the goal-less path):
curl -X DELETE "$API_BASE/v1/goals/scenarios/$SCENARIO_ID" \
-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.delete_goals_scenarios_by_scenario_id(SCENARIO_ID)
import { PredictAI } from "@predictai/sdk";
const client = new PredictAI({ token: "pa_live_…", workspaceId: "ws_…" });
const data = await client.goals.deleteGoalsScenariosByScenarioId(SCENARIO_ID);
{ "data": { "deleted": true } }Returns 404 ("Scenario not found") for unknown IDs.

