Reference
Experiments
Run two versions of a policy against each other on real signups, and score them on what those signups went on to do — not on how many Sortance let in.
Endpoints
| Method | Path | Does |
|---|---|---|
GET | /v1/experiments | Every experiment. |
POST | /v1/experiments | Create one as a draft. Nothing changes until you start it. |
POST | /v1/experiments/compile | { text } — English in, a draft configuration out. Saves nothing. |
GET | /v1/experiments/:slug | One experiment, with status, started_at and stopped_at. |
POST | /v1/experiments/:slug/start | Draft to running. Within about a minute, /v1/decide starts assigning requests to variants. |
POST | /v1/experiments/:slug/stop | Running to stopped. Within about a minute, /v1/decide goes back to the active version alone. |
GET | /v1/experiments/:slug/report | The report. 404 while the experiment is still a draft. |
The configuration
POST /v1/experiments
{
"slug": "block-freemail-q4",
"hypothesis": "Reviewing consumer mailboxes cuts support load without cutting paid conversion.",
"policy": "default",
"variants": [
{ "name": "control", "version": 14, "weight": 80 },
{ "name": "treatment", "version": 15, "weight": 20 }
],
"mode": "enforce",
"metric": { "event": "subscribed", "window_days": 14 },
"guardrails": [ { "event": "support_ticket", "max_rate_delta": 0.02 } ],
"min_sample": 400
}| Field | Meaning |
|---|---|
slug | Lowercase letters, digits and -, up to 63 characters. Unique per account. |
hypothesis | Optional, up to 500 characters. What you expect, in a sentence. |
policy | The policy slug whose versions are being compared. It must exist. |
variants | One or two of { name, version, weight }. The first is the control. Weights are integers that sum to 100; every version must exist. |
mode | enforce or shadow. |
metric | { event, window_days } — the funnel event that counts as success, and how long after the decision it may arrive (1–365 days). |
guardrails | Optional, up to 5 of { event, max_rate_delta } — an event that must not rise by more than this between variants. |
min_sample | Integer, at least 30. No rate and no probability is shown until every variant has this many exposures. |
Enforce and shadow
- Assignment is per person, not per request. A request’s bucket is a hash of the experiment’s own salt and the hashed address, mod 100, cut by the weights. Someone who retries your signup form lands in the same variant every time.
- enforce returns the assigned variant’s decision. The
experimentblock on the decide response names the slug, the variant and the mode. - shadow evaluates every variant and records each decision, but the active version answers. The response’s
experiment.shadow_decisionis what the assigned variant would have done. A one-variant shadow experiment is how you try a new version with zero risk to signups before enforcing it. - One running experiment per policy at a time. A shadow variant that reads enrichment buys it, so starting one can change cost even though it cannot change a decision.
GET /v1/experiments/:slug/report
| Field | Meaning |
|---|---|
status | draft, running, stopped or concluded. |
started_at | ISO 8601, or null. |
exposures | Decisions per variant. |
decisions | The decision mix per variant. |
metric.control, metric.treatment | { events, rate, ci } — rate is the posterior mean and ci the 90% credible interval. Both null below min_sample. |
metric.p_treatment_better | The probability the treatment's rate is higher. Null below min_sample, with one variant, or until the nightly run has computed it. |
metric.expected_loss | { choose_control, choose_treatment } — the expected cost of being wrong either way, in rate units. Null under the same conditions. |
metric.verdict | insufficient_data, inconclusive, treatment_better or control_better. |
metric.says | One plain sentence, e.g. "Not enough data yet — 212 of 400 minimum exposures in treatment." |
guardrails | { event, delta, ok } per guardrail. Null values while there is not enough data. |
shadow | In shadow mode, each variant's decision mix side by side. Otherwise null. |
How it is analysed
- Each variant’s metric rate is a Beta posterior over its events and exposures, starting from a uniform Beta(1, 1) prior.
- The probability that treatment beats control comes from 10,000 Monte Carlo draws from the two posteriors. A verdict is called at 90%:
treatment_betterat 0.9 or above,control_betterat 0.1 or below, otherwiseinconclusive. - The draws run in the nightly job, never on a request. Before the first nightly run, the report shows exposures, decision mix and per-variant rates with the probability left null and a sentence saying it is computed nightly.
- Below
min_samplein any variant the verdict isinsufficient_dataand no rate or probability is shown. A three-exposure experiment at 100% is the most common way a dashboard lies. - A shadow experiment’s verdict is always
inconclusive: every request received the same decision, so the metric cannot separate the variants. Compare the decision mix, then run it in enforce mode to measure the outcome. - Metrics come only from events — ones you send to /v1/events or ones Stripe sends. Sortance’s own decision counts are never the metric.
Errors
| Status | Code | When |
|---|---|---|
400 | invalid_request | The configuration fails validation (the first five problems are in the message), or starting or stopping from the wrong status. |
404 | not_found | No such experiment, or a report requested for a draft. |
409 | policy_conflict | An experiment with that slug exists, or another experiment is already running on the same policy. |
422 | compile_failed | The compiler could not produce a valid configuration, or is not configured on this deployment. |