API Documentation
The Experiment Flow API lets you run A/B tests and multi-armed bandits programmatically.
Authentication
All API requests require authentication. Include your API key in one of these ways:
X-API-Key: YOUR_API_KEY
Authorization: Bearer YOUR_API_KEY
?api_key=YOUR_API_KEY
Quick Start
Create an experiment and start tracking in 3 steps:
# 1. Create experiment
curl -X POST https://experimentflow.com/api/experiments \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "CTA Test", "variants": ["control", "variant_a"]}'
# 2. Get variant for visitor
curl "https://experimentflow.com/api/decide?experiment_id=1&visitor_id=user123&api_key=YOUR_API_KEY"
# 3. Track conversion
curl -X POST https://experimentflow.com/api/convert \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"experiment_id": 1, "visitor_id": "user123"}'
Storage & Privacy
Experiment Flow is designed for privacy compliance. Here's how data is stored:
Client-Side (JavaScript SDK)
| Storage | Key | Purpose | Duration |
|---|---|---|---|
| localStorage | experimentflow_visitor_id | Anonymous visitor ID for consistent variant assignment | Persistent |
No cookies are used for tracking. The SDK uses localStorage only, making it GDPR/CCPA compliant without cookie consent banners for A/B testing.
Dashboard Authentication
| Cookie | Purpose | Type | Duration |
|---|---|---|---|
optimize_session | Dashboard login session | HTTP-only, Secure, SameSite=Lax | Session or 30 days (remember me) |
This cookie is only set when you log into the dashboard. It's not used for visitor tracking.
Backend SDK
When using the Backend SDK, you manage user IDs yourself. No cookies or localStorage are involved - you pass the user_id directly in API calls.
JavaScript SDK Installation
Add the SDK to your website with a single script tag:
<script src="https://experimentflow.com/sdk.js"
data-optimize-key="YOUR_API_KEY"></script>
Or initialize manually for more control:
<script src="https://experimentflow.com/sdk.js"></script>
<script>
var ef = new ExperimentFlow({
apiKey: 'YOUR_API_KEY',
debug: true // Enable console logging
});
</script>
JavaScript SDK Usage
Get Variant
// Get variant for an experiment
ef.getVariant(1, function(variant) {
if (variant === 'control') {
// Show original
} else if (variant === 'variant_a') {
// Show variation
}
});
// Or use the helper
ef.onVariant(1, {
'control': function() { /* original */ },
'variant_a': function() { document.querySelector('.cta').textContent = 'Buy Now!'; }
});
Track Conversion
// Track conversion for specific experiment
ef.convert(1);
// Track with value (e.g., order amount)
ef.convert(1, 49.99);
// Track with goal name
ef.convert(1, 49.99, 'purchase');
// Convert all active experiments
ef.convertAll(49.99, 'purchase');
DOM Modifications
// Automatically modify DOM based on variant
ef.activate(1, {
'control': {},
'variant_a': {
'.hero-title': { text: 'New Headline!' },
'.cta-button': { style: { backgroundColor: 'green' } },
'.old-section': { hide: true }
}
});
Set User ID (Cross-Device)
// After user logs in
ef.setUserId('user_12345');
// Now experiments sync across devices
Shopify Integration
The SDK includes built-in Shopify support with automatic event tracking:
// Initialize with Shopify support
ef.initShopify();
// Automatically tracks:
// - Add to cart (with product value)
// - Checkout start
// - Page context (product type, collection, etc.)
// Manual tracking on thank you page
ef.trackPurchase({ total_price: 9999 }); // Price in cents
// Test add-to-cart button variants
ef.testAddToCart(1, {
'control': { text: 'Add to Cart' },
'variant_a': { text: 'Buy Now - Free Shipping!', style: { backgroundColor: '#00aa00' } }
});
Backend SDK Overview
Run A/B tests entirely on your server without any client-side code. Perfect for:
- API response variations
- Pricing experiments
- Algorithm testing
- Email subject lines
- Feature flags
The Backend SDK uses your user_id for consistent assignment. No cookies or localStorage needed.
Python Example
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://experimentflow.com"
def get_variant(experiment_key: str, user_id: str) -> str:
"""Get variant assignment for a user."""
resp = requests.post(
BASE_URL + "/api/backend/decide",
headers={"X-API-Key": API_KEY},
json={"experiment_key": experiment_key, "user_id": user_id}
)
return resp.json().get("variant", "control")
def track_conversion(experiment_key: str, user_id: str, revenue: float = None):
"""Track a conversion event."""
requests.post(
BASE_URL + "/api/backend/convert",
headers={"X-API-Key": API_KEY},
json={
"experiment_key": experiment_key,
"user_id": user_id,
"revenue": revenue
}
)
# Usage in your app
@app.route("/checkout")
def checkout():
user_id = get_current_user_id()
variant = get_variant("checkout_flow", user_id)
if variant == "new_checkout":
return render_template("checkout_v2.html")
return render_template("checkout.html")
@app.route("/purchase", methods=["POST"])
def purchase():
user_id = get_current_user_id()
order_total = calculate_order_total()
track_conversion("checkout_flow", user_id, revenue=order_total)
return jsonify({"status": "ok"})
Node.js Example
const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://experimentflow.com';
async function getVariant(experimentKey, userId) {
const resp = await fetch(BASE_URL + '/api/backend/decide', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY
},
body: JSON.stringify({ experiment_key: experimentKey, user_id: userId })
});
const data = await resp.json();
return data.variant || 'control';
}
async function trackConversion(experimentKey, userId, revenue) {
await fetch(BASE_URL + '/api/backend/convert', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY
},
body: JSON.stringify({
experiment_key: experimentKey,
user_id: userId,
revenue: revenue
})
});
}
// Express.js example
app.get('/api/pricing', async (req, res) => {
const userId = req.user.id;
const variant = await getVariant('pricing_test', userId);
const prices = {
control: { monthly: 29, annual: 290 },
annual_first: { monthly: 29, annual: 249 }
};
res.json(prices[variant] || prices.control);
});
app.post('/api/subscribe', async (req, res) => {
const { plan, amount } = req.body;
await trackConversion('pricing_test', req.user.id, amount);
// ... process subscription
});
Go Example
package main
import (
"bytes"
"encoding/json"
"net/http"
)
const apiKey = "YOUR_API_KEY"
const baseURL = "https://experimentflow.com"
type DecideResponse struct {
Variant string
}
func getVariant(experimentKey, userID string) (string, error) {
body, _ := json.Marshal(map[string]string{
"experiment_key": experimentKey,
"user_id": userID,
})
req, _ := http.NewRequest("POST", baseURL+"/api/backend/decide", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "control", err
}
defer resp.Body.Close()
var result DecideResponse
json.NewDecoder(resp.Body).Decode(&result)
return result.Variant, nil
}
func trackConversion(experimentKey, userID string, revenue float64) error {
body, _ := json.Marshal(map[string]interface{}{
"experiment_key": experimentKey,
"user_id": userID,
"revenue": revenue,
})
req, _ := http.NewRequest("POST", baseURL+"/api/backend/convert", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", apiKey)
_, err := http.DefaultClient.Do(req)
return err
}
// Usage
func handleCheckout(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
variant, _ := getVariant("checkout_flow", userID)
switch variant {
case "streamlined":
renderTemplate(w, "checkout_v2.html")
default:
renderTemplate(w, "checkout.html")
}
}
Get Current User
Get the current authenticated user's information including team details and plan.
{
"id": 1,
"email": "[email protected]",
"team_id": 1,
"team_name": "My Team",
"role": "owner",
"plan": "pro"
}
Try it
Regenerate API Key
Generate a new API key. The old key will immediately stop working.
{
"api_key": "opt_new_key_here"
}
Try it
List Experiments
List all experiments for your team.
| Parameter | Type | Description |
|---|---|---|
| archived | string | Set to "1" to list archived experiments |
Try it
Create Experiment
Create a new A/B test experiment.
| Field | Type | Description |
|---|---|---|
| name required | string | Experiment name |
| variants required | array | List of variant names (min 2) |
| description | string | Optional description |
| traffic_percent | number | % of traffic to include (default: 100) |
| auto_promote | boolean | Auto-promote winner when significant |
| auto_promote_threshold | number | Confidence threshold (default: 0.95) |
Try it
Get Experiment Statistics
Get detailed statistics including conversion rates, statistical significance, and winner.
{
"experiment_id": 1,
"experiment_name": "CTA Test",
"status": "running",
"total_visitors": 1000,
"total_conversions": 120,
"variants": [
{"variant": "control", "visitors": 500, "conversions": 50, "conversion_rate": 0.10},
{"variant": "variant_a", "visitors": 500, "conversions": 70, "conversion_rate": 0.14}
],
"significance": {
"is_significant": true,
"confidence": 95.2,
"p_value": 0.048,
"z_score": 1.98
},
"winner": "variant_a",
"improvement": 40.0
}
Try it
Promote Winner
Manually promote a variant as the winner and end the experiment.
Try it
Archive Experiment
Archive an experiment. Archived experiments are hidden from the main list but data is preserved.
Try it
Delete Experiment
Permanently delete an experiment and all associated data. This action cannot be undone.
Try it
Get Variant Assignment
Get variant assignment for a visitor. Automatically tracks exposure.
| Field | Type | Description |
|---|---|---|
| experiment_id required | integer | Experiment ID |
| visitor_id required | string | Unique visitor identifier |
| user_id | string | Optional user ID for cross-device tracking |
| context | object | Optional context/features for targeting |
{
"variant": "variant_a",
"visitor_id": "user_abc123",
"in_test": true
}
Try it
Batch Decide (Multiple Experiments)
Get variant assignments for multiple experiments in a single request. Ideal for pages with multiple active tests.
| Field | Type | Description |
|---|---|---|
| experiment_ids required | array | Array of experiment IDs |
| visitor_id required | string | Unique visitor identifier |
| user_id | string | Optional user ID for cross-device tracking |
| context | object | Optional context/features for targeting |
{
"variants": {
"1": "control",
"2": "variant_a",
"3": "variant_b"
},
"visitor_id": "user_abc123"
}
Try it
Track Conversion
Record a conversion event for a visitor.
| Field | Type | Description |
|---|---|---|
| experiment_id required | integer | Experiment ID |
| visitor_id required | string | Unique visitor identifier |
| value | number | Conversion value (default: 1) |
| goal_name | string | Optional goal name for multiple goals |
Try it
Track Custom Event
Track custom events for detailed analytics. Useful for tracking intermediate steps, engagement metrics, or custom goals.
| Field | Type | Description |
|---|---|---|
| experiment_id required | integer | Experiment ID |
| visitor_id required | string | Unique visitor identifier |
| variant required | string | The variant the visitor was assigned |
| event_type required | string | Type of event (e.g., "click", "scroll", "video_play") |
| value | number | Optional numeric value for the event |
| context | object | Optional additional context data |
Try it
Feature Flag (Backend SDK)
Feature flag style API for server-side experiments. Returns whether a feature is enabled and which variant to show. Ideal for backend A/B testing in Python, Go, Node.js, etc.
| Field | Type | Description |
|---|---|---|
| experiment_key | string | Experiment name (alternative to experiment_id) |
| experiment_id | integer | Experiment ID |
| user_id required | string | Unique user identifier |
| attributes | object | User attributes for targeting |
{
"enabled": true,
"variant": "new_checkout",
"experiment_id": 1,
"in_test": true
}
Try it
Backend Decide
Server-side variant assignment for backend experiments. Use experiment_key (name) or experiment_id to identify experiments.
| Field | Type | Description |
|---|---|---|
| experiment_key | string | Experiment name (alternative to experiment_id) |
| experiment_id | integer | Experiment ID |
| user_id required | string | Unique user identifier |
| attributes | object | User attributes for targeting |
{
"variant": "treatment_a",
"experiment_id": 1,
"user_id": "user_12345",
"in_test": true
}
Try it
Backend Batch Decide
Get variant assignments for multiple experiments in a single request. Supports both experiment keys (names) and IDs.
| Field | Type | Description |
|---|---|---|
| experiment_keys | array | List of experiment names |
| experiment_ids | array | List of experiment IDs |
| user_id required | string | Unique user identifier |
| attributes | object | User attributes for targeting |
{
"variants": {
"checkout_flow": {"variant": "new_checkout", "in_test": true, "experiment_id": 1},
"pricing_test": {"variant": "annual_first", "in_test": true, "experiment_id": 2}
},
"user_id": "user_12345"
}
Try it
Backend Convert
Track a conversion from your backend. Automatically looks up the user's variant assignment.
| Field | Type | Description |
|---|---|---|
| experiment_key | string | Experiment name (alternative to experiment_id) |
| experiment_id | integer | Experiment ID |
| user_id required | string | User who converted |
| goal_name | string | Optional goal name (e.g., "purchase", "signup") |
| value | number | Conversion value (default: 1) |
| revenue | number | Revenue amount (takes precedence over value) |
{
"status": "ok",
"experiment_id": 1,
"user_id": "user_12345",
"variant": "treatment_a",
"value": 99.99
}
Try it
Experiment Status
Get detailed experiment status including visitors, conversions, conversion rate, and statistical significance.
{
"id": 1,
"name": "Checkout Flow",
"status": "running",
"variants": ["control", "new_checkout"],
"total_visitors": 1500,
"total_conversions": 180,
"conversion_rate": 0.12,
"is_significant": true,
"confidence": 95.2,
"traffic_percent": 100,
"auto_promote": true,
"auto_promote_threshold": 0.95
}
Try it
Experiment by Key
Look up an experiment by its name (key). Useful when you don't know the experiment ID.
{
"id": 1,
"name": "checkout_flow",
"status": "running",
"variants": ["control", "new_checkout"],
"winner": ""
}
Try it
List Bandits
List all multi-armed bandits for your team.
Try it
Create Bandit
Create a new multi-armed bandit for automatic traffic optimization.
| Field | Type | Description |
|---|---|---|
| name required | string | Bandit name |
| arms required | array | List of arm names (min 2) |
| algorithm | string | Algorithm: "thompson" (default), "ucb1", or "epsilon" |
Try it
Select Bandit Arm
Select the best arm using Thompson Sampling. Returns the arm to show and records the selection.
Try it
Record Bandit Reward
Record a reward (conversion) for a bandit arm selection.
Try it
Get Team
Get team details and members.
Try it
Invite Team Member
Invite a new member to your team (owner/admin only).
Try it
List Personalizers
List all personalizers for your team. Personalizers use neural networks for real-time action ranking.
Try it
Create Personalizer
Create a new personalizer for AI-powered action ranking.
| Field | Type | Description |
|---|---|---|
| name required | string | Personalizer name |
| exploration_rate | number | Exploration rate 0-1 (default: 0.2) |
| learning_rate | number | Learning rate (default: 0.01) |
Try it
Rank Actions
Rank a set of actions for a user based on context. Returns actions sorted by predicted reward. Costs $0.01 per call.
| Field | Type | Description |
|---|---|---|
| visitor_id required | string | Unique visitor identifier |
| context required | object | Context features (e.g., user attributes, page info) |
| actions required | array | List of action objects with features |
{
"ranked_actions": [
{"id": "product_3", "score": 0.85},
{"id": "product_1", "score": 0.72},
{"id": "product_2", "score": 0.45}
],
"event_id": "evt_abc123"
}
Try it
Record Personalizer Reward
Record a reward for a previous rank call. Used to train the model.
| Field | Type | Description |
|---|---|---|
| event_id required | string | Event ID from the rank response |
| reward required | number | Reward value (0-1 recommended) |
Try it
Get Credit Balance
Get current credit balance for personalization API calls.
{
"balance": 50.00,
"currency": "USD"
}
Try it
Top Up Credits
Purchase additional credits for personalization API calls.
| Field | Type | Description |
|---|---|---|
| amount required | number | Amount in USD ($5, $20, $50, $100, $500) |
| success_url | string | Redirect URL after successful payment |
| cancel_url | string | Redirect URL if payment cancelled |
Try it
Get Subscription
Get current subscription details and usage.
{
"plan": "pro",
"seats": 3,
"experiments_used": 5,
"experiments_limit": 0,
"events_used": 15000,
"events_limit": 1000000
}
Try it
Start Checkout
Start a Stripe checkout session to upgrade to Pro plan.
| Field | Type | Description |
|---|---|---|
| seats | number | Number of seats (default: 1) |
| embedded | boolean | Use embedded checkout (default: false) |
| billing_period | string | "monthly" ($29/mo) or "yearly" ($290/year, save 17%) |
{
"checkout_url": "https://checkout.stripe.com/...",
"client_secret": "cs_..."
}