SDK Integration Guide

The platform provides official SDKs for Python and JavaScript to integrate experiment assignment and feature flag evaluation directly into your application.


Python SDK

Installation

pip install experimentation-sdk
# or from source:
pip install -e ./sdk/python

Quick Start

from experimentation import ExperimentationClient

client = ExperimentationClient(
    api_url="https://your-platform.example.com",
    api_key="your-api-key",
)

# Get experiment variant assignment
variant = client.get_variant(
    experiment_key="checkout-button-color",
    user_id="user-123",
    user_attributes={"country": "US", "plan": "pro"},
)
print(variant)  # "control" or "treatment"

# Check feature flag
is_enabled = client.is_feature_enabled(
    flag_key="dark-mode",
    user_id="user-123",
)

# Track a conversion event
client.track(
    user_id="user-123",
    event_type="checkout_completed",
    event_value=49.99,
    properties={"payment_method": "card"},
)

Client Configuration

client = ExperimentationClient(
    api_url="https://your-platform.example.com",
    api_key="your-api-key",
    timeout_seconds=2.0,         # Default: 5.0
    cache_ttl_seconds=60,        # Local assignment cache TTL. Default: 300
    default_variant="control",   # Fallback when API is unreachable
)

Experiments

# Get variant assignment (returns default_variant on error)
variant = client.get_variant("experiment-key", user_id="user-123")

# Get assignment with full metadata
assignment = client.get_assignment(
    experiment_key="experiment-key",
    user_id="user-123",
    user_attributes={"plan": "enterprise"},
)
print(assignment.variant_key)      # "treatment-a"
print(assignment.experiment_id)    # UUID
print(assignment.is_control)       # False

Feature Flags

# Boolean flag check
enabled = client.is_feature_enabled("flag-key", user_id="user-123")

# Get flag with targeting evaluation
flag = client.get_feature_flag(
    flag_key="new-checkout",
    user_id="user-123",
    user_attributes={"country": "US"},
)
print(flag.enabled)         # True / False
print(flag.rollout_pct)     # 0.5 (50% rollout)

Event Tracking

# Simple event
client.track("user-123", "page_view")

# Event with value and properties
client.track(
    user_id="user-123",
    event_type="purchase_completed",
    event_value=149.00,
    properties={
        "product_id": "prod-456",
        "currency": "USD",
    },
)

# Batch track (more efficient for high-volume scenarios)
client.track_batch([
    {"user_id": "user-1", "event_type": "click", "event_value": None},
    {"user_id": "user-2", "event_type": "click", "event_value": None},
])

JavaScript / TypeScript SDK

Installation

npm install @experimentation/sdk
# or from source:
npm install ./sdk/js

Quick Start

import { ExperimentationClient } from '@experimentation/sdk';

const client = new ExperimentationClient({
  apiUrl: 'https://your-platform.example.com',
  apiKey: 'your-api-key',
});

// Get variant assignment
const variant = await client.getVariant('checkout-button-color', {
  userId: 'user-123',
  attributes: { country: 'US', plan: 'pro' },
});
console.log(variant); // "control" or "treatment"

// Feature flag check
const isEnabled = await client.isFeatureEnabled('dark-mode', { userId: 'user-123' });

// Track event
await client.track('user-123', 'checkout_completed', { value: 49.99 });

React Integration

import { useExperiment, useFeatureFlag } from '@experimentation/sdk/react';

function CheckoutButton() {
  const { variant, loading } = useExperiment('checkout-cta', {
    userId: currentUser.id,
    attributes: { plan: currentUser.plan },
  });

  if (loading) return <DefaultButton />;

  return variant === 'treatment' ? <GreenButton /> : <DefaultButton />;
}

function SettingsPanel() {
  const darkMode = useFeatureFlag('dark-mode', { userId: currentUser.id });

  return <Panel theme={darkMode ? 'dark' : 'light'} />;
}

Client Configuration

const client = new ExperimentationClient({
  apiUrl: 'https://your-platform.example.com',
  apiKey: 'your-api-key',
  timeoutMs: 2000,          // Default: 5000
  cacheTtlSeconds: 60,      // Default: 300
  defaultVariant: 'control', // Fallback on error
});

Java SDK

Installation

Maven (pom.xml):

