Usage

The Decision API provides REST endpoints that enable you to retrieve feature flags and campaigns based on visitor context. This guide covers the three core endpoints with practical examples.

Overview

The Decision API uses a visitor-centric model where decisions are made based on:

  • Visitor ID: A unique identifier for the user

  • Context: Key-value pairs describing the visitor (device type, location, user attributes, etc.)

The API returns personalized campaigns (A/B tests, feature flags) and modifications (variations, flag values) tailored to each visitor.


Get Decision

The /v2/campaigns endpoint retrieves all active campaigns and their variations for a specific visitor.

Concept

When you send a visitor's ID and context, the Decision API:

  1. Fetches your configuration from FE&R

  2. Evaluates targeting rules against the visitor's context

  3. Assigns the visitor to appropriate campaigns and variations

  4. Returns the list of campaigns with their modifications

This is the primary endpoint for getting all decisions in one call.

Endpoint

POST /v2/campaigns

Request Body

{
  "visitor_id": "unique_visitor_id",
  "context": {
    "key": "value"
  },
  "trigger_hit": false
}

Parameters:

  • visitor_id (string, required): Unique identifier for the visitor

  • context (object, optional): Key-value pairs describing the visitor

  • trigger_hit (boolean, optional): If true, automatically sends an activation hit (default: false)

Example: Basic Request

curl -X POST http://localhost:8080/v2/campaigns \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "user_123",
    "context": {
      "platform": "web",
      "device": "desktop"
    }
  }'

Response

{
  "visitorId": "user_123",
  "campaigns": [
    {
      "id": "c1a2b3c4d5e6f7g8",
      "variationGroupId": "vg1234567890",
      "variation": {
        "id": "var_abc123",
        "modifications": {
          "type": "FLAG",
          "value": {
            "btnColor": "blue",
            "btnText": "Buy Now"
          }
        },
        "reference": false
      }
    },
    {
      "id": "c1a2b3c4d5e6f7g9",
      "variationGroupId": "vg9876543210",
      "variation": {
        "id": "var_xyz789",
        "modifications": {
          "type": "FLAG",
          "value": {
            "enableNewCheckout": true,
            "maxCartItems": 50
          }
        },
        "reference": false
      }
    }
  ]
}

Response Structure:

  • visitorId: The visitor ID from the request

  • campaigns: Array of active campaigns for this visitor

    • id: Campaign identifier

    • variationGroupId: Variation group identifier

    • variationGroupName: Variation group name

    • variation.id: Assigned variation identifier

    • variation.modifications.type: Modification type

    • variation.modifications.value: Key-value pairs with configuration values

    • variation.reference: true if this is the control group (original)

Example: With Context-Based Targeting

curl -X POST http://localhost:8080/v2/campaigns \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "premium_user_456",
    "context": {
      "userType": "premium",
      "country": "US",
      "appVersion": "2.1.0"
    }
  }'

Example: With Automatic Activation

curl -X POST http://localhost:8080/v2/campaigns \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "user_789",
    "context": {
      "platform": "mobile"
    },
    "trigger_hit": true
  }'

When trigger_hit: true, the API automatically sends activation events for all returned campaigns.


Get Feature Flag

The /v2/flags endpoint allows you to retrieves feature flags in a simplified, developer-friendly format.

Concept

Feature flags are part of the data included in campaigns and are specifically designed for:

  • Enabling/disabling features

  • Configuration management

  • Progressive rollouts

This endpoint transforms campaign modifications into a flat key-value structure, making it easier to check flags in your code.

Endpoint

POST /v2/flags

Request Body

{
  "visitor_id": "unique_visitor_id",
  "context": {
    "key": "value"
  }
}

Parameters:

  • visitor_id (string, required): Unique identifier for the visitor

  • context (object, optional): Key-value pairs describing the visitor

Example: Basic Request

curl -X POST http://localhost:8080/v2/flags \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "user_123",
    "context": {
      "platform": "web"
    }
  }'

Response

{
  "enableNewCheckout": {
    "value": true,
    "metadata": {
      "campaignId": "campaign_feature_x",
      "campaignName": "New Checkout Test",
      "slug": null,
      "type": "ab",
      "variationGroupId": "vg9876543210",
      "variationGroupName": "VG New Checkout",
      "variationId": "var_xyz789",
      "variationName": "Variation A",
      "reference": false
    }
  },
  "maxCartItems": {
    "value": 50,
    "metadata": {
      "campaignId": "campaign_feature_x",
      "campaignName": "New Checkout Test",
      "slug": null,
      "type": "ab",
      "variationGroupId": "vg9876543210",
      "variationGroupName": "VG New Checkout",
      "variationId": "var_xyz789",
      "variationName": "Variation A",
      "reference": false
    }
  },
  "btnColor": {
    "value": "blue",
    "metadata": {
      "campaignId": "c1a2b3c4d5e6f7g8",
      "campaignName": "Homepage CTA Test",
      "slug": "homepage-cta",
      "type": "ab",
      "variationGroupId": "vg1234567890",
      "variationGroupName": "VG Homepage",
      "variationId": "var_abc123",
      "variationName": "Blue Button",
      "reference": false
    }
  }
}

