Optimizations
Endpoints for creating, managing, and serving optimizations.
All endpoints are under /api/v2/optimizations. Unless noted otherwise, requests require an Authorization: Bearer <token> header.
List optimizations
GET /api/v2/optimizationsAuth: Required
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 10 | Maximum number of results (1--100) |
order_by | string | updated_at | Sort field: updated_at or created_at |
include_archived | boolean | false | Include archived optimizations |
Response:
{
"optimizations": [
{
"id": "699ede80-c94b-4f98-9c79-dd3e07748703",
"name": "Landing Page Hero",
"status": "live",
"model_status": "trained",
"model_id": "4754cae0-9a5d-4691-91af-6d15ccc1f82e",
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-03-28T14:22:00Z",
"model_source": "store",
"summary": {
"reference": "baseline",
"variants_count": 6,
"exposures": 1250
}
}
],
"meta": {
"request_id": "req_abc123",
"organization_id": "org_xyz"
}
}The summary object is null for optimizations that have not been trained yet. reference is what lifts are measured against: baseline, or equal_split (an even split of traffic) for an optimization without a baseline variant.
model_source says where the optimization's model figures (lift, confidence, probability of best, importance) come from:
store: the posterior. No optimization response carries them; query them there. Every new optimization isstore.bandit: optimizations created before the posterior store keep their figures where they always were, on the optimization's results endpoints and in itssummary(estimated_improvement_percent,estimated_improvement_confidence, …), so their numbers don't change mid-run. Their design-factor importance is a share of the reward's variance rather than a lift.
Get optimization
GET /api/v2/optimizations/:idAuth: Required
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id | string (UUID) | Optimization ID |
Response: Returns the full optimization object including training_data_config, linked metrics, current status, and model_source (see List optimizations).
Create optimization
POST /api/v2/optimizationsAuth: Required
Request body:
{
"name": "Landing Page Hero",
"model_type": "cmab",
"training_data_config": {
"reward": {
"name": "signup",
"type": "bool"
},
"design_factors": [
{
"name": "headline",
"dtype": "str",
"levels": ["Ship faster", "Save time", "Build better"]
},
{
"name": "cta_text",
"dtype": "str",
"levels": ["Start free", "Try it now"]
}
],
"context_factors": [
{
"name": "device",
"dtype": "str",
"levels": ["mobile", "desktop"]
}
],
"attribution": {
"type": "last_touch",
"conversion_window_hours": 24
}
},
"reward_metric_id": "metric-uuid",
"holdout_percentage": 10
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable name |
description | string | No | Optional description (max 1000 characters) |
model_type | string | No | mab or cmab (default: cmab) |
training_data_config.reward | object | No | Reward definition with name and type (bool or numeric). Defaults to {"name": "reward", "type": "bool"} |
training_data_config.design_factors | array | Yes | Variables you control. Each has name, dtype, and levels |
training_data_config.context_factors | array | No | Contextual variables for personalization. Same shape as design factors |
training_data_config.attribution | object | No | Attribution model. type is last_touch, conversion_window_hours sets the lookback (default: 24) |
reward_metric_id | string (UUID) | Yes | Link a reward metric at creation time |
assignment_source_id | string (UUID) | No | Link an assignment source. If omitted, Levered auto-creates one pointing to an exposures table in your warehouse |
holdout_percentage | integer | No | Percentage of traffic to hold out for control (0--50, default: 10) |
control_type | string | No | What users outside the optimization see: variant (default; one of the variants is the baseline and the holdout is served it), external (the holdout keeps a version that is none of the variants; needs holdout_percentage above 0; holdout users are left out of training) or none (every variant is new; holdout_percentage must be 0, no guardrails). See Holdout |
baseline_variant | object | No | With control_type variant: the baseline as a map of factor name to level value, one entry for every design factor. Default: every factor's first level |
guardrails | array | No | Guardrail definitions to attach at creation time |
Response:
{
"id": "699ede80-c94b-4f98-9c79-dd3e07748703",
"model_id": "4754cae0-9a5d-4691-91af-6d15ccc1f82e",
"meta": {
"request_id": "req_abc123",
"organization_id": "org_xyz"
}
}Update optimization
PATCH /api/v2/optimizations/:idAuth: Required
Request body:
{
"name": "Updated Name",
"status": "completed"
}Allowed status transitions:
| From | To |
|---|---|
draft | live |
live | paused, shipped, completed, archived |
paused | live, shipped, completed, archived |
shipped | live, completed |
completed | archived |
control_type and baseline_variant can also be changed with PATCH, while the optimization is a draft. Send "baseline_variant": null to go back to every factor's first level. The holdout percentage of an external optimization can only be changed while it's a draft.
Setting status to shipped requires a trained model. It freezes the leading variant for each context into a policy, which GET /optimizations/:id returns as shipped_policy. holdout_percentage cannot be changed while an optimization is shipped.
Delete (archive) optimization
DELETE /api/v2/optimizations/:idAuth: Required
Archives the optimization. Data is retained but the optimization is hidden from default list views. Equivalent to setting status to archived.
Response:
{
"id": "699ede80-c94b-4f98-9c79-dd3e07748703",
"status": "archived",
"meta": {
"request_id": "req_abc123",
"organization_id": "org_xyz"
}
}Upload variant screenshot
POST /api/v2/optimizations/:id/variants/by-number/:n/screenshotAuth: Required
Multipart upload of a single PNG showing variant :n rendered in your app. Validated by magic bytes (PNG only), 10 MB limit. Re-uploading overwrites the previous screenshot. Variant numbers are assigned when results are first fetched. If the variant number is unknown, fetch GET /api/v2/optimizations/:id/results/state once and retry.
This is normally called by levered optimizations screenshot, which captures and uploads all variants in one step.
Request: multipart/form-data with a single file field.
Response:
{
"ok": true,
"variant_number": 3,
"meta": {
"request_id": "req_abc123",
"organization_id": "org_xyz"
}
}Get variant screenshot
GET /api/v2/optimizations/:id/variants/by-number/:n/screenshotAuth: Required
Returns the stored PNG (Content-Type: image/png), or 404 if no screenshot has been uploaded for that variant. Screenshots are private to the owning organization and are only served through this endpoint. The variants in GET /api/v2/optimizations/:id/results/state carry a screenshot_uploaded_at timestamp (null when absent) so clients can tell which variants have a preview.
Serve variant
POST /api/v2/optimizations/:id/serveAuth: Not required (public endpoint). Rate limited to 300 requests/min per IP.
Returns one variant selected for the user by the optimization policy. This is the primary endpoint for real-time variant delivery.
Request body:
{
"anonymous_id": "user-abc",
"context": {
"device": "mobile"
}
}| Field | Type | Required | Description |
|---|---|---|---|
anonymous_id | string | Yes | Stable user identifier, required so the exposure can be joined with rewards in your warehouse |
context | object | No | Context factor values for personalization |
Each successful request returns one selection in the variants array. Omit the legacy n_variants field; an explicit 1 is also accepted. Counts greater than 1 are rejected with HTTP 400.
Response:
{
"variants": [
{
"props": { "headline": "Ship faster", "cta_text": "Start free" },
"expected_reward": 0.31
}
],
"optimization_id": "699ede80-c94b-4f98-9c79-dd3e07748703",
"is_holdout": false,
"trained": true,
"meta": {
"request_id": "req_abc123",
"organization_id": "org_xyz"
}
}| Field | Type | Description |
|---|---|---|
variants | array | One selected variant with its design factor values and expected reward |
optimization_id | string | The optimization that served this variant |
is_holdout | boolean | Whether the user was assigned the baseline through holdout routing |
trained | boolean | true if the model has been trained, false for an untrained policy |
Best variant by design factors
POST /api/v2/optimizations/best-variantAuth: Not required (public endpoint). Rate limited to 300 requests/min per IP.
Matches design factor names to a live optimization and returns one variant selected for the user by its policy. The JavaScript SDK uses /optimizations/:id/serve with an explicit optimization ID.
Request body:
{
"anonymous_id": "user-abc",
"props": ["headline", "cta_text"],
"context": {
"device": "mobile"
}
}| Field | Type | Required | Description |
|---|---|---|---|
anonymous_id | string | Yes | Stable user identifier |
props | string[] | Yes | Array of design factor names. Levered finds the live optimization whose design factors match this set. |
context | object | No | Context factor values for personalization |
Each successful request returns one selection in the variants array. Omit the legacy control.n_variants field; an explicit 1 is also accepted. Counts greater than 1 are rejected with HTTP 400.
Response:
Same shape as the /serve endpoint:
{
"variants": [
{
"props": { "headline": "Build better", "cta_text": "Try it now" },
"expected_reward": 0.29
}
],
"optimization_id": "699ede80-c94b-4f98-9c79-dd3e07748703",
"is_holdout": false,
"trained": true,
"meta": {
"request_id": "req_abc123",
"organization_id": "org_xyz"
}
}