<dependency>
    <groupId>com.experimentation</groupId>
    <artifactId>experimentation-sdk</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle (build.gradle):

implementation 'com.experimentation:experimentation-sdk:1.0.0'

Quick Start

import com.experimentation.sdk.ExperimentationClient;
import com.experimentation.sdk.ExperimentationConfig;

ExperimentationClient client = new ExperimentationClient(
    ExperimentationConfig.builder()
        .apiUrl("https://your-platform.example.com")
        .apiKey("your-api-key")
        .build()
);

// Get experiment variant assignment
String variant = client.getVariant(
    "checkout-button-color",   // experimentKey
    "user-123",                // userId
    Map.of("country", "US", "plan", "pro")  // attributes
);
System.out.println(variant); // "control" or "treatment"

// Check feature flag
boolean isEnabled = client.isFeatureEnabled(
    "dark-mode",  // flagKey
    "user-123",   // userId
    Map.of()      // attributes
);

// Track a conversion event
client.trackEvent(
    "user-123",           // userId
    "checkout_completed", // eventKey
    49.99                 // value
);

// Close the client when done (releases connection pool)
client.close();

Client Configuration

ExperimentationClient client = new ExperimentationClient(
    ExperimentationConfig.builder()
        .apiUrl("https://your-platform.example.com")
        .apiKey("your-api-key")
        .timeoutSeconds(2)         // HTTP call timeout. Default: 5
        .cacheTtlSeconds(60)       // Local assignment cache TTL. Default: 300
        .cacheMaxSize(1000)        // Max entries in local cache. Default: 10000
        .defaultVariant("control") // Fallback when API is unreachable
        .build()
);

Experiment Variant Assignment

// Simple variant lookup (returns defaultVariant on error)
String variant = client.getVariant("experiment-key", "user-123", Map.of());

// With targeting attributes
String variant = client.getVariant(
    "premium-checkout",
    "user-123",
    Map.of(
        "country", "US",
        "plan", "enterprise",
        "accountAge", "365"
    )
);

// Full assignment metadata
Assignment assignment = client.getAssignment(
    "experiment-key",
    "user-123",
    Map.of("plan", "pro")
);
System.out.println(assignment.getVariantKey());    // "treatment-a"
System.out.println(assignment.getExperimentId());  // UUID string
System.out.println(assignment.isControl());        // false

Feature Flag Evaluation

// Boolean flag
boolean enabled = client.isFeatureEnabled("flag-key", "user-123", Map.of());

// With targeting attributes
boolean enabled = client.isFeatureEnabled(
    "new-checkout",
    "user-123",
    Map.of("country", "US", "segment", "beta")
);

Event Tracking

// Simple event (no value)
client.trackEvent("user-123", "page_view", null);

// Event with a numeric value
client.trackEvent("user-123", "purchase_completed", 149.00);

Consistent-Hash Bucketing

The Java SDK uses MD5-based consistent hashing for deterministic variant assignment. Given the same experimentKey and userId, getVariant() always returns the same variant without a network call when the assignment is already cached. The hash input is "{experimentKey}:{userId}" and bucket boundaries are derived from the experiment's traffic allocation configuration returned by the API. This guarantees that:

  • The same user always sees the same variant for a given experiment.
  • Variant assignment is stable across SDK restarts (within cache TTL).
  • No sticky-session infrastructure is required.

Spring Boot Auto-Configuration

Add the starter dependency to use Spring Boot auto-configuration:

Maven:

<dependency>
    <groupId>com.experimentation</groupId>
    <artifactId>experimentation-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Gradle:

implementation 'com.experimentation:experimentation-spring-boot-starter:1.0.0'

Enable the integration in your application class:

import com.experimentation.spring.EnableExperimentation;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableExperimentation
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

The ExperimentationClient bean is then available for injection:

import com.experimentation.sdk.ExperimentationClient;
import org.springframework.stereotype.Service;

@Service
public class CheckoutService {

    private final ExperimentationClient experimentationClient;

    public CheckoutService(ExperimentationClient experimentationClient) {
        this.experimentationClient = experimentationClient;
    }

    public String resolveCheckoutVariant(String userId, Map<String, String> attributes) {
        return experimentationClient.getVariant("checkout-flow", userId, attributes);
    }
}

Spring Boot Properties

Configure the SDK via application.properties or application.yml:

PropertyTypeDefaultDescription
experimentation.api-urlString(required)Base URL of the Experimentation Platform API
experimentation.api-keyString(required)API key for SDK authentication (X-API-Key header)
experimentation.cache-ttl-secondsint300Seconds before a cached assignment expires
experimentation.cache-max-sizeint10000Maximum number of entries held in the local LRU cache
experimentation.timeout-secondsint5HTTP request timeout in seconds
experimentation.default-variantString"control"Variant returned when the API is unreachable

Example application.yml:

experimentation:
  api-url: https://your-platform.example.com
  api-key: ${EXPERIMENTATION_API_KEY}
  cache-ttl-seconds: 120
  cache-max-size: 5000
  timeout-seconds: 2
  default-variant: control

React SDK

Installation

npm install @experimentation/react-sdk
# or
yarn add @experimentation/react-sdk

Quick Start — Provider Setup

Wrap your application (or the subtree that needs experimentation) with ExperimentationProvider:

import React from 'react';
import { ExperimentationProvider } from '@experimentation/react-sdk';

function App() {
  return (
    <ExperimentationProvider
      apiUrl="https://your-platform.example.com"
      apiKey="your-api-key"
      userId="user-123"
      userAttributes={{ country: 'US', plan: 'pro' }}
    >
      <YourApplication />
    </ExperimentationProvider>
  );
}

export default App;

Hooks Reference

useFeatureFlag(flagKey, defaultValue?)

Evaluates a single feature flag for the current user. Returns the flag's boolean state. Falls back to defaultValue (default: false) while loading or on error.

import { useFeatureFlag } from '@experimentation/react-sdk';

function SettingsPanel() {
  const darkMode = useFeatureFlag('dark-mode', false);

  return <Panel theme={darkMode ? 'dark' : 'light'} />;
}

useExperiment(experimentKey)

Returns the variant assignment and loading state for an experiment.

import { useExperiment } from '@experimentation/react-sdk';

function CheckoutButton() {
  const { variant, loading, error } = useExperiment('checkout-button-color');

  if (loading) return <DefaultButton />;
  if (error) return <DefaultButton />;

  return variant === 'green' ? <GreenButton /> : <DefaultButton />;
}

useTrackEvent()

Returns a trackEvent function for recording conversion events. The function is stable across renders.

import { useTrackEvent } from '@experimentation/react-sdk';

function PurchaseButton({ amount }: { amount: number }) {
  const trackEvent = useTrackEvent();

  const handleClick = async () => {
    await trackEvent('purchase_completed', amount);
  };

  return <button onClick={handleClick}>Buy Now</button>;
}

Signature:

trackEvent(eventKey: string, value?: number, properties?: Record<string, unknown>): Promise<void>

useVariant(experimentKey, variantKey)

Returns true if the current user is assigned to the specified variant of an experiment. Useful for conditional rendering without an explicit switch statement.

import { useVariant } from '@experimentation/react-sdk';

function HeroBanner() {
  const isNewHero = useVariant('hero-image-test', 'new-hero');

  return isNewHero ? <NewHeroBanner /> : <ClassicHeroBanner />;
}

useMultipleFlags(flagKeys)

Evaluates multiple feature flags in a single call. Returns a map of flagKey → boolean and a shared loading state, avoiding multiple round-trips.

import { useMultipleFlags } from '@experimentation/react-sdk';

function FeatureSuite() {
  const { flags, loading } = useMultipleFlags([
    'dark-mode',
    'new-checkout',
    'beta-dashboard',
  ]);

  if (loading) return <Spinner />;

  return (
    <div>
      {flags['dark-mode'] && <DarkModeToggle />}
      {flags['new-checkout'] && <NewCheckoutFlow />}
      {flags['beta-dashboard'] && <BetaDashboard />}
    </div>
  );
}

withExperimentation Higher-Order Component

Use the HOC to inject experimentation props into class components or when you prefer a HOC pattern over hooks:

import { withExperimentation } from '@experimentation/react-sdk';

interface OwnProps {
  productId: string;
}

interface InjectedProps {
  variant: string;
  isFeatureEnabled: (flagKey: string) => boolean;
  trackEvent: (eventKey: string, value?: number) => void;
}

type Props = OwnProps & InjectedProps;