Response Structure:

  • Each flag key maps to an object containing:

    • value: The flag value (can be any JSON type)

    • metadata: Information about the campaign and variation

      • campaignId: Campaign identifier

      • campaignName: Human-readable campaign name

      • slug: Campaign slug (if set)

      • type: Campaign type (ab, toggle, etc.)

      • variationGroupId: Variation group identifier

      • variationGroupName: Variation group name

      • variationId: Assigned variation identifier

      • variationName: Variation name

      • reference: true if this is the control/reference variation

Example: Multi-Platform Context

curl -X POST http://localhost:8080/v2/flags \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "mobile_user_999",
    "context": {
      "platform": "mobile",
      "os": "iOS",
      "osVersion": "17.1",
      "appVersion": "3.2.1"
    }
  }'

Example: Anonymous Visitor

curl -X POST http://localhost:8080/v2/flags \
  -H "Content-Type: application/json" \
  -d '{
    "anonymous_id": "anonymous_' $(uuidgen) '",
    "visitor_id": null,
    "context": {
      "isAuthenticated": false
    }
  }'

Activate a Campaign

The /v2/activate endpoint sends an activation event when a visitor is exposed to a campaign variation.

Concept

Activation tracking is crucial for analytics and attribution. You should activate a campaign when:

  • A visitor sees the variation (impression tracking)

  • A feature is actually used (not just loaded)

  • You want to track which users experienced a variation

Important: Only activate when the user is genuinely exposed to the experience. Don't activate on page load if the feature isn't visible.

Endpoint

POST /v2/activate

Request Body

{
  "vid": "visitor_id",
  "cid": "environment_id",
  "caid": "variation_group_id",
  "vaid": "variation_id"
}

Parameters:

  • vid (string, required): Visitor ID

  • cid (string, required): Environment ID (your FE&R environment ID)

  • caid (string, required): Variation Group ID (from /v2/campaigns response: variationGroupId)

  • vaid (string, required): Variation ID (from /v2/campaigns response: variation.id)

  • aid (string, optional): Anonymous ID if different from visitor ID

Example: Basic Activation

curl -X POST http://localhost:8080/v2/activate \
  -H "Content-Type: application/json" \
  -d '{
    "vid": "user_123",
    "cid": "your_env_id",
    "caid": "vg1234567890",
    "vaid": "var_abc123"
  }'

Response

Status Code: 204 No Content

The activate endpoint returns an empty response body with a 204 status code on success.

Example: Activate Multiple Campaigns

Default settings:

  • Batch size: 50 hits

  • Batching window: 30 seconds

  • Hits are sent when either condition is met:

    • Batch reaches 50 hits, OR

    • 30 seconds have passed since last send

Batch Activate

You can activate multiple campaigns in a single HTTP request using the batch format:

curl -X POST http://localhost:8080/v2/activate \
  -H "Content-Type: application/json" \
  -d '{
    "cid": "your_env_id",
    "batch": [
      {
        "vid": "user_456",
        "caid": "vg_homepage",
        "vaid": "variation_blue_button"
      },
      {
        "vid": "user_456",
        "caid": "vg_checkout",
        "vaid": "variation_express_checkout"
      },
      {
        "vid": "user_456",
        "caid": "vg_feature_x",
        "vaid": "variation_enabled"
      }
    ]
  }'

Batch Format:

  • cid (string, required): Environment ID - applies to all items in the batch

  • batch (array, required): Array of activation objects, each containing:

    • vid (string, required): Visitor ID

    • caid (string, required): Variation Group ID

    • vaid (string, required): Variation ID

    • aid (string, optional): Anonymous ID

Benefits of batching:

  • Reduces HTTP overhead (1 request instead of N)

  • Atomic operation - all activations processed together

  • Better performance for multiple simultaneous activations

Separate Requests

# Activate first campaign
curl -X POST http://localhost:8080/v2/activate \
  -H "Content-Type: application/json" \
  -d '{
    "vid": "user_456",
    "cid": "your_env_id",
    "caid": "vg_homepage",
    "vaid": "variation_blue_button"
  }'

# Activate second campaign
curl -X POST http://localhost:8080/v2/activate \
  -H "Content-Type: application/json" \
  -d '{
    "vid": "user_456",
    "cid": "your_env_id",
    "caid": "vg_checkout",
    "vaid": "variation_express_checkout"
  }'

When to Activate

Do activate when:

  • User sees a variation on their screen

  • Feature is rendered/displayed

  • User interacts with a feature

Don't activate when:

  • Just fetching configuration

  • Preloading data

  • User hasn't seen the variation yet

Best Practices

