{"id":19935157,"url":"https://github.com/optimizely/fs_rest_customer","last_synced_at":"2025-05-03T12:31:08.882Z","repository":{"id":71780295,"uuid":"136378456","full_name":"optimizely/fs_rest_customer","owner":"optimizely","description":"Example python scripts that use the REST API for FS objects","archived":true,"fork":false,"pushed_at":"2018-11-29T05:03:37.000Z","size":19,"stargazers_count":1,"open_issues_count":2,"forks_count":3,"subscribers_count":78,"default_branch":"master","last_synced_at":"2025-03-01T12:17:28.774Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/optimizely.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-06T19:44:56.000Z","updated_at":"2023-09-29T15:47:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"45a2369f-4f76-4c11-be2e-c41b6911daa7","html_url":"https://github.com/optimizely/fs_rest_customer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/optimizely%2Ffs_rest_customer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/optimizely%2Ffs_rest_customer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/optimizely%2Ffs_rest_customer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/optimizely%2Ffs_rest_customer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/optimizely","download_url":"https://codeload.github.com/optimizely/fs_rest_customer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252190756,"owners_count":21708948,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T23:18:58.052Z","updated_at":"2025-05-03T12:31:08.870Z","avatar_url":"https://github.com/optimizely.png","language":"Python","readme":"# Optimizely Full Stack REST Tutorial\n\nThis tutorial enables you to quickly get started in your development efforts to programmatically create an Experiment using the Full Stack REST APIs. This Python programming sample demonstrates how to:\n\n* Create a Project, Attribute, Event, Audience, and Experiment.\n* Run, pause, and update an Experiment.  \n\n## Prerequisites\n* Python\n* YAML and JSON libraries for Python\n\n## Quick Start\nThis section shows you how to prepare, build, and run the sample project.\n\n### Create an Account and Get a Token\nIn order to build and run the sample you must retrieve a token: \n\n1. Create a developer account at [app.optimizely.com](https://app.optimizely.com/v2/profile/api). Once logged in, the dashboard is displayed.\n2. Click **Profile** on the bottom left hand corner of the navigation tree.\n3. Select the **API Access** tab.\n4. Click **Generate New Token**.\n5. Enter a name for the new token on the popup and click **Create**. The website will redirect to the dashboard.\n6. Copy the token value from the **Token** column on the dashboard.\n7. Open **[.settings.yaml](./.settings.yaml)** in this package using a text editor.\n8. Replace the token value in **[.settings.yaml](./.settings.yaml)** with the value obtained in Step 6. For example:\n\n```yaml\ntoken: \"2:Ba4f4asdcasd1234.....\"\n\n```\n\n### Run the Python Scripts\nThe sample project consists of a series of Python scripts that invoke Optimizely's REST APIs to build the necessary components of an Experiment. As you run the scripts, you must copy various pieces of information (e.g. ID values) from the responses that are displayed on the screen and paste them into the appropriate parts of **[.settings.yaml](./.settings.yaml)**.\n\nThis section walks you through the scripts and the steps to copy and paste the data you need from the output.\n\n#### Generate a Project\n1. Run **[project.py](./project.py)**:\n\n```\npython project.py\n```\n\n2. Copy the value for `id` from the **Update the project** response:\n```\nUpdate the project\nRequest:\nPATCH https://api.optimizely.com/v2/projects/10839870236\n...\n\nResponse:\n200\nbody:\n{\n    \"account_id\": 10761771283, \n    \"confidence_threshold\": 0.9, \n    \"created\": \"2018-06-08T16:12:29.455700Z\", \n    \"id\": 10839870236, // Copy this generated Project ID\n    ...\n}\n```\n\n3. Paste the ID value into the `project_id` field in **[.settings.yaml](./.settings.yaml)**:\n```yaml\n...\n# Create a project ID by executing project.py\nproject_id: 10839870236 // Paste the Project ID here\n...\n```\n\n#### Generate an Attribute\n1. Run **attribute.py**:\n\n```\npython attribute.py\n```\n\n2. Copy the value for `name` from the **Update the attribute** response:\n```\nUpdate the attribute\nRequest:\nPATCH https://api.optimizely.com/v2/attributes/10804487279\n...\n\nResponse:\n200\nbody:\n{\n    ... \n    \"last_modified\": \"2018-06-08T16:13:40.378120Z\", \n    \"name\": \"FS attribute 4001593747014 - updated\", // Copy this generated Attribute name\n    \"project_id\": 10839870236\n}\n```\n3. Paste the value into the `custom_audience_attribute` field in **[.settings.yaml](./.settings.yaml)**:\n```yaml\n# Create an attribute Name by executing attribute.py\ncustom_audience_attribute: FS attribute 4001593747014 - updated // Paste the name here\n```\n\n#### Generate an Event\n1. Run **[event.py](./event.py)**:\n\n```\npython event.py\n```\n\n2. Copy the value for `id` from the **Update the event** response:\n\n```\nUpdate the event\nRequest:\nPATCH https://api.optimizely.com/v2/projects/10839870236/custom_events/10843270186\n...\n\nResponse:\n200\nbody:\n{\n    \"archived\": false, \n    \"category\": \"add_to_cart\", \n    \"created\": \"2018-06-08T16:14:36.952890Z\", \n    \"description\": \"A new event_updated\", \n    \"event_type\": \"custom\", \n    \"id\": 10843270186, // Copy this generated ID\n    ...\n}\n```\n\n3. Paste the value into the `event_id` field in **[.settings.yaml](./.settings.yaml)**:\n\n```yaml\n# Create an event ID by executing event.py\nevent_id: 10843270186 // Paste the ID here\n```\n\n#### Generate an Audience\n1. Run **[audience.py](./audience.py)**:\n\n```\npython audience.py\n```\n\n2. Copy the value for `id` from the **Update the audience** response:\n\n```\nUpdate the audience\nRequest:\nPATCH https://api.optimizely.com/v2/audiences/10828513760\nh...\n\nResponse:\n200\nbody:\n{\n    \"archived\": false, \n    \"conditions\": \"[\\\"and\\\", [\\\"or\\\", [\\\"or\\...\", \n    \"created\": \"2018-06-08T16:15:02.745860Z\", \n    \"description\": \"Using a Full Stack attribute\", \n    \"id\": 10828513760, // Copy this generated ID\n    ...\n}\n```\n\n3. Paste the value into the `audience_id` field in **[.settings.yaml](./.settings.yaml)**:\n\n```yaml\n# Create an audience ID by executing audience.py\naudience_id: 10828513760 //Paste the ID here\n```\n\n#### Generate a Feature\n1. Run **[feature.py](./feature.py)**:\n\n```\npython feature.py\n```\n\n2. Copy the value for `key` from the **Update the feature** response:\n\n```\nUpdate the feature\nRequest:\nPATCH https://api.optimizely.com/v2/features/10839281466\n...\n\nResponse:\n200\nbody:\n{\n    \"archived\": false, \n    \"created\": \"2018-06-08T16:16:04.143120Z\", \n    \"description\": \"This is s a fs feature description2458297709274_updated\", \n    \"environments\": {\n        \"Production\": {\n        ...\n        }\n    }, \n    \"id\": 10839281466, \n    \"key\": \"fs_feature_2458297709274_updated\", // Copy this generated key\n    ...\n    ]\n}\n```\n\n3. Paste the key under the `variable_values` field in **[.settings.yaml](./.settings.yaml)**:\n\n```yaml\n# Create an feature ID and variables by executing feature.py\n# feature_id: 10756572576\n# variable_values:\n#   - fs_feature_2458297709274_updated  // Paste the key here\n```\n\n**Note:** Providing a Feature and Variable for an Experiment is optional. For this quickstart, this section can be commented out.\n\n#### Generate an Experiment\n1. Run **[experiment.py](./experiment.py)**:\n\n```\npython experiment.py\n```\n\n2. Press the **return** key when the following prompt appears:\n```\nPlease provide an Feature ID (leave empty to create an experiment without a feature): \n```\n\n3. Copy the value for `id` from the **Update the experiment #2** response:\n\n```\nResponse:\nUpdate the experiment #2\nRequest:\nPATCH https://api.optimizely.com/v2/experiments/10834392320\n...\n\n200\nbody:\n{\n    \"allocation_policy\": \"manual\", \n    \"audience_conditions\": \"everyone\", \n    \"campaign_id\": 10802528852, \n    \"changes\": [], \n    \"created\": \"2018-06-08T16:17:35.589410Z\", \n    \"description\": \"This is s a fs exp description3614128498008_updated_2\", \n    \"earliest\": \"2018-06-08T16:17:43.023580Z\", \n    \"holdback\": 5000, \n    \"id\": 10834392320,  // Copy this generated ID\n    ...\n}\n```\n\n4. Paste the ID in the `experiment_id` field in **[.settings.yaml](./.settings.yaml)**:\n\n```\n# Create an experiment ID by executing experiment.py\nexperiment_id: 10834392320 // Paste the ID here\n```\n\n#### Run the Experiment\n1. Save **[.settings.yaml](./.settings.yaml)**.\n\n2. Run **[result.py](./result.py)**:\n\n```\npython result.py\n```\n\nThe script runs the Experiment using the settings pasted into **[.settings.yaml](./.settings.yaml)** to generate results, run a time series, and retrieve and display the results (stored as CSV data) to the screen.\n\n## Steps to Create the Sample \nThe sample consists of a series of Python scripts that you execute to configure and run an Experiment. This section describes the important aspects of those scripts including helper functions, variables, and output. \n\n* [Develop the Helper Functions](#develop-the-helper-functions)\n* [Create and Update an Optimizely Project](#create-and-update-an-optimizely-project)\n* [Create and Update an Attribute to add to the Project](#create-and-update-an-attribute-to-add-to-the-project)\n* [Create and Update an Event](#create-and-update-an-event)\n* [Create and Update an Audience](#create-and-update-an-audience)\n* [Create and Update a Feature](#create-and-update-a-feature)\n* [Create and Run an Experiment](#create-and-run-an-experiment)\n* [Get the Results of the Experiment](#get-the-results-of-the-experiment)\n\n### Develop the Helper Functions\nThe purpose of running the individual scripts is to demonstrate the endpoints used to create and update the elements of an Experiment. Once you have created these elements and manually added their identifiers to **[.settings.yaml](./.settings.yaml)**, execute **[result.py](./result.py)** to run the experiment.\n\nEach script reads values from **[.settings.yaml](./.settings.yaml)** using a helper module called `example_tools`. The source code for this is located in **[./example_tools/\\_\\_init\\_\\_.py](./example_tools/__init__.py)** and contains a number of important helper functions used by the scripts:\n* `load_settings()`: Loads **[settings.yaml](./settings.yaml)** and returns a dictionary representing the key/value settings stored in that file.\n* `print_request_details()`: Prints the details of a given REST request to the screen using different font colors.\n* `print_response_details()`: Prints the details of a given REST response to the screen using different font colors.\n* `print_new_method()`: Prints an action name to the screen (e.g. \"Create a new event\") in different font colors to make the output easier to read.\n\nEach script imports `example_tools` at the top of the script. The scripts then invoke `load_settings()` to read **[.settings.yaml](./.settings.yaml)** and extract the necessary settings referencing each by name. For example:\n\n```python\nimport example_tools as et\n\nsettings = et.load_settings()\n\ntoken = ('token' in settings and settings['token']) or raw_input(\n    \"Please enter a valid personal token (https://app.optimizely.com/v2/profile/api): \")\n\nbase_url = ('base_url' in settings and settings[\n            'base_url']) or 'https://api.optimizely.com/v2'\n\nproject_id = ('project_id' in settings and settings['project_id']) or int(raw_input(\"Please provide a Project ID: \"))\n```\n\nEach script retrieves the token and base URL from the settings. The token ensures that you are authenticated to invoke endpoints on the specified base URL. Note that some scripts retrieve additional settings such as the project ID, as shown in the example above. \n\n### Create and Update an Optimizely Project\n\nA [Project](https://help.optimizely.com/Set_Up_Optimizely/Manage_projects_in_Optimizely_X_Web) is a container for Experiments and is the first element that is created. \n\n**[project.py](./project.py)** starts by loading the token and base URL from the settings as described above. It then generates a new random number that is used to generate a unique project name and description:\n```python\n...\nmake_string_unique = str(random.randrange(0, 10000000000001, 2))\n\nname = 'full_stack project' + make_string_unique\ndescription = 'My REST API FS project' + make_string_unique\n\n...\n...\n```\n\nThe script defines two helper methods: \n* `create_project()`: Creates a new Optimizely Project by invoking the [Create a Project](https://developers.optimizely.com/x/rest/v2/#create-a-project) endpoint and passing in the unique name and description generated above. The function then outputs the request and response to the screen and returns the ID of the new project:\n\n```python\ndef create_project():\n    url = base_url + '/projects'\n\n    project = {\n        'name': name,\n        'confidence_threshold': 0.9,\n        'description': description,\n        'platform': 'custom',\n        'sdks': ['python'],\n        'status': 'active',\n    }\n    et.print_request_details('POST', url, project, headers)\n    r = requests.post(url, data=json.dumps(project), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return r.json()['id']\n```\n\n* `update_project()`: Updates the project that was created above by appending `_updated` to the name and description and invoking the [Update a Project](https://developers.optimizely.com/x/rest/v2/#update-a-project) endpoint passing in the ID of the project. The function then outputs the request and response to the screen:\n\n```python\ndef update_project(id):\n    url = base_url + '/projects/' + str(id)\n\n    project = {\n        'name': name + \"_updated\",\n        'confidence_threshold': 0.9,\n        'description': description + \"_updated\",\n        'platform': 'custom',\n        'sdks': ['python'],\n        'status': 'active',\n    }\n    et.print_request_details('PATCH', url, project, headers)\n    r = requests.patch(url, data=json.dumps(project), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n```\n\nThe script then invokes these two functions:\n\n```python\net.print_new_method('Create a new project')\nid = create_project()\n\net.print_new_method('Update the project')\nupdate_project(id)\n```\n\n**Note:** Most of the scripts use the pattern of creating an entity followed by updating that entity.\n\n### Create and Update an Attribute to add to the Project\nAn [Attribute](https://help.optimizely.com/Target_Your_Visitors/Custom_Attributes%3A_Capture_visitor_data_through_the_API_in_Optimizely_X) is an entity that allows you to segment a project. For this quickstart, **[attribute.py](./attribute.py)** is used to create and add one attribute to the Project.\n\nThe script starts by importing the token and base URL followed by the ID of the project that was created by **[project.py](./project.py)** and manually added to **[.settings.yaml](./.settings.yaml)**. It stores this ID in `project_id`:\n\n```python\n...\nproject_id = ('project_id' in settings and settings['project_id']) or int(\n    raw_input(\"Please provide a Project ID: \"))\n...\n```\n\nThe script then generates a new random number that is used to create a unique Attribute key, name, and description:\n```python\n...\nmake_string_unique = str(random.randrange(0, 10000000000001, 2))\n\n# Variables to modify\nkey = 'fs_attribute ' + make_string_unique\nname = 'FS attribute ' + make_string_unique\ndescription = 'This is s a fs attr description' + make_string_unique\n...\n```\n\nThe script then defines two helper methods: \n* `create_attribute()`: Creates a new Attribute by invoking the [Create an Attribute](https://developers.optimizely.com/x/rest/v2/#create-an-attribute) endpoint and passing in the unique key, name, and description created above. The function then outputs the request and response to the screen and returns the ID of the new Attribute:\n\n```python\ndef create_attribute():\n    url = base_url + '/attributes'\n\n    attribute = {\n        'key': key,\n        'project_id': project_id,\n        'archived': False,\n        'description': 'A new attribute',\n        'name': name,\n    }\n\n    et.print_request_details('POST', url, attribute, headers)\n\n    r = requests.post(url, data=json.dumps(attribute), headers=headers)\n\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return j['id']\n```\n\n* `update_project()`: Updates the Attribute that was created above by appending `_updated` to the key, name, and description and invoking the [Update an Attribute](https://developers.optimizely.com/x/rest/v2/#update-an-attribute) endpoint passing in the ID of the Attribute. The function then outputs the request and response to the screen:\n\n```python\ndef update_attribute(id):\n    url = base_url + '/attributes/' + str(id)\n\n    attribute = {\n        'key': key + '_updated',\n        'project_id': project_id,\n        'archived': False,\n        'description': description + ' - updated',\n        'name': name + ' - updated',\n    }\n\n    et.print_request_details('PATCH', url, attribute, headers)\n\n    r = requests.patch(url, data=json.dumps(attribute), headers=headers)\n\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n```\n\nThe script then invokes these two functions:\n\n```python\net.print_new_method('Create a new attribute')\nid = create_attribute()\n\net.print_new_method('Update the attribute')\nupdate_attribute(id)\n```\n\n### Create and Update an Event\nAn [Event](https://help.optimizely.com/Measure_success%3A_Track_visitor_behaviors/Events%3A_Tracking_clicks%2C_pageviews%2C_and_other_visitor_actions) tracks an action that a visitor takes on a website. For this quickstart, **[event.py](./event.py)** is used to create and add one event to the Project.\n\nThe script starts by importing the token and base URL followed by the ID of the Project that was created by **[project.py](./project.py)** and manually added to **[.settings.yaml](./.settings.yaml)**. It stores this ID in `project_id`:\n\n```python\n...\nproject_id = ('project_id' in settings and settings['project_id']) or int(\n    raw_input(\"Please provide a Project ID: \"))\n...\n```\n\nThe script then generates a new random number that is used to create a unique attribute key and name:\n```python\n...\nmake_string_unique = str(random.randrange(0, 10000000000001, 2))\n\n# Variables to modify\nkey = \"full_stack_event_\" + make_string_unique\nname = 'FS event' + make_string_unique\n...\n```\n\nThe script then defines two helper methods: \n* `create_event()`: Creates a new Event by invoking the [Create a Custom Event](https://developers.optimizely.com/x/rest/v2/#create-a-custom-event) endpoint and passing in the unique key and name generated above. The function then outputs the request and response to the screen and returns the ID of the new Event:\n\n```python\ndef create_event():\n    url = base_url + '/projects/' + str(project_id) + '/custom_events'\n\n    event = {\n        'key': key,\n        \"event_type\": \"custom\",\n        'archived': False,\n        'description': 'A new event',\n        'name': name,\n        \"category\": \"add_to_cart\"\n    }\n\n    et.print_request_details('POST', url, event, headers)\n    r = requests.post(url, data=json.dumps(event), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return r.json()['id']\n```\n\n* `update_event()`: Updates the Event that was created above by appending `_updated` to the key and name and invoking the [Update a Custom Event](https://developers.optimizely.com/x/rest/v2/#update-a-custom-event) endpoint passing in the ID of the Event created above. The function then outputs the request and response to the screen:\n\n```python\ndef update_event(id):\n    url = base_url + '/projects/' + \\\n        str(project_id) + '/custom_events/' + str(id)\n\n    event = {\n        'key': key + '_updated',\n        \"event_type\": \"custom\",\n        'archived': False,\n        'description': 'A new event' + '_updated',\n        'name': 'FS event' + '_updated',\n        \"category\": \"add_to_cart\"\n    }\n\n    et.print_request_details('PATCH', url, event, headers)\n    r = requests.patch(url, data=json.dumps(event), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return r.json()['id']\n```\n\nThe script then invokes these two functions:\n\n```python\net.print_new_method('Create a new event')\nid = create_event()\n\net.print_new_method('Update the event')\nupdate_event(id)\n```\n\n### Create and Update an Audience\nAn [Audience](https://help.optimizely.com/Target_Your_Visitors/Audiences%3A_Choose_which_visitors_to_include) is a type of visitor who comes to your site. For this quickstart, **[audience.py](./audience.py)** is used to create and add one Audience to the Project.\n\nThe script starts by importing the token and base URL followed by the Project ID and Attribute name that were created by **[project.py](./project.py)** and **[attribute.py](./attribute.py)** respectively and manually added to **[.settings.yaml](./.settings.yaml)**. It stores these values in `project_id` and `custom_audience_attribute`:\n\n```python\n...\nproject_id = ('project_id' in settings and settings['project_id']) or int(\n    raw_input(\"Please provide a Project ID: \"))\ncustom_audience_attribute = ('project_id' in settings and settings['custom_audience_attribute']) or int(\n    raw_input(\"Please provide the name of a custom attribute: \"))\n...\n```\n\nThe script then generates a new random number that is used to create a unique Audience name:\n```python\n...\nmake_string_unique = str(random.randrange(0, 10000000000001, 2))\n\n# Variables to modify\nname = \"Using a Full Stack attribute\" + make_string_unique\n...\n```\n\nThe script then defines two helper methods: \n* `create_audience()`: Creates a new Audience by invoking the [Create an Audience](https://developers.optimizely.com/x/rest/v2/#create-an-audience) endpoint and passing in the Project ID and unique audience name generated above. The function then outputs the request and response to the screen and returns the ID of the new Audience:\n\n```python\ndef create_audience():\n    url = base_url + '/audiences'\n\n    audience = {\n        \"project_id\": project_id,\n        \"archived\": False,\n        \"conditions\": '[\"and\", [\"or\", [\"or\", {\"name\": \"' + custom_audience_attribute + '\", \"type\": \"custom_attribute\", \"value\": \"full stack value\"}], [\"or\", {\"name\": \"' + custom_audience_attribute + '\", \"type\": \"custom_attribute\", \"value\": \"val 2\"}]]]',\n        \"description\": \"Using a Full Stack attribute\",\n        \"is_classic\": False,\n        \"name\": name,\n        \"segmentation\": False\n    }\n\n    et.print_request_details('POST', url, audience, headers)\n    r = requests.post(url, data=json.dumps(audience), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return r.json()['id']\n```\n\n* `update_audience()`: Updates the Audience that was created above by appending `_updated` to the name and invoking the [Update an Audience](https://developers.optimizely.com/x/rest/v2/#update-an-audience) endpoint passing in the ID of the project and new name. The function then outputs the request and response to the screen:\n\n```python\ndef update_audience(id):\n    url = base_url + '/audiences/' + str(id)\n\n    audience = {\n        \"project_id\": project_id,\n        \"archived\": False,\n        \"conditions\": '[\"and\", [\"or\", [\"or\", {\"name\": \"' + custom_audience_attribute + '\", \"type\": \"custom_attribute\", \"value\": \"full stack value\"}], [\"or\", {\"name\": \"' + custom_audience_attribute + '\", \"type\": \"custom_attribute\", \"value\": \"val 2\"}]]]',\n        \"description\": \"Using a Full Stack attribute\",\n        \"is_classic\": False,\n        \"name\": name + '_updated',\n        \"segmentation\": False\n    }\n\n    et.print_request_details('PATCH', url, audience, headers)\n    r = requests.patch(url, data=json.dumps(audience), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n```\n\nThe script then invokes these two functions:\n\n```python\net.print_new_method('Create a new audience')\nid = create_audience()\n\net.print_new_method('Update the audience')\nupdate_audience(id)\n```\n\n### Create and Update a Feature\nA [Feature](https://help.optimizely.com/Build_Campaigns_and_Experiments/Create_and_deploy_features_and_feature_configurations) is a part of your website to test as part of an Experiment. For this quickstart, **[feature.py](./feature.py)** is used to create and add one Feature and one Variable for that Feature to the Project. A Variable is a parameter of your feature that you want to optimize.\n\nThe script starts by importing the token and base URL followed by the Project ID and Audience ID that were created by **[project.py](./project.py)** and **[audience.py](./audience.py)** respectively, and manually added to **[.settings.yaml](./.settings.yaml)**. It stores these values in `project_id` and `audience_id`:\n\n```python\n...\nproject_id = ('project_id' in settings and settings['project_id']) or int(\n    raw_input(\"Please provide a Project ID: \"))\naudience_id = ('audience_id' in settings and settings['audience_id']) or int(\n    raw_input(\"Please provide an Audience ID: \"))\n...\n```\n\nThe script then generates a new random number that is used to create a unique keys and descriptions for the Feature and a Variable:\n```python\n...\nmake_string_unique = str(random.randrange(0, 10000000000001, 2))\n\n# Variables to modify\nkey = 'fs_feature_' + make_string_unique\ndescription = 'This is s a fs feature description' + make_string_unique\nvariable_key = 'fs_feature_variable_' + make_string_unique\nvariable_description = 'This is s a fs feature variable description' + make_string_unique\n...\n```\n\nThe script then defines two helper methods: \n* **create_feature**: Creates a new Feature by invoking the [Create a Feature](https://developers.optimizely.com/x/rest/v2/#create-a-feature) endpoint and passing in the Project ID and Audience ID created above. The function then outputs the request and response to the screen and returns the ID of the new Feature and that of the Variable added as part of the Feature:\n\n```python\ndef create_feature():\n    url = base_url + '/features'\n\n    feature = {\n        \"key\": key,\n        \"project_id\": project_id,\n        \"archived\": False,\n        \"description\": description,\n        \"environments\": {\n            \"Production\": {\n                \"rollout_rules\": [\n                    {\n                        \"audience_ids\": [\n                            audience_id\n                        ],\n                        \"enabled\": False,\n                        \"percentage_included\": 100\n                    }\n                ]\n            }\n        },\n        \"variables\": [\n            {\n                \"default_value\": \"false\",\n                \"key\": variable_key,\n                \"type\": \"string\",\n                \"archived\": False,\n                \"description\": variable_description\n            }\n        ]\n    }\n    et.print_request_details('POST', url, feature, headers)\n    r = requests.post(url, data=json.dumps(feature), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return j['id'], j['variables'][0]['id']\n```\n\n* **update_feature**: Updates the Feature and Variable that were created above by appending `_updated` to their key and descriptions and invoking the [Update a Feature](https://developers.optimizely.com/x/rest/v2/#update-a-feature) endpoint passing in the ID's of the Project, Feature, and Variable. The function then outputs the request and response to the screen:\n\n```python\ndef update_feature(id, variable_id):\n    url = base_url + '/features/' + str(id)\n\n    feature = {\n        \"key\": key + '_updated',\n        \"project_id\": project_id,\n        \"archived\": False,\n        \"description\": description + '_updated',\n        \"environments\": {\n            \"Production\": {\n                \"rollout_rules\": [\n                    {\n                        \"audience_ids\": [\n                            audience_id\n                        ],\n                        \"enabled\": True,\n                        \"percentage_included\": 1000\n                    }\n                ]\n            }\n        },\n        \"variables\": [\n            {\n                \"id\": variable_id,\n                \"default_value\": \"true\",\n                \"key\": variable_key + '_updated',\n                \"type\": \"string\",\n                \"archived\": True,\n                \"description\": variable_description + '_updated'\n            }\n        ]\n    }\n    et.print_request_details('PATCH', url, feature, headers)\n    r = requests.patch(url, data=json.dumps(feature), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return j['id']\n```\n\nThe script then invokes these two functions:\n```python\net.print_new_method('Create a new feature')\n(id, variable_id) = create_feature()\n\net.print_new_method('Update the feature')\nupdate_feature(id, variable_id)\n```\n\n\n\n### Create and Run an Experiment\nCreating an Experiment using **[experiment.py](./experiment.py)** is where everything from the previous steps comes together. An Experiment represents the tests to perform on your website and is the main purpose of the Optimizely platform. An Experiment contains one or *variations* describing different tests to run and can optionally contain one or more Features. \n\nFor this quickstart, you will use **[experiment.py](./experiment.py)** to create and run a single Experiment.\n\nThe script starts by importing the token and base URL followed by the Project ID, Event ID, Feature ID, and Feature variables that were created by the previous scripts, and manually added to **[.settings.yaml](./.settings.yaml)**. It stores these values in `project_id`, `event_id`, `feature_id`, and `variable_values`:\n\n```python\n...\nproject_id = ('project_id' in settings and settings['project_id']) or int(\n    raw_input(\"Please provide a Project ID: \"))\nevent_id = ('event_id' in settings and settings['event_id']) or int(\n    raw_input(\"Please provide an Event ID: \"))\nfeature_id = ('feature_id' in settings and settings['feature_id']) or raw_input(\n    \"Please provide an Feature ID (leave empty to create an experiment without a feature): \")\nvariable_values = ('variable_values' in settings and settings[\n                   'variable_values'])\n...\n```\n\nExperiments can run without features and variables, so for simplicity, `feature_id` and `variable_values:` are commented out in **[.settings.yaml](./.settings.yaml)**. **[experiment.py](./experiment.py)** checks for the presence of a Feature ID and if not found, gives the user the option to enter one or or more variables at runtime:\n\n```pythong\nhas_features = False\n\nif feature_id == '':\n    feature_id = None\nelse:\n    feature_id = int(feature_id)\n\nif feature_id and not variable_values:\n    variable_values = []\n    while True:\n        key = raw_input(\n            \"Please provide a variable key (or hit enter without a value to stop adding): \")\n        if key == '':\n            break\n        else:\n            value = raw_input(\"Please provide a variable value: \")\n        variable_values.append({\n            key: value\n        })\n```\n\nIf a Feature ID was specified, then the code adds the feature's variable(s) to its `variables` dictionary:\n\n```python\nvariables = {}\nif feature_id: \n    for variable in variable_values:\n        for key in variable:\n            variables[key] = variable[key]\n```\n\n\nThe script then generates a new random number that is used to create a unique Experiment key, description, and Variation key:\n\n```python\nmake_string_unique = str(random.randrange(0, 10000000000001, 2))\n\n# Variables to modify\nkey = 'fs_exp_' + make_string_unique\ndescription = 'This is s a fs exp description' + make_string_unique\nvariation_key = 'var_name_' + make_string_unique\n```\n\nThe script defines the following helper methods: \n* `create_experiment()`: Creates a new Experiment by invoking the [Create an Experiment](https://developers.optimizely.com/x/rest/v2/#create-an-experiment) endpoint and passing in the Project ID generated above. The function then outputs the request and response to the screen and returns the ID of the new Experiment and that of the Variation added:\n\n```python\ndef create_experiment():\n    url = base_url + '/experiments'\n\n    experiment = {\n        'project_id': project_id,\n        'type': 'a/b',\n        'description': description,\n        'holdback': 5000,\n        'archived': False,\n        'key': key,\n        \"metrics\": [\n            {\n                \"aggregator\": \"unique\",\n                \"event_id\": event_id,\n                \"scope\": \"visitor\",\n                \"winning_direction\": \"increasing\"\n            }\n        ],\n        \"multivariate_traffic_policy\": \"full_factorial\",\n        \"variations\": [\n            {\n                \"weight\": 10000,\n                \"archived\": False,\n                \"description\": \"blabla\",\n                \"key\": variation_key\n            }\n        ]\n    }\n\n    if feature_id:\n        experiment['feature_id'] = feature_id\n        if len(variable_values) \u003e 0:\n            experiment['variations'][0]['variable_values'] = variables\n\n    et.print_request_details('POST', url, experiment, headers)\n    r = requests.post(url, data=json.dumps(experiment), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n\n    return (j['id'], j['variations'][0]['variation_id'])\n```\n\n* `update_start_experiment()`: Updates the Experiment's description and Variation key that were created above by appending `_updated` to their key and descriptions and invoking the [Update an Experiment](https://developers.optimizely.com/x/rest/v2/#update-an-experiment) endpoint passing in the ID of the Project. The function also starts execution of the Experiment by appending `?action=start` to the endpoint's URL and checks if any features are available to add as variations. Then function then outputs the request and response to the screen:\n\n```python\ndef update_start_experiment(id, variation_id):\n    url = base_url + '/experiments/' + str(id) + '?action=start'\n\n    experiment = {\n        'project_id': project_id,\n        'type': 'a/b',\n        'description': description + \"_updated\",\n        'holdback': 5000,\n        'archived': False,\n        'key': key + \"_updated\",\n        \"metrics\": [\n            {\n                \"aggregator\": \"unique\",\n                \"event_id\": event_id,\n                \"scope\": \"visitor\",\n                \"winning_direction\": \"increasing\"\n            }\n        ],\n        \"multivariate_traffic_policy\": \"full_factorial\",\n        \"variations\": [\n            {\n                \"weight\": 10000,\n                \"archived\": False,\n                \"description\": \"blabla\",\n                \"key\": variation_key + '_updated',\n                \"variation_id\": variation_id\n            }\n        ]\n    }\n\n    if feature_id:\n        experiment['feature_id'] = feature_id\n        if len(variable_values) \u003e 0:\n            experiment['variations'][0]['variable_values'] = variables\n            experiment['variations'][0]['feature_enabled'] = True\n\n    et.print_request_details('PATCH', url, experiment, headers)\n    r = requests.patch(url, data=json.dumps(experiment), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n```\n\n* `pause_experiment()`: Pauses the execution of the Experiment so that it can be updated by a subsequent endpoint. The function includes the Experiment ID in the URL and appends `?action=pause` before re-invoking the [Update an Experiment](https://developers.optimizely.com/x/rest/v2/#update-an-experiment) endpoint:\n\n```python\ndef pause_experiment(id):\n    url = base_url + '/experiments/' + str(id) + '?action=pause'\n\n    experiment = {}\n\n    et.print_request_details('PATCH', url, experiment, headers)\n    r = requests.patch(url, data=json.dumps(experiment), headers=headers)\n    j = r.json()\n    et.print_response_details(r.status_code, j)\n    return r.json()\n```\n\n* `update_experiment_2()`: Demonstrates how to update a paused Experiment using the [Update an Experiment](https://developers.optimizely.com/x/rest/v2/#update-an-experiment) endpoint.\n\n### Get the Results of the Experiment\nAfter you have run the Experiment, you can retrieve its results. \n\nFor this quickstart, **[results.py](./results.py)** gets the results, a timeseries, stores the results as CSV data, and displays them to the screen.\n\nThe script starts by importing the token and base URL, followed by the Experiment ID that was created by **[experiment.py](./experiment.py)** and manually added to **[.settings.yaml](./.settings.yaml)**. It stores this value in `experiment_id`:\n\n```python\n...\nexperiment_id = ('experiment_id' in settings and settings['experiment_id']) or int(\n    raw_input(\"Please provide an Experiment ID: \"))\n...\n```\n\nThe script then defines one helper method: \n* `create_result()`: Retrieves results using the [Get Experiment Results](https://developers.optimizely.com/x/rest/v2/#get-experiment-results) endpoint, a time series using the [Get Experiment results time series](https://developers.optimizely.com/x/rest/v2/#get-experiment-results-time-series) endpoint, and the results as CSV data using the [Get Experiment results as a CSV](https://developers.optimizely.com/x/rest/v2/#get-experiment-results-as-a-csv) endpoint. The functions then output the requests and responses to the screen to display these results.\n\nThe script then invokes this function:\n```python\net.print_new_method('Get experiment results')\ncreate_result()\n```\n\nHere is a portion of the results that are displayed on the screen:\n\n```\nGet experiment results\nRequest:\nGET https://api.optimizely.com/v2/experiments/10834392320/results\nheaders: \n{\n    \"Authorization\": \"Bearer 2:Abc21231234....\", \n    \"user-agent\": \"application/json\"\n}\nbody:\n{}\n{  \n   \"confidence_threshold\":0.9,\n   \"end_time\":\"2018-06-08T16:17:47.000000Z\",\n   \"experiment_id\":10834392320,\n   \"metrics\":[  \n      {  \n         \"aggregator\":\"unique\",\n         \"event_id\":123456789,\n         \"name\":\"FS event_updated\",\n         \"results\":{  \n            \"10831751478\":{  \n               \"is_baseline\":true,\n               \"level\":\"variation\",\n               \"samples\":0,\n               \"value\":0,\n               \"variation_id\":\"987654321\"\n            }\n         },\n         \"scope\":\"visitor\",\n         \"winning_direction\":\"increasing\"\n      }\n   ],\n   \"reach\":{  \n      \"baseline_count\":0,\n      \"baseline_reach\":0.0,\n      \"total_count\":0,\n      \"treatment_count\":0,\n      \"treatment_reach\":0.0,\n      \"variations\":{  \n         \"10831751478\":{  \n            \"count\":0,\n            \"variation_id\":\"987654321\",\n            \"variation_reach\":0.0\n         }\n      }\n   },\n   \"start_time\":\"2018-06-08T16:17:43.000000Z\",\n   \"stats_config\":{  \n      \"confidence_level\":0.9,\n      \"difference_type\":\"relative\",\n      \"epoch_enabled\":false\n   }\n}\n\nResponse:\n200\nbody:\n{\n    \"confidence_threshold\": 0.9, \n    \"end_time\": \"2018-06-08T16:17:47.000000Z\", \n    \"experiment_id\": 10834392320, \n    \"metrics\": [\n        {\n            \"aggregator\": \"unique\", \n            \"event_id\": 123456789, \n            \"name\": \"FS event_updated\", \n            \"results\": {\n                \"10831751478\": {\n                    \"is_baseline\": true, \n                    \"level\": \"variation\", \n                    \"samples\": 0, \n                    \"value\": 0, \n                    \"variation_id\": \"987654321\"\n                }\n            }, \n            \"scope\": \"visitor\", \n            \"winning_direction\": \"increasing\"\n        }\n    ], \n    \"reach\": {\n        \"baseline_count\": 0, \n        \"baseline_reach\": 0.0, \n        \"total_count\": 0, \n        \"treatment_count\": 0, \n        \"treatment_reach\": 0.0, \n        \"variations\": {\n            \"10831751478\": {\n                \"count\": 0, \n                \"variation_id\": \"987654321\", \n                \"variation_reach\": 0.0\n            }\n        }\n    }, \n    \"start_time\": \"2018-06-08T16:17:43.000000Z\", \n    \"stats_config\": {\n        \"confidence_level\": 0.9, \n        \"difference_type\": \"relative\", \n        \"epoch_enabled\": false\n    }\n}\n...\n```\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foptimizely%2Ffs_rest_customer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foptimizely%2Ffs_rest_customer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foptimizely%2Ffs_rest_customer/lists"}