class ProductCard extends React.Component<Props> {
  handleAddToCart = () => {
    this.props.trackEvent('add_to_cart', 1);
  };

  render() {
    const { variant, isFeatureEnabled } = this.props;
    const showNewLayout = isFeatureEnabled('new-product-layout');

    return (
      <div className={showNewLayout ? 'card-v2' : 'card-v1'}>
        {variant === 'treatment' && <PriceHighlight />}
        <button onClick={this.handleAddToCart}>Add to Cart</button>
      </div>
    );
  }
}

export default withExperimentation(ProductCard, {
  experimentKey: 'product-card-layout',
});

SSR / Next.js Support

For server-side rendering, use the ServerClient to evaluate flags and experiments on the server before hydration. This prevents layout shift and ensures consistent rendering.

// app/layout.tsx (Next.js App Router)
import { ServerClient } from '@experimentation/react-sdk/server';

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const serverClient = new ServerClient({
    apiUrl: process.env.EXPERIMENTATION_API_URL!,
    apiKey: process.env.EXPERIMENTATION_API_KEY!,
  });

  // Pre-fetch flags for the request's user (from session/cookie)
  const userId = await getUserIdFromSession();
  const flags = await serverClient.getAllFlags(userId, {
    country: 'US',
    plan: 'pro',
  });

  return (
    <html>
      <body>
        <ExperimentationProvider
          apiUrl={process.env.NEXT_PUBLIC_EXPERIMENTATION_API_URL!}
          apiKey={process.env.NEXT_PUBLIC_EXPERIMENTATION_API_KEY!}
          userId={userId}
          initialFlags={flags}  // Hydrate client with server-fetched values
        >
          {children}
        </ExperimentationProvider>
      </body>
    </html>
  );
}

Pages Router (getServerSideProps):

// pages/checkout.tsx
import { ServerClient } from '@experimentation/react-sdk/server';
import type { GetServerSideProps } from 'next';

export const getServerSideProps: GetServerSideProps = async (context) => {
  const serverClient = new ServerClient({
    apiUrl: process.env.EXPERIMENTATION_API_URL!,
    apiKey: process.env.EXPERIMENTATION_API_KEY!,
  });

  const userId = context.req.cookies['user_id'] ?? 'anonymous';
  const variant = await serverClient.getVariant('checkout-flow', userId, {});
  const flags = await serverClient.getAllFlags(userId, {});

  return {
    props: {
      variant,
      initialFlags: flags,
    },
  };
};

export default function CheckoutPage({
  variant,
  initialFlags,
}: {
  variant: string;
  initialFlags: Record<string, boolean>;
}) {
  return (
    <ExperimentationProvider initialFlags={initialFlags} userId="...">
      {variant === 'express' ? <ExpressCheckout /> : <StandardCheckout />}
    </ExperimentationProvider>
  );
}

ServerClient API:

MethodSignatureDescription
getVariant(experimentKey, userId, attributes) => Promise<string>Returns the assigned variant key
isFeatureEnabled(flagKey, userId, attributes) => Promise<boolean>Returns the flag's boolean state
getAllFlags(userId, attributes) => Promise<Record<string, boolean>>Returns all flags for pre-hydration

API Key Authentication

The SDK uses API key authentication, separate from user JWT tokens. API keys are intended for server-side and client-side SDK use.

Obtaining an API Key: Contact your platform ADMIN or use the Admin UI under Settings → API Keys.

The API key is passed as X-API-Key: your-api-key in all SDK requests.


Error Handling

Both SDKs handle API failures gracefully:

# Python — falls back to default_variant, never raises on assignment
variant = client.get_variant("my-experiment", user_id="user-123")
# Returns "control" (default_variant) if API is unreachable
// JavaScript — falls back to defaultVariant, never rejects on assignment
const variant = await client.getVariant('my-experiment', { userId: 'user-123' });
// Returns 'control' (defaultVariant) if API is unreachable

For event tracking, failures are logged but do not throw exceptions by default. Pass throwOnError: true to opt into strict mode.


Local Development

Point the SDK at your local instance during development:

client = ExperimentationClient(
    api_url="http://localhost:8000",
    api_key="dev-api-key",
)
const client = new ExperimentationClient({
  apiUrl: 'http://localhost:8000',
  apiKey: 'dev-api-key',
});