1. Use Meaningful Visitor IDs

# Good: Persistent user ID
"visitor_id": "user_12345"

# Good: Anonymous but trackable
"visitor_id": "device_abc-xyz-123"

# Bad: Random on every request
"visitor_id": "random_' $(uuidgen) '"

2. Provide Rich Context

# Good: Rich targeting context
curl -X POST http://localhost:8080/v2/campaigns \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "user_123",
    "context": {
      "platform": "web",
      "device": "mobile",
      "userType": "premium",
      "country": "US",
      "language": "en",
      "appVersion": "2.1.0"
    }
  }'

# Bad: Minimal context
curl -X POST http://localhost:8080/v2/campaigns \
  -H "Content-Type: application/json" \
  -d '{
    "visitor_id": "user_123"
  }'

3. Activate Only on Exposure

# Good: Activate when feature is shown
if user_sees_feature; then
  curl -X POST http://localhost:8080/v2/activate ...
fi

# Bad: Activate immediately after getting campaigns
curl -X POST http://localhost:8080/v2/campaigns ...
curl -X POST http://localhost:8080/v2/activate ...  # User hasn't seen it yet!

Custom Visitor Cache Assignment Connector

The Decision API supports custom visitor cache assignment connectors to persist visitor assignments when using advanced features:

  • Experience Continuity (XPC): Keep visitors in the same variation across sessions

  • 1 Visitor 1 Test (1v1t): Ensure each visitor is assigned to only one campaign

  • Dynamic Allocation: Support traffic allocation changes

By default, the Decision API uses an empty connector (no persistence). For production use with these features, configure one of the available cache types. The Self-Hosted Decision API provides 4 cache systems to store and retrieve visitor assignments to allow traffic allocation changes for live use cases:

  • in memory: the assignments are stored in memory

  • local: the assignments are stored in a local file using a key/value database

  • redis: the assignments are stored in a redis server

  • dynamo: the assignments are stored in an AWS DynamoDB table

If you want to use another cache management system, you still can, but you will need to create a Go application that runs the Decision API package, and implement your own visitor assignment interface.

Here is an example of how you can do that:

package main

import (
  "log"
  "net/http"
  "os"
  "time"

  "github.com/flagship-io/decision-api/pkg/connectors"
  "github.com/flagship-io/decision-api/pkg/server"
  common "github.com/flagship-io/flagship-common"
)

type CustomAssignmentManager struct {
}

func (m *CustomAssignmentManager) LoadAssignments(envID string, visitorID string) (*common.VisitorAssignments, error) {
  // TODO implement this method
  return nil, nil
}

func (m *CustomAssignmentManager) ShouldSaveAssignments(context connectors.SaveAssignmentsContext) bool {
  // TODO implement this method
  return true
}

func (m *CustomAssignmentManager) SaveAssignments(envID string, visitorID string, vgIDAssignments map[string]*common.VisitorCache, date time.Time) error {
  // TODO implement this method
  return nil
}

func main() {
  srv, err := server.CreateServer(
    os.Getenv("ENV_ID"),
    os.Getenv("API_KEY"),
    ":8080",
    server.WithAssignmentsManager(&CustomAssignmentManager{}),
  )

  if err != nil {
    log.Fatalf("error when creating server: %v", err)
  }

  log.Printf("server listening on :8080")
  if err := srv.Listen(); err != http.ErrServerClosed {
    log.Fatalf("error when starting server: %v", err)
  }
}

Metrics endpoint

On top of the usual endpoints, the open source Decision API also exposes a /v2/metrics endpoint, which can be used to monitor the API performance, errors and response time. The metrics output format looks like:

{
  "cmdline": [
    "./bin/server"
  ],
  "handlers.activate.errors": 0,
  "handlers.activate.response_time.p50": 0,
  "handlers.activate.response_time.p90": 0,
  "handlers.activate.response_time.p95": 0,
  "handlers.activate.response_time.p99": 0,
  "handlers.campaign.errors": 0,
  "handlers.campaign.response_time.p50": 0,
  "handlers.campaign.response_time.p90": 0,
  "handlers.campaign.response_time.p95": 0,
  "handlers.campaign.response_time.p99": 0,
  "handlers.campaigns.errors": 0,
  "handlers.campaigns.response_time.p50": 1,
  "handlers.campaigns.response_time.p90": 4,
  "handlers.campaigns.response_time.p95": 10,
  "handlers.campaigns.response_time.p99": 11,
  "handlers.flags.errors": 0,
  "handlers.flags.response_time.p50": 1,
  "handlers.flags.response_time.p90": 1,
  "handlers.flags.response_time.p95": 1,
  "handlers.flags.response_time.p99": 1,
  "memstats": {}
}

Swagger endpoint

The Decision API also provides a /v2/swagger/ endpoint with interactive Swagger UI documentation where you can explore all available endpoints and test API calls directly from your browser.

Last updated

Was this helpful?