Predict.aiDocs
Goals

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": []
  }'
{
  "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

FieldMeaning
overrides requiredDriver moves — at least one override or one event. Up to a handful per scenario.
events optionalInjected occurrences on event streams.
name optionalDisplay name for the saved scenario.

Each overrides[] entry:

FieldMeaning
key requiredThe driver to move — must be flagged simulatable in drivers-now.
mode requiredpct (percent), abs (absolute), or sigma (standard deviations).
amount requiredHow much, in the chosen mode's units.
shape optionalstep (immediate, the default) or ramp (gradual).

Each events[] entry:

FieldMeaning
stream requiredThe event stream to inject on.
magnitude optionalSize versus a typical event, 0.1–5. Defaults to 1.
direction optional1 or -1. The response replays the stream's measured response curve.

Errors

StatusWhyExample message
400Empty or invalid spec (validated before charging)"Scenario needs at least one driver override or one injected event" · "Unknown override mode 'x' (pct | abs | sigma)"
400No champion yet"This goal has no live forecast model yet — run discovery first, then come back to simulate"
400Overriding a derived feature"'ad_spend_lag3' is a feature derived from 'ad_spend'. Override 'ad_spend' instead — …"
402Not enough creditserror.code insufficient_credits, error.details.credits_needed 5
404Goal 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?" }'
{
  "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": []
  }
}
FieldMeaning
text requiredThe 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"
{
  "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 }'
{
  "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"
  }
}
FieldMeaning
keys optionalRestrict the probe to specific drivers. Default: the goal's evidence-backed drivers, strongest first.
magnitude_pct optionalThe 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"
{ "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"
{ "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"
{ "data": { "deleted": true } }

Returns 404 ("Scenario not found") for unknown IDs.

On this page