Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

The availability of features may depend on your plan type. Contact your Customer Success Manager if you have any questions.

Dev guideRecipesAPI ReferenceChangelog
Dev guideAPI ReferenceRecipesChangelogUser GuideGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

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.

🚧

Important

MAB 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:

  1. Create a flag in your Feature Experimentation project.

  2. Handle user IDs.

    📘

    Note

    You should configure a user profile service to ensure consistent user bucketing if you are using a server-side SDK.

  3. 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.

🚧

Important

For PATCH requests, first GET the current ruleset, merge only the fields you need to change, and then send the PATCH. This helps prevent overwriting existing settings you did not intend to modify.

EndpointPATCH 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

  1. Enable the flag by using the Enable the Ruleset for a Flag in an Environment endpoint.
    • EndpointPOST 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"
      }
      
  2. 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 to true.
    🚧

    Important

    For PATCH requests, first GET the current ruleset, merge only the fields you need to change, and then send the PATCH. This helps prevent overwriting existing settings you did not intend to modify.

    • EndpointPATCH 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"
      }
      

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.

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.