Skip to main content
beta This runbook walks through the full lifecycle of a Contextual Bandit driven entirely from the REST API. Every step assumes you have a valid Personal Access Token or Secret Key with the relevant Contextual Bandit permissions on the target project, and that the general prerequisites — a 1.7+ SDK, an updated tracking callback, and a connected data source — are already in place. See Setting up a Contextual Bandit for that checklist. Contextual Bandits use two REST resources:
  • /v1/contextual-bandit-queries — the assignment SQL plus the targeting (context) columns the bandit splits on. This is the bandit-specific replacement for borrowing an Experiment Assignment Query off the datasource.
  • /v1/contextual-bandits — the bandit itself, which references a query by id.
See the REST API reference for the full schema.
Contextual Bandits are an Enterprise-only feature. Every endpoint below returns 402 Plan Does Not Allow on Pro / Free plans.

1. Create a Contextual Bandit Query

A Contextual Bandit needs a query that defines the assignment SQL and the targeting-attribute columns it splits on. targetingAttributeColumns is required and must be non-empty — a bandit with no context to split on is not a contextual bandit. Each named column must also appear in the query’s SELECT. The query should also select the leaf_id, bandit_version, and variation_weights columns logged by your tracking callback — without them the SRM health check is skipped.
The response includes the new query id (cbq_…). You can manage queries with the standard CRUD endpoints (GET /v1/contextual-bandit-queries, GET/PUT/DELETE /v1/contextual-bandit-queries/:id); pass ?datasourceId=ds_abc on the list endpoint to scope by datasource.

2. Create the Contextual Bandit

Reference the query from the previous step via contextualBanditQueryId.
Required fields are name, trackingKey, datasource, contextualBanditQueryId, variations, decisionMetric, and contextualAttributes. Other fields (hashAttribute, queryFilter, activationMetric, minUsersPerLeaf, maxLeaves, etc.) are optional and fall back to defaults. The response includes the new Contextual Bandit id (cb_…). The CB is provisioned in status: "draft" with an empty currentLeafWeights array at the document root; it stays empty until the first successful refresh. A Contextual Bandit serves its variations through a contextual-bandit-ref rule on a Feature Flag, and it needs at least one linked Feature Flag before it can start. Create the flag first with POST /v2/features, then link it:
variations must cover every Contextual Bandit variation exactly once — variationId is the id of each entry in the CB’s variations array, not its key. Targeting (condition, saved groups, prerequisites, coverage) lives on the Contextual Bandit and is inherited by the rule, so those fields are not accepted here. The rule is appended to the bottom of the flag’s rule list in a new draft revision, which auto-publishes when you start the Contextual Bandit. Pass "autoPublish": true to publish it right away, or "draftVersion": 7 to add the rule to an existing draft instead of starting a new one. By default the rule applies to every environment; pass "allEnvironments": false with "environments": ["production"] to narrow it. Response:
Two companion endpoints round this out:
  • GET /v1/contextual-bandits/:id/linked-features — the linked flags enriched with live/draft state, per-environment rule state, and variation values (the same payload the CB detail page renders).
  • DELETE /v1/contextual-bandits/:id/linked-feature/:featureId — removes every contextual-bandit-ref rule pointing at this bandit from the flag and drops the linkage. Like the POST, the removal lands in a draft unless you pass ?autoPublish=true. When the flag has no such rule left, only the linkage is cleared.

4. Start the Contextual Bandit

Move the CB out of draft (to status: "running") so it is eligible for refresh:
The response returns the updated CB: { "contextualBandit": { … } }.

5. Refresh — run a snapshot

Triggers the contextual-bandit snapshot pipeline. The orchestrator builds frozen settings, opens a ContextualBanditSnapshot (CBS) doc, runs the warehouse query and the stats engine, and on success persists a ContextualBanditEvent (CBE) with the new per-leaf weights. Each successful snapshot increments the CB’s banditVersion.
Response shape:
cbeId is omitted when the run failed before producing an event (the CBS doc will be in status: "error").

6. Read the latest results

Returns the same payload the GrowthBook UI uses to render the CB results table — the latest stats engine output plus a snapshot-status summary.
Response (abbreviated; contextualBanditSnapshot and latest are each null until a run exists):
latest.status is one of running, success, or error, and latest.runStarted may be null if the run hasn’t started. Use the companion endpoints for finer-grained inspection:
  • GET /v1/contextual-bandits/:id/snapshots — list recent snapshot runs (optional ?limit=, max 100).
  • GET /v1/contextual-bandits/:id/snapshots/:snapshotId — single snapshot status.
  • GET /v1/contextual-bandits/:id/events — list CBE outputs, one per successful snapshot (optional ?limit=, max 100).
  • GET /v1/contextual-bandits/:id/events/:eventId — single CBE.
  • GET /v1/contextual-bandits/:id/current — current root-level currentLeafWeights plus the latest event object (latestEvent, or null).
The /current response looks like:

7. Stop the Contextual Bandit

When you’re done, stop the CB:
Like start, this returns the updated CB under contextualBandit.

Standard CRUD

Both resources also expose the default CRUD endpoints:
  • GET /v1/contextual-bandits (filter with ?projectId=, ?datasourceId=, or ?trackingKey=), GET /v1/contextual-bandits/:id, POST /v1/contextual-bandits, PUT /v1/contextual-bandits/:id, DELETE /v1/contextual-bandits/:id.
  • GET /v1/contextual-bandit-queries (filter with ?datasourceId=), GET /v1/contextual-bandit-queries/:id, POST /v1/contextual-bandit-queries, PUT /v1/contextual-bandit-queries/:id, DELETE /v1/contextual-bandit-queries/:id.

Permissions cheat sheet

Permissions are checked directly against the Contextual Bandit doc (CBs no longer delegate RBAC to a paired experiment):
  • Read (GET results, GET current, GET snapshot(s), GET event(s), GET /:id, GET /) — canReadSingleProjectResource(cb.project).
  • Create (POST /) — canCreateContextualBandit(cb).
  • Update (PUT /:id) — canUpdateContextualBandit(existing, updated).
  • Run (POST refresh, POST start, POST stop) — canRunContextualBandit(cb, environments).
  • Delete (DELETE /:id) — canDeleteContextualBandit(cb).
  • Link / unlink a Feature Flag (POST / DELETE /:id/linked-feature/:featureId) — canUpdateContextualBandit on the bandit, plus canUpdateFeature, canManageFeatureDrafts, and canPublishFeature (scoped to the rule’s environments) on the Feature Flag.
Contextual Bandit Query create/update/delete are gated by the datasource: you need canUpdateDataSourceSettings on the query’s datasource. Plan gate (hasPremiumFeature("contextual-bandits")) is checked first on every endpoint and short-circuits with 402 before any data access.