Posterior
Query an optimization's posterior: the source of every model figure.
Every model figure Levered shows (traffic share and probability of best, expected rewards, lifts, progress, importance, per-segment figures) is computed from the optimization's posterior when it is read. The other endpoints return counts and configuration only. To read a model figure, query the posterior with SQL.
This applies to optimizations whose model_source is store, which is every new one. Optimizations created before the posterior store have model_source: "bandit" and keep their figures on the results endpoints, as before; see Optimizations.
Query the posterior
POST /api/v2/posterior/optimizations/:optimizationId/queryAuth: Required. A Clerk bearer token or an API key (Authorization: Bearer <key> or X-API-Key: <key>).
Runs one read-only SQL query (DuckDB dialect) over the optimization's posterior draws and the views described below. Each published posterior is a version, named after the time its fit finished; every fit publishes a new one.
Request body:
{
"sql": "SELECT variant_key, share FROM thompson_traffic_share ORDER BY share DESC",
"params": {},
"model_version": "latest"
}| Field | Type | Required | Description |
|---|---|---|---|
sql | string | Yes | One read-only query, up to 20,000 characters. |
params | object | No | Values bound to $name placeholders: string, number, boolean or null. Pass a list as a JSON string and cast it in SQL, e.g. $q::DOUBLE[] with "q": "[0.025,0.5,0.975]". |
model_version | string | No | latest (default) or a version from available_versions. |
Response:
{
"columns": ["variant_key", "share"],
"rows": [
["{\"cta_text\":\"lv_cta_text_1\",\"headline\":\"lv_headline_0\"}", 0.62],
["{\"cta_text\":\"lv_cta_text_0\",\"headline\":\"lv_headline_0\"}", 0.21]
],
"model_version": "2026-10-26T10-15-00Z",
"available_versions": ["2026-10-26T10-15-00Z", "2026-10-25T10-12-00Z"],
"n_samples": 2000,
"elapsed_ms": 140,
"warnings": []
}| Field | Description |
|---|---|
columns, rows | The result set. Lists and structs come back as JSON arrays and objects. |
model_version | The version the query read. |
available_versions | Every published version, newest first. Run the same query over several of them for a trend: nothing else stores model history. |
n_samples | Number of posterior draws. |
Errors carry { "error": "<message>" }:
| Status | Meaning |
|---|---|
400 | Invalid SQL or parameters. |
404 | Optimization not found, or no posterior has been published for it yet. |
422 | The result exceeds the row limit. Aggregate in SQL. |
503 | The posterior store is not available. |
504 | The query timed out. |
Until the first posterior is published, the policy is an even split over the servable variants, and every other model figure has no estimate.
Tables and views
| Name | What |
|---|---|
posterior_reward(variant_id, context_id, sample_id, reward) | Expected reward of every variant in every context, per posterior draw. |
variants(variant_id, variant_key, permitted, …) | The design grid, one column per design factor. permitted is false for disabled and excluded variants. |
contexts(context_id, context_key, weight, …) | The contexts (segments), one column per context factor. weight is the context's share of traffic. A model without context factors has one context, {}. |
thompson_policy, thompson_policy_marginal, baseline_policy | Policies (context_id, context_key, variant_id, variant_key, pi): the live Thompson policy, the same policy blind to context, and the reference (the baseline variant, or an even split over permitted variants when there is none). |
thompson_traffic_share(variant_id, variant_key, share, share_mc_se) | Each variant's share of all optimized traffic, which is also its probability of being best. |
policy_draws(pol, alt), policy_draws_by_context(pol, alt) | Per draw (and context): reward, reward_alt, reward_oracle, lift, headroom_lift, headroom_captured of policy pol against alt. |
variant_draws(alt), variant_draws_by_context(alt) | The same for serving each variant everywhere. |
oracle_draws(context_factors, design_factors), oracle_draws_by_context(…) | reward of the best design restricted to those factors, and reward_oracle of the unrestricted best. |
interaction_draws, interaction_draws_by_context | reward of each design factor's individually best level combined, and reward_oracle. |
design_factors(factor), context_factors(factor) | The factor names. |
dist(x, q), p_gt(x, t) | Reducers over draws: {n, mean, mc_se, quantiles, quantiles_mc_se} at quantiles q, and {p, p_mc_se} for P(x > t). |
Pass a policy to a draw macro by name: one of the views above, or a CTE in your query. lift = reward / reward_alt − 1, headroom_lift = reward_oracle / reward_alt − 1, and headroom_captured is how much of the headroom the policy has captured, from 0 to 1. The averaged (non-_by_context) forms average rewards over contexts by weight first, then take the ratios.
Standard figures
The dashboard, CLI and agent skills derive their figures the same way. Intervals are central credible intervals, and the median is the point estimate.
| Figure | Query |
|---|---|
| Policy lift ("Lift (now)") | lift of policy_draws('thompson_policy', 'baseline_policy') |
| Ceiling ("Lift (best variant)") | (lift + headroom_lift) / 2 per draw, from the same macro. The best-variant figure is shrunk halfway toward the policy lift. |
| P(beats reference) | avg((lift > 0)::DOUBLE), or p_gt(lift, 0) |
| Progress to best | median of headroom_captured |
| Traffic share, probability of best | share of thompson_traffic_share; per segment, pi of thompson_policy |
| Variant expected reward | reward of variant_draws('baseline_policy') |
| Segment policy and ceiling reward | reward and reward_oracle of policy_draws_by_context('thompson_policy', 'baseline_policy') |
| Importance | reward_oracle / reward − 1 of oracle_draws with one factor withheld, or of interaction_draws |
Importance is a lift for design factors, context factors and the interaction alike: what optimizing that factor (or conditioning on that context factor, or choosing the design jointly) is worth. All three are on one scale and compare directly. They are not shares and do not add up to anything. Withhold one factor per query; two restrictions in one query can exceed memory on large models.
Example: the headline figures.
SELECT quantile_cont(lift, [0.025, 0.5, 0.975]) AS policy_lift,
quantile_cont((lift + headroom_lift) / 2, [0.025, 0.5, 0.975]) AS ceiling_lift,
avg((lift > 0)::DOUBLE) AS p_beats_reference,
median(headroom_captured) AS progress
FROM policy_draws('thompson_policy', 'baseline_policy')Example: what optimizing design factor $factor is worth.
SELECT quantile_cont(reward_oracle / reward - 1, [0.025, 0.5, 0.975]) AS lift
FROM oracle_draws(
(SELECT list(factor) FROM context_factors),
(SELECT list(factor) FROM design_factors WHERE factor <> $factor))For a context factor, withhold it from the first list and pass every design factor in the second.
From the CLI, levered posterior query <optimization-id> --sql '<sql>' [--param name=value] [--version <v>] [--json] runs the same route.