- Sells as: the
experimentation product (a product-scoped API key). This capability has no separate product-face contract — its public product route reuses the capability contract below; see the Edge route table's Product face row.
Experimentation capability — a durable, org-scoped experiment registry with a DETERMINISTIC variant assignment engine (stable SHA-256 hash of (salt, key, user_id) → a fixed bucket space → sliced by cumulative normalized weight; sticky by construction, no per-user assignment row). Products stop re-rolling an A/B engine + an exposure pipeline; they define experiments, assign users, and record exposures over this one capability. I4: own Postgres store (fleet_experiments); the results SoT is the local exposures table, analytics is a downstream mirror.
- Group: Governance & platform
- Contract:
contracts/experimentation/v1/openapi.yaml
- Console: Manage the
experimentation product in the console →
- Runbook: operational checklist for
experimentation — internal (Vagary Labs ops; not part of this public site): docs/runbooks/capability-operations.md#experimentation
- Public base:
https://api.vagarylabs.com (the consolidated API gateway — one host, per-brand sibling api.<zone>)
- Auth: a product API key (
vgk_…) issued from the console — Authorization: Bearer vgk_…
- Product face (customer-keyed):
https://api.vagarylabs.com/product/v1/experiments
Edge route table
The routes this capability actually serves on the public edge — live-synced from GET https://api.vagarylabs.com/product/v1/_meta/catalog (snapshot v1). The metric is the request-granularity billing counter; the scope is the key permission required.
Product face (customer-keyed)
| Method | Path | Metric | Scope |
|---|
POST | /product/v1/experiments | experiment_operations | write |
Endpoints
| Method | Path | Summary |
|---|
POST | /v1/experiments | Define (idempotent upsert) an experiment — key, weighted variants, status, metric |
GET | /v1/experiments | List the org's experiments (newest first) |
GET | /v1/experiments/{experiment_id} | One experiment definition or 404 |
PATCH | /v1/experiments/{experiment_id} | Partially update an experiment (name / variants / status / metric) |
GET | /v1/assign | Deterministically assign a user to a variant (sticky; control on not-running / kill-switch) |
POST | /v1/exposures | Record a user's exposure (persist to results SoT + fan out to analytics, fail-open) |
GET | /v1/experiments/{experiment_id}/results | Per-variant exposure / unique-user readout for the experiment |
GET | /health | liveness |
GET | /metrics | Prometheus |
Schemas
Variant
| Field | Type | Description |
|---|
name | string | variant name (unique within the experiment; the first is control) |
weight | number | relative weight; normalized across variants (equal split when all omitted) |
ExperimentRequest
| Field | Type | Description |
|---|
organization_id | string | product-tier end-customer tenant (I6) |
experiment_id | string | server-generated (exp_...) if omitted; supply to upsert an existing one |
key | string | stable human key, unique per org — the /v1/assign lookup handle |
name | string | |
variants | array | |
status | string | |
metric | string | the primary success metric name (recorded on emitted exposure events) |
salt | string | per-experiment bucketing salt; server default when omitted. Rotating it re-randomizes assignment — never on a live experiment |
ExperimentPatch
| Field | Type | Description |
|---|
organization_id | string | |
name | string | |
variants | array | |
status | string | |
metric | string | |
Experiment
| Field | Type | Description |
|---|
experiment_id | string | |
key | string | |
name | string | |
variants | array | |
status | string | |
metric | string | |
salt | string | |
created_at | number | epoch seconds |
updated_at | number | |
ExperimentList
| Field | Type | Description |
|---|
experiments | array | |
count | integer | |
Assignment
| Field | Type | Description |
|---|
experiment | string | the experiment key |
experiment_id | string | |
variant | string | assigned variant name (control when not-running/killed) |
sticky | boolean | always true — a pure deterministic function of (key, user_id, salt) |
reason | string | assigned |
ExposureRequest
| Field | Type | Description |
|---|
organization_id | string | |
experiment | string | experiment_id or key |
user_id | string | |
variant | string | omit → recomputed deterministically server-side |
ExposureResult
| Field | Type | Description |
|---|
experiment | string | |
experiment_id | string | |
user_id | string | |
variant | string | |
recorded | boolean | persisted to the local exposures SoT |
emitted_to_analytics | boolean | fanned out to the analytics capability (fail-open — false on a downstream outage) |
Generated by scripts/gen-capability-docs.py from contracts/experimentation/v1/openapi.yaml — the contract IS the source of truth; edit the contract, not this page. The Edge route table section is synced from the live edge catalog snapshot (v1) — re-sync with python3 scripts/sync-edge-catalog.py.