Implement Server-Side Testing with the REST API
What you will build
Section titled “What you will build”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.
Before you start
Section titled “Before you start”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
Step 1: Authenticate
Section titled “Step 1: Authenticate”Every request requires a Bearer token in the Authorization header.
# Test authenticationcurl -s -H "Authorization: Bearer YOUR_API_TOKEN" \ https://api.optimizely.com/v2/projects | head -c 200A successful response returns a JSON array of your projects. If you receive a 401 error, verify your token is correct and has not expired.
Step 2: List your projects
Section titled “Step 2: List your projects”Identify the project where you will create the experiment.
curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
https://api.optimizely.com/v2/projects | python3 -m json.tool 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.
Step 3: Create an experiment
Section titled “Step 3: Create an experiment”Create a new A/B test experiment in your project.
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 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.
Step 4: Add a variation
Section titled “Step 4: Add a variation”Create a variation with custom JavaScript changes.
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 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.
Step 5: Create a custom event
Section titled “Step 5: Create a custom event”Define the event you will use as a metric.
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.
Step 6: Add a metric to the experiment
Section titled “Step 6: Add a metric to the experiment”Attach the event as the experiment’s primary metric.
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).
Step 7: Configure audience targeting
Section titled “Step 7: Configure audience targeting”Add audience conditions to target specific visitors.
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".
Step 8: Start the experiment
Section titled “Step 8: Start the experiment”Change the experiment status from not_started to running.
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 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.
Step 9: Fetch results
Section titled “Step 9: Fetch results”Retrieve experiment results programmatically. Wait at least 24 hours after launch for meaningful data.
curl -s -H "Authorization: Bearer YOUR_API_TOKEN" \
https://api.optimizely.com/v2/experiments/EXPERIMENT_ID/results | python3 -m json.tool 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.
Step 10: Stop or archive the experiment
Section titled “Step 10: Stop or archive the experiment”When the experiment reaches significance or you have enough data:
# 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.
What to do next
Section titled “What to do next”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