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 uses reinforcement learning to dynamically shift traffic toward better-performing variations. Unlike A/B tests, which split traffic evenly and wait for statistical significance, MABs continuously reallocate based on observed conversion rates.

🚧

Important

MAB optimizations do not produce statistical significance. The algorithm optimizes for total conversions by shifting traffic to winning variations, without analyzing why a variation performs better or worse.

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

Before sending a PATCH, GET the current ruleset and merge in only the fields you want to change. This prevents accidentally overwriting other settings.

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_KEY/environments/ENVIRONMENT_KEY/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_KEY/environments/ENVIRONMENT_KEY/ruleset",
  "update_url": "/projects/PROJECT_ID/flags/FLAG_KEY/environments/ENVIRONMENT_KEY/ruleset",
  "enable_url": "/projects/PROJECT_ID/flags/FLAG_KEY/environments/ENVIRONMENT_KEY/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": 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-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 Update the Ruleset for a Flag in an Environment for the full API reference.

With the Feature Experimentation UI

See Create an MAB optimization in the Optimizely app 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": 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"
          },
          "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": ENVIRONMENT_ID,
        "default_variation_key": "off",
        "default_variation_name": "Off",
        "revision": 10,
        "status": "running",
        "role": "admin"
      }
      
  2. Launch your multi-armed bandit rule by enabling the rule. Use the Update the Ruleset for a Flag in an Environment endpoint and setting the enabled path to true.
    🚧

    Important

    Before sending a PATCH, GET the current ruleset and merge in only the fields you want to change. This prevents accidentally overwriting other settings.

    • 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/doc-test-flag/environments/development/ruleset \
           --header 'accept: application/json' \
           --header 'authorization: Bearer TOKEN' \
           --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, no further changes are needed. If not, add the Decide call to your code:

// 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', { 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.

Decide determines whether a user qualifies for a rule and which variation they receive. You can reuse the same flag implementation across different flag rules.

Users are evaluated against rules in the ruleset in order. See Create feature flags.