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.
at_offset_bins optionalWhen in the horizon the event lands, as a step offset (0 = now, the default). The response curve is applied from that step.
at optionalSame thing as a timestamp (epoch or ISO 8601), converted to the nearest step. at_offset_bins wins when both are sent.

Timing an injection answers "what if the promo runs mid-horizon instead of today?" For the full "which timing is best" question, use the timing sweep — one call instead of one scenario per candidate.

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.

Event timing sweep

If I can pick when an event happens, which timing moves the goal most? The sweep slides one injected event across every step of the horizon and reports the impact per candidate timing. The champion runs once (the baseline); each offset is then applied analytically from the stream's measured response curve — so the whole sweep is charged like a single scenario:

curl -X POST "$API_BASE/v1/goals/$GOAL_ID/scenarios/timing" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-Id: $WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{ "stream": "promotions", "magnitude": 1, "direction": 1 }'
{
  "data": {
    "goal_id": "b7e9c2d4-…",
    "stream": "promotions",
    "magnitude": 1.0,
    "direction": 1,
    "horizon_bins": 14,
    "interval_seconds": 86400,
    "baseline_end": 14210.55,
    "offsets": [
      { "offset_bins": 0, "at_ms": 1789459200000, "end_delta": 12.4, "peak_delta": 261.0, "total_delta": 903.1 },
      { "offset_bins": 1, "at_ms": 1789545600000, "end_delta": 19.8, "peak_delta": 261.0, "total_delta": 890.7 },
      { "…": "…" }
    ],
    "best_end_offset": 11,
    "best_peak_offset": 0,
    "best_total_offset": 0,
    "curve": { "edge_id": "7d3f…", "half_life_s": 172800, "curve_bins": 6, "scale": 261.0 },
    "caveats": ["…", "Timing sweep applies the measured response curve analytically at each candidate offset — the champion ran once, on the baseline."],
    "credits_charged": 5
  }
}
FieldMeaning
stream requiredThe event stream to sweep. Must hold a validated edge to this goal — the response curve is the measured edge.
magnitude optionalSize versus a typical event, 0.1–5. Defaults to 1.
direction optional1 or -1.

Per offset: end_delta (impact on the final step), peak_delta (largest single-step move), total_delta (area under the response). best_end_offset / best_peak_offset / best_total_offset index the offsets that maximize each — pick the one matching what you're optimizing. A stream with no validated response curve returns 400 ("Event response for '<stream>' could not be applied: …") and the charge is refunded.

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