Skip to content

Implement Server-Side Testing with the REST API

⏱ 45 minutes advanced

By the end of this tutorial, you will have:

  • Authenticated with the Optimizely REST API
  • Created an experiment programmatically
  • Added variations with custom code changes
  • Configured metrics and audience targeting
  • Started the experiment via API
  • Fetched and parsed results programmatically

This approach is ideal for teams that manage experimentation through CI/CD pipelines, infrastructure-as-code workflows, or custom dashboards.

Ensure you have:

  • An API access token — Navigate to Account Settings > API Access in Optimizely and generate a personal access token
  • Your project ID — Find it in Settings > General in the Optimizely application
  • curl or a similar HTTP client — All examples use curl and can be adapted to any language

Base URL: All API requests go to https://api.optimizely.com/v2

Every request requires a Bearer token in the Authorization header.

Terminal window
# Test authentication
curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
https://api.optimizely.com/v2/projects | head -c 200

A successful response returns a JSON array of your projects. If you receive a 401 error, verify your token is correct and has not expired.

Identify the project where you will create the experiment.

List projects
bash
curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.optimizely.com/v2/projects | python3 -m json.tool
javascript
const response = await fetch('https://api.optimizely.com/v2/projects', {
  headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' },
});

const projects = await response.json();
projects.forEach(p => console.log(`${p.id}: ${p.name}`));

Note the id of the project you want to use. You will need it in subsequent requests.

Create a new A/B test experiment in your project.

Create an experiment
bash
curl -s -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": YOUR_PROJECT_ID,
    "type": "a/b",
    "name": "API-created checkout CTA test",
    "description": "Testing a new CTA on the checkout page via API",
    "url_targeting": {
      "edit_url": "https://www.example.com/checkout",
      "conditions": "[\"and\", [\"or\", {\"type\": \"url\", \"value\": \"https://www.example.com/checkout\", \"match_type\": \"simple\"}]]"
    }
  }' \
  https://api.optimizely.com/v2/experiments
javascript
const response = await fetch('https://api.optimizely.com/v2/experiments', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    project_id: YOUR_PROJECT_ID,
    type: 'a/b',
    name: 'API-created checkout CTA test',
    description: 'Testing a new CTA on the checkout page via API',
    url_targeting: {
      edit_url: 'https://www.example.com/checkout',
      conditions: '["and", ["or", {"type": "url", "value": "https://www.example.com/checkout", "match_type": "simple"}]]',
    },
  }),
});

const experiment = await response.json();
console.log('Experiment ID:', experiment.id);

Save the id from the response — you will use it in all subsequent steps.

Create a variation with custom JavaScript changes.

Create a variation
bash
curl -s -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Green CTA with urgency text",
    "weight": 5000,
    "actions": [{
      "page_id": YOUR_PAGE_ID,
      "changes": [{
        "type": "custom_code",
        "value": "var utils = window.optimizely.get('utils');\nutils.waitForElement('.checkout-cta').then(function(el) {\n  el.style.backgroundColor = '#2ecc71';\n  el.textContent = 'Complete Purchase Now';\n});"
      }]
    }]
  }' \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID/variations
javascript
const variationCode = `
var utils = window.optimizely.get('utils');
utils.waitForElement('.checkout-cta').then(function(el) {
  el.style.backgroundColor = '#2ecc71';
  el.textContent = 'Complete Purchase Now';
});
`;

const response = await fetch(
  `https://api.optimizely.com/v2/experiments/${experimentId}/variations`,
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      name: 'Green CTA with urgency text',
      weight: 5000,
      actions: [{
        page_id: pageId,
        changes: [{
          type: 'custom_code',
          value: variationCode,
        }],
      }],
    }),
  }
);

const variation = await response.json();
console.log('Variation ID:', variation.variation_id);

The weight field controls traffic allocation in basis points — 5000 equals 50%. The original (control) receives the remaining traffic.

Define the event you will use as a metric.

Create an event
bash
curl -s -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": YOUR_PROJECT_ID,
    "name": "Checkout Completed",
    "key": "checkout_completed",
    "event_type": "custom"
  }' \
  https://api.optimizely.com/v2/events

Save the event id from the response.

Attach the event as the experiment’s primary metric.

Add a metric
bash
curl -s -X PATCH \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "metrics": [{
      "event_id": YOUR_EVENT_ID,
      "aggregator": "unique",
      "field": "revenue",
      "scope": "visitor",
      "winning_direction": "increasing"
    }]
  }' \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID

Set winning_direction to increasing when a higher value is better (e.g., conversions, revenue) or decreasing when a lower value is better (e.g., bounce rate, load time).

Add audience conditions to target specific visitors.

Set audience conditions
bash
curl -s -X PATCH \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "audience_conditions": "[\"and\", {\"audience_id\": YOUR_AUDIENCE_ID}]"
  }' \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID

To target all visitors, omit the audience_conditions field or set it to "everyone".

Change the experiment status from not_started to running.

Start the experiment
bash
curl -s -X PATCH \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "running" }' \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID
javascript
const response = await fetch(
  `https://api.optimizely.com/v2/experiments/${experimentId}`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': 'Bearer YOUR_API_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ status: 'running' }),
  }
);

const updated = await response.json();
console.log('Status:', updated.status);

The experiment is now live. Visitors matching the targeting conditions are bucketed into variations.

Retrieve experiment results programmatically. Wait at least 24 hours after launch for meaningful data.

Get experiment results
bash
curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID/results | python3 -m json.tool
javascript
const response = await fetch(
  `https://api.optimizely.com/v2/experiments/${experimentId}/results`,
  {
    headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' },
  }
);

const results = await response.json();

results.metrics.forEach(metric => {
  console.log(`Metric: ${metric.name}`);
  metric.results.forEach(r => {
    console.log(`  Variation: ${r.name}`);
    console.log(`  Lift: ${(r.lift.value * 100).toFixed(2)}%`);
    console.log(`  Significance: ${r.is_significant ? 'Yes' : 'No'}`);
    console.log(`  Confidence interval: [${(r.lift.confidence_interval[0] * 100).toFixed(2)}%, ${(r.lift.confidence_interval[1] * 100).toFixed(2)}%]`);
  });
});

The results object contains lift values, significance indicators, confidence intervals, and visitor counts for each variation and metric.

When the experiment reaches significance or you have enough data:

Stop the experiment
bash
# Pause the experiment
curl -s -X PATCH \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "paused" }' \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID

# Archive when analysis is complete
curl -s -X PATCH \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "status": "archived" }' \
  https://api.optimizely.com/v2/experiments/EXPERIMENT_ID

Archiving preserves the experiment data but removes it from active views.

You have created a full experiment lifecycle through the API. Here are ways to build on this:

  • Automate experiment creation — Build templates that create standardized experiments from a CI/CD pipeline
  • Build a custom dashboard — Fetch results from multiple experiments and visualize them in your own reporting tool
  • Integrate with feature flags — Combine the REST API with the Feature Experimentation SDK for full-stack experimentation
  • Implement experiment-as-code — Store experiment configurations in version control and deploy them through your release process