Run a multi-armed bandit optimization
How to run a multi-armed bandit optimization in Optimizely Feature Experimentation.
A multi-armed bandit (MAB) optimization is a different type of experiment, compared to an A/B test, because it uses reinforcement learning to allocate traffic to variations that perform well while allocating less traffic to underperforming variations.
ImportantMAB optimizations do not generate statistical significance. Instead, the algorithm pushes traffic to variations with the most conversions; the reason for a variation's performance is unimportant.
See Run a multi-armed bandit optimization in Feature Experimentation in the user documentation for additional information and use cases.
Prerequisites
To configure a MAB, complete the following:
-
Create a flag in your Feature Experimentation project.
-
Note
You should configure a user profile service to ensure consistent user bucketing if you are using a server-side SDK.
-
If you have not done so yet, implement the Optimizely Feature Experimentation SDK's Decide method in your application's codebase through a flag.
Configure an MAB optimization in your Feature Experimentation project
With the Feature Experimentation REST API
Use the Update the Ruleset for a Flag in an Environment endpoint to add a rule with type
set to multi_armed_bandit
.
ImportantFor
PATCH
requests, firstGET
the current ruleset, merge only the fields you need to change, and then send thePATCH
. This helps prevent overwriting existing settings you did not intend to modify.
Endpoint – PATCH https://api.optimizely.com/flags/v1/projects/PROJECT_ID/flags/FLAG_KEY/environments/ENVIRONMENT_KEY/ruleset
Authentication – Include your API key in the Authorization
header as a Bearer token. See Generate tokens and use the REST APIs.
Request example
curl --request PATCH \
--url https://api.optimizely.com/flags/v1/projects/PROJECT_ID/flags/FLAG_ID/environments/ENVIRONMENT_ID/ruleset \
--header 'accept: application/json' \
--header 'authorization: Bearer TOKEN' \
--header 'content-type: application/json-patch+json' \
--data '
[
{
"op": "add",
"value": {
"key": "mab_test",
"name": "Multi-armed Test",
"type": "multi_armed_bandit",
"distribution_mode": "manual",
"percentage_included": 10000,
"metrics": [
{
"aggregator": "unique",
"display_title": "add_to_cart",
"event_id": EVENT_ID,
"event_type": "custom",
"scope": "visitor",
"winning_direction": "increasing"
}
],
"variations": {
"off": {
"key": "off",
"name": "Off",
"percentage_included": 3333
},
"on": {
"key": "on",
"name": "On",
"percentage_included": 3333
},
"on_hide_amounts": {
"key": "on_hide_amounts",
"name": "On Hide Amounts",
"percentage_included": 3334
}
},
"audience_conditions": []
},
"path": "/rules/mab_test"
}
]
'
Response example
{
"url": "/projects/PROJECT_ID/flags/FLAG_ID/environments/ENVIRONMENT_ID/ruleset",
"update_url": "/projects/PROJECT_ID/flags/FLAG_ID/environments/ENVIRONMENT_ID/ruleset",
"enable_url": "/projects/PROJECT_ID/flags/FLAG_ID/environments/ENVIRONMENT_ID/ruleset/enabled",
"rules": {
"already-exisiting-a-b-test": {
"key": "already-exisiting-a-b-test",
"name": "Already-exisiting-a-b-test",
"description": "",
"variations": {
"off": {
"key": "off",
"name": "Off",
"percentage_included": 5000,
"variation_id": VARIATION_ID,
"id": ID
},
"on": {
"key": "on",
"name": "On",
"percentage_included": 5000,
"variation_id": VARIATOIN_ID,
"id": ID
}
},
"baseline_variation_id": VARIATION_ID,
"type": "a/b",
"distribution_mode": "manual",
"id": ID,
"urn": "rules.flags.optimizely.com::ID",
"archived": false,
"enabled": false,
"created_time": "2025-09-12T16:44:11.824135Z",
"updated_time": "2025-09-12T17:03:20.001335Z",
"audience_conditions": [
"or",
{
"audience_id": AUDIENCE_ID
}
],
"audience_ids": [
AUDIENCE_ID
],
"percentage_included": 10000,
"metrics": [
{
"event_id": EVENT_ID,
"event_type": "pageview",
"scope": "visitor",
"aggregator": "unique",
"winning_direction": "increasing",
"display_title": "PDP Page Views",
"alias": "b20520c09877f956ad8a7ef7723139006f01174e69b253d1896c9d0a82ebe212",
"id": "2f7cd919-c73b-4a99-81c7-daa69e1cc2a6",
"visibility": "account",
"updated_at": "2025-05-21T15:40:30.967744070Z",
"account_id": ACCOUNT_ID,
"event_scoped": false
}
],
"allow_list": {},
"group_rule": {
"group_id": 0,
"traffic_allocation": 0
},
"group_remaining_traffic_allocation": 100,
"layer_id": 9300001984917,
"layer_experiment_id": 9300002730629,
"status": "draft"
},
"mab_test": {
"key": "mab_test",
"name": "Multi-armed Test",
"description": "",
"variations": {
"off": {
"key": "off",
"name": "Off",
"percentage_included": 3333,
"variation_id": VARIATION_ID,
"id": ID
},
"on": {
"key": "on",
"name": "On",
"percentage_included": 3333,
"variation_id": VARIATION_ID,
"id": ID
},
"on_hide_amounts": {
"key": "on_hide_amounts",
"name": "On Hide Amounts",
"percentage_included": 3334,
"variation_id": VARIATION_ID,
"id": ID
}
},
"baseline_variation_id": null,
"type": "multi_armed_bandit",
"id": ID,
"urn": "rules.flags.optimizely.com::ID",
"archived": false,
"enabled": false,
"created_time": "2025-09-12T17:45:13.103056Z",
"updated_time": "2025-09-12T17:45:13.072498Z",
"audience_conditions": [],
"audience_ids": [],
"percentage_included": 10000,
"metrics": [
{
"event_id": EVENT_ID,
"event_type": "custom",
"scope": "visitor",
"aggregator": "unique",
"winning_direction": "increasing",
"display_title": "add_to_cart",
"alias": "ALIAS_ID",
"id": "ID",
"project_id": PROJECT_ID,
"visibility": "experiment",
"updated_at": "2025-09-12T17:45:14.341649449Z",
"account_id": ACCOUNT_ID,
"event_scoped": false
}
],
"allow_list": {},
"group_rule": {
"group_id": 0,
"traffic_allocation": 0
},
"group_remaining_traffic_allocation": 100,
"layer_id": LAYER_ID,
"layer_experiment_id": EXPERIMENT_LAYER_ID,
"status": "draft"
}
},
"rule_priorities": [
"already-exisiting-a-b-test",
"mab_test"
],
"id": ID,
"urn": "rulesets.flags.optimizely.com::ID",
"archived": false,
"enabled": false,
"updated_time": "2025-09-12T17:45:13.443788Z",
"flag_key": "doc-test-flag",
"environment_key": "development",
"environment_name": "Development",
"environment_id": ENVIRONMENT_ID,
"default_variation_key": "off",
"default_variation_name": "Off",
"revision": 9,
"status": "draft",
"role": "admin"
}
See the Update the Ruleset for a Flag in an Environment endpoint reference documentation for information on creating a multi-armed bandit with the Feature Experimentation API.
With the Feature Experimentation UI
To create an optimization in the Optimizely app, see Create an MAB optimization in your Feature Experimentation project section in the user documentation.
Run the multi-armed bandit rule
Start your multi-armed bandit rule and flag (if it is not already running).
With the Feature Experimentation REST API
- Enable the flag by using the Enable the Ruleset for a Flag in an Environment endpoint.
- Endpoint –
POST https://api.optimizely.com/flags/v1/projects/PROJECT_ID/flags/FLAG_KEY/environments/ENVIRONMENT_KEY/ruleset/enabled
- Authentication – Include your API key in the
Authorization
header as a Bearer token. See Generate tokens and use the REST APIs. - Request example
curl --request POST \ --url https://api.optimizely.com/flags/v1/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset/enabled \ --header 'accept: application/json' \ --header 'authorization: Bearer TOKEN'
- Response example
{ "url": "/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset", "update_url": "/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset", "disable_url": "/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset/disabled", "rules": { "already-exisiting-a-b-test": { "key": "already-exisiting-a-b-test", "name": "Already-exisiting-a-b-test", "description": "", "variations": { "off": { "key": "off", "name": "Off", "percentage_included": 5000, "variation_id": VARIATION_ID, "id": ID }, "on": { "key": "on", "name": "On", "percentage_included": 5000, "variation_id": VARIATION_ID, "id": ID } }, "baseline_variation_id": VARIATION_ID, "type": "a/b", "distribution_mode": "manual", "id": ID, "urn": "rules.flags.optimizely.com::ID", "archived": false, "enabled": false, "created_time": "2025-09-12T16:44:11.824135Z", "updated_time": "2025-09-12T19:40:45.033536Z", "audience_conditions": [ "or", { "audience_id": AUDIENCE_ID } ], "audience_ids": [ AUDIENCE_ID ], "percentage_included": 10000, "metrics": [ { "event_id": EVENT_ID, "event_type": "pageview", "scope": "visitor", "aggregator": "unique", "winning_direction": "increasing", "display_title": "PDP Page Views", "alias": "ALIAS_ID", "id": "ID", "visibility": "account", "updated_at": "2025-05-21T15:40:30.967744070Z", "account_id": 10886221501, "event_scoped": false } ], "allow_list": {}, "group_rule": { "group_id": 0, "traffic_allocation": 0 }, "group_remaining_traffic_allocation": 100, "layer_id": LAYER_ID, "layer_experiment_id": LAYER_EXPERIMENT-ID, "status": "draft" }, "mab_test": { "key": "mab_test", "name": "Multi-armed Test", "description": "", "variations": { "off": { "key": "off", "name": "Off", "percentage_included": 3333, "variation_id": VARIATION_ID, "id": ID }, "on": { "key": "on", "name": "On", "percentage_included": 3333, "variation_id": VARIATION_ID, "id": ID }, "on_hide_amounts": { "key": "on_hide_amounts", "name": "On Hide Amounts", "percentage_included": 3334, "variation_id": VARIATION_ID, "id": ID } }, "baseline_variation_id": null, "type": "multi_armed_bandit", "id": ID, "urn": "rules.flags.optimizely.com::ID", "archived": false, "enabled": false, "created_time": "2025-09-12T17:45:13.103056Z", "updated_time": "2025-09-12T19:40:45.033536Z", "audience_conditions": [], "audience_ids": [], "percentage_included": 10000, "metrics": [ { "event_id": EVENT_ID, "event_type": "custom", "scope": "visitor", "aggregator": "unique", "winning_direction": "increasing", "display_title": "add_to_cart", "alias": "ALIAS_ID", "id": "ID", "project_id": PROJECT_ID, "visibility": "experiment", "updated_at": "2025-09-12T17:45:14.341649449Z", "account_id": ACCOUNT_ID, "event_scoped": false } ], "allow_list": {}, "group_rule": { "group_id": 0, "traffic_allocation": 0 }, "group_remaining_traffic_allocation": 100, "layer_id": LAYER_ID, "layer_experiment_id": EXPERIMENT_ID, "status": "draft" } }, "rule_priorities": [ "already-exisiting-a-b-test" ], "id": ID, "urn": "rulesets.flags.optimizely.com::ID", "archived": false, "enabled": true, "updated_time": "2025-09-12T19:40:45.033536Z", "flag_key": "doc-test-flag", "environment_key": "development", "environment_name": "Development", "environment_id": ENIRONMENT_ID, "default_variation_key": "off", "default_variation_name": "Off", "revision": 10, "status": "running", "role": "admin" }
- Endpoint –
- Launch your mult-armed bandit rule by enabling the rule by using the Update the Ruleset for a Flag in an Environment endpoint and setting the
enabled
path totrue
.ImportantFor
PATCH
requests, firstGET
the current ruleset, merge only the fields you need to change, and then send thePATCH
. This helps prevent overwriting existing settings you did not intend to modify.- Endpoint –
PATCH https://api.optimizely.com/flags/v1/projects/PROJECT_ID/flags/FLAG_KEY/environments/ENVIRONMENT_KEY/ruleset
- Authentication – Include your API key in the
Authorization
header as a Bearer token. See Generate tokens and use the REST APIs. - Request example
curl --request PATCH \ --url https://api.optimizely.com/flags/v1/projects/5629661695180800/flags/doc-test-flag/environments/development/ruleset \ --header 'accept: application/json' \ --header 'authorization: Bearer 2:GgwONZa2GYbdATLU4OPmU5qZKq5RNkqQDf_w_a2B3310AnOOELX0' \ --header 'content-type: application/json-patch+json' \ --data ' [ { "op": "replace", "path": "/rules/mab-test/enabled", "value": true } ] '
- Response example
{ "url": "/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset", "update_url": "/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset", "disable_url": "/projects/PROJECT_ID/flags/doc-test-flag/environments/development/ruleset/disabled", "rules": { "already-exisiting-a-b-test": { "key": "already-exisiting-a-b-test", "name": "Already-exisiting-a-b-test", "description": "", "variations": { "off": { "key": "off", "name": "Off", "percentage_included": 5000, "variation_id": VARIATION_ID, "id": ID }, "on": { "key": "on", "name": "On", "percentage_included": 5000, "variation_id": VARIATION_ID, "id": ID } }, "baseline_variation_id": BASELINE_VARIATION_ID, "type": "a/b", "distribution_mode": "manual", "id": ID, "urn": "rules.flags.optimizely.com::ID", "archived": false, "enabled": false, "created_time": "2025-09-12T16:44:11.824135Z", "updated_time": "2025-09-12T19:51:52.213225Z", "audience_conditions": [ "or", { "audience_id": AUDIENCE_ID } ], "audience_ids": [ AUDIENCE_ID ], "percentage_included": 10000, "metrics": [ { "event_id": EVENT_ID, "event_type": "pageview", "scope": "visitor", "aggregator": "unique", "winning_direction": "increasing", "display_title": "PDP Page Views", "alias": "b20520c09877f956ad8a7ef7723139006f01174e69b253d1896c9d0a82ebe212", "id": "2f7cd919-c73b-4a99-81c7-daa69e1cc2a6", "visibility": "account", "updated_at": "2025-05-21T15:40:30.967744070Z", "account_id": ACCOUNT_ID, "event_scoped": false } ], "fetch_results_ui_url": "https://app.optimizely.com/v2/projects/PROJECT_ID/results/RESULT_ID/experiments/EXPERIMENT_ID?baseline=1500745", "allow_list": {}, "group_rule": { "group_id": 0, "traffic_allocation": 0 }, "group_remaining_traffic_allocation": 100, "layer_id": LAYER_ID, "layer_experiment_id": LAYER_EXPERIMENT_ID, "status": "running" }, "mab_test": { "key": "mab_test", "name": "Multi-armed Test", "description": "", "variations": { "off": { "key": "off", "name": "Off", "percentage_included": 3333, "variation_id": VARIATION_ID, "id": ID }, "on": { "key": "on", "name": "On", "percentage_included": 3333, "variation_id": VARIATION_ID, "id": ID }, "on_hide_amounts": { "key": "on_hide_amounts", "name": "On Hide Amounts", "percentage_included": 3334, "variation_id": VARIATION_ID, "id": ID } }, "baseline_variation_id": null, "type": "multi_armed_bandit", "id": ID, "urn": "rules.flags.optimizely.com::ID", "archived": false, "enabled": true, "created_time": "2025-09-12T17:45:13.103056Z", "updated_time": "2025-09-12T19:40:45.033536Z", "audience_conditions": [], "audience_ids": [], "percentage_included": 10000, "metrics": [ { "event_id": EVENT_ID, "event_type": "custom", "scope": "visitor", "aggregator": "unique", "winning_direction": "increasing", "display_title": "add_to_cart", "alias": "ALIAS_ID", "id": "ID", "project_id": PROJECT_ID, "visibility": "experiment", "updated_at": "2025-09-12T17:45:14.341649449Z", "account_id": ACCOUNT-ID, "event_scoped": false } ], "allow_list": {}, "group_rule": { "group_id": 0, "traffic_allocation": 0 }, "group_remaining_traffic_allocation": 100, "layer_id": LAYER_ID, "layer_experiment_id": LAYER_EXPERIMENT_ID, "status": "draft" } }, "rule_priorities": [ "already-exisiting-a-b-test", "mab_test" ], "id": ID, "urn": "rulesets.flags.optimizely.com::ID", "archived": false, "enabled": true, "updated_time": "2025-09-12T19:51:52.287147Z", "flag_key": "doc-test-flag", "environment_key": "development", "environment_name": "Development", "environment_id": ENVIRONMENT_ID, "default_variation_key": "off", "default_variation_name": "Off", "revision": 11, "status": "running", "role": "admin" }
- Endpoint –
With the Feature Experimentation UI
See Run a multi-armed bandit optimization in Feature Experimentation in the user documentation.
Implement the multi-armed bandit
If you have already implemented the flag in your application's codebase, no further configuration is required for the flag delivery. If you have not, implement the Decide method call in your code to enable or disable the flag for a user:
// Decide if user sees a feature flag variation
let user = optimizely.createUserContext(userId: "user123", attributes: ["logged_in":true])
let decision = user.decide(key: "flag_1")
let enabled = decision.enabled
// Decide if user sees a feature flag variation
user := optimizely.CreateUserContext("user123", map[string]interface{}{"logged_in": true})
decision := user.Decide("flag_1", nil)
enabled := decision.Enabled
# Decide if user sees a feature flag variation
user = optimizely.create_user_context("user123", {'logged_in': True})
decision = user.decide("flag_1")
enabled = decision.enabled
// Decide if user sees a feature flag variation
$user = $optimizely_client->createUserContext('user123', ['logged_in' => true]);
$decision = $user->decide('flag_1');
$enabled = $decision->getEnabled();
# Decide if user sees a feature flag variation
user = optimizely_client.create_user_context('user123', {'logged_in' => true})
decision = user.decide('flag_1')
enabled = decision.enabled
// Decide if user sees a feature flag variation
var user = optimizely.CreateUserContext("user123", new UserAttributes { { "logged_in", true } });
var decision = user.Decide("flag_1");
var enabled = decision.Enabled;
// Decide if user sees a feature flag variation
OptimizelyUserContext user = optimizely.createUserContext("user123", new HashMap<String, Object>() { { put("logged_in", true); } });
OptimizelyDecision decision = user.decide("flag_1");
Boolean enabled = decision.getEnabled();
// Decide if user sees a feature flag variation
const user = optimizely.createUserContext('user123', { logged_in: true });
const decision = user.decide('flag_1');
const enabled = decision.enabled;
// Decide if user sees a feature flag variation
const decision = useDecision('flag_1', null, { overrideUserAttributes: { logged_in: true }});
const enabled = decision.enabled;
// Decide if user sees a feature flag variation
var user = await flutterSDK.createUserContext(userId: "user123");
var decisionResponse = await user!.decide("flag_1");
var decision = decisionResponse.decision;
var enabled = decision!.enabled;
See the following for more detailed examples.
- Android example usage
- Go example usage
- C# example usage
- Flutter example usage
- Java example usage
- JavaScript example usage – SDK versions 6.0.0 and later.
- Javascript (Browser) example usage – SDK versions 5.3.5 and earlier.
- JavaScript (Node) example usage – SDK versions 5.3.5 and earlier.
- PHP example usage
- Python example usage
- React example usage
- Ruby example usage
- Swift example usage
Optimizely Feature Experimentation uses the Decide method call to decide whether a user qualifies for the rule and which variation they receive. The Optimizely Feature Experimentation SDKs let you reuse the exact flag implementation for different flag rules.
Remember, a user evaluates against all the rules in a ruleset in order before being bucketed into a rule's variation. See Create feature flags.
Updated 13 days ago