Phoenix Gamification
Spin the wheel

Spin-the-Wheel API

Phoenix provides a flexible spin-the-wheel gamification feature with configurable probability weights, frequency limits, date ranges, and visual themes.

Spin-the-Wheel API

Phoenix provides a flexible spin-the-wheel gamification feature that allows users to spin for rewards with configurable probability weights, frequency limits, and visual themes.

Overview

Spin-the-wheel lets you create wheel games where users can spin to win rewards. Each wheel has:

  • Segments: Multiple segments with different reward items (or no-win segments)
  • Probabilities: Weighted chances for each segment
  • Frequency Limits: Configurable limits (daily, total, cooldown, or unlimited)
  • Date Ranges: Optional start/end dates for time-limited wheels
  • Themes: Visual styling configurations for the wheel appearance

Client Endpoints

All client endpoints require JWT authentication. Tenant is resolved from the JWT; paths do not include tenant_id.

Get Available Wheels

Retrieve all active wheels available for the tenant (from JWT).

GET /v1/wheels

Response:

{
  "wheels": [
    {
      "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
      "name": "Daily Spin",
      "description": "Spin once per day for rewards",
      "config": {
        "segments": [
          {
            "reward_id": "550e8400-e29b-41d4-a716-446655440000",
            "probability": 3,
            "label": "100 Gems"
          },
          {
            "reward_id": null,
            "probability": 5,
            "label": "Try Again"
          },
          {
            "reward_id": "660e8400-e29b-41d4-a716-446655440001",
            "probability": 2,
            "label": "50 Coins"
          }
        ],
        "frequency": {
          "type": "daily_limit",
          "value": 1
        },
        "starts_at": "2025-01-01T00:00:00Z",
        "ends_at": "2025-12-31T23:59:59Z",
        "theme_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
        "theme_overrides": {
          "wheel": {
            "background_color": "#FF6B6B"
          }
        }
      },
      "resolved_theme": {
        "wheel": {
          "background_color": "#FF6B6B",
          "border_color": "#4ECDC4",
          "border_width": 3
        },
        "segments": {
          "default_color": "#FFFFFF",
          "text_color": "#000000"
        }
      }
    }
  ]
}

Theme Information:

  • theme_id - Optional reference to a theme configuration
  • theme_overrides - Optional partial theme overrides for this specific wheel
  • resolved_theme - The final resolved theme configuration (base theme + overrides)

Spin the Wheel

Spin a wheel for a user. The result is determined by weighted random selection.

POST /v1/wheels/{wheel_id}/spin

Request Body:

{
  "user_id": "user_123"
}

Response:

{
  "segment_index": 0,
  "segment": {
    "reward_item_id": "item-gems-100",
    "probability": 3,
    "label": "100 Gems"
  },
  "reward_item": {
    "item_id": "item-gems-100",
    "name": "100 Gems",
    "description": "100 premium gems",
    "reward_type": "currency",
    "payload": {
      "currency": "gems",
      "amount": 100
    }
  }
}

Error Responses:

  • 400 - Wheel not active, expired, not started, or frequency limit exceeded
  • 404 - Wheel not found

Get User Spin History

Retrieve a user's spin history.

GET /v1/wheels/user/{user_id}/history

Query Parameters:

ParameterTypeDescription
wheel_idUUIDFilter by specific wheel (optional)
limitintegerPage size (default: 100, max: 1000)
offsetintegerPage offset (default: 0)

Response:

{
  "spins": [
    {
      "id": "spin-123",
      "wheel_id": "wheel-abc",
      "result_index": 0,
      "reward_item_id": "item-gems-100",
      "spun_at": "2025-01-04T10:30:00Z"
    },
    {
      "id": "spin-124",
      "wheel_id": "wheel-abc",
      "result_index": 1,
      "reward_item_id": null,
      "spun_at": "2025-01-03T15:45:00Z"
    }
  ],
  "total": 25
}

JavaScript Example

// Get available wheels (tenant from JWT)
async function getAvailableWheels() {
  const response = await fetch(
    `${QUERY_API_URL}/v1/wheels`,
    {
      headers: {
        'Authorization': `Bearer ${jwt}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.json();
}

// Spin a wheel
async function spinWheel(wheelId, userId) {
  const response = await fetch(
    `${QUERY_API_URL}/v1/wheels/${wheelId}/spin`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${jwt}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ user_id: userId })
    }
  );
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || 'Failed to spin wheel');
  }
  
  return response.json();
}

// Example: Display wheel and handle spin
const { wheels } = await getAvailableWheels();
const dailyWheel = wheels[0];

const result = await spinWheel(dailyWheel.id, 'user_123');

if (result.reward_item) {
  console.log(`You won: ${result.reward_item.name}!`);
} else {
  console.log('Better luck next time!');
}

Wheel Configuration

Segments

Each segment represents one slice of the wheel:

{
  "reward_id": "uuid-or-null",  // null = no-win segment (UUID from rewards catalog)
  "probability": 3,              // Weight (higher = more likely)
  "label": "100 Gems"            // Optional display label
}

Note: Segments now use reward_id which references rewards from the rewards catalog, not reward_item_id.

Probability Calculation:

  • Probabilities are relative weights, not percentages
  • Example: [3, 5, 2] means segment 2 is 5/10 = 50% likely, segment 1 is 3/10 = 30%, segment 3 is 2/10 = 20%

Frequency Limits

Control how often users can spin:

TypeDescriptionValue Meaning
unlimitedNo limitsvalue ignored
daily_limitMax spins per dayvalue = max spins (resets at midnight UTC)
total_limitMax spins evervalue = max spins per user
cooldownTime between spinsvalue = hours to wait

Example Configurations:

// Daily limit: 3 spins per day
{
  "type": "daily_limit",
  "value": 3
}

// Total limit: 10 spins ever
{
  "type": "total_limit",
  "value": 10
}

// Cooldown: 4 hours between spins
{
  "type": "cooldown",
  "value": 4
}

// Unlimited
{
  "type": "unlimited"
}

Date Ranges

Optional time-based availability:

{
  "starts_at": "2025-01-01T00:00:00Z",  // Wheel not available before this
  "ends_at": "2025-12-31T23:59:59Z"     // Wheel not available after this
}

Themes

Wheels can use visual themes to customize their appearance. Themes define colors, styling, and visual properties for the wheel and its segments.

Creating Themes

Create reusable theme configurations that can be applied to multiple wheels.

Endpoint:

POST /v1/wheel-themes?tenant_id=${tenant_id}

Request:

{
  "name": "Modern Blue Theme",
  "description": "Clean blue color scheme",
  "theme_type": "custom",
  "config": {
    "wheel": {
      "background_color": "#4ECDC4",
      "border_color": "#45B7B8",
      "border_width": 3,
      "center_color": "#FFFFFF",
      "center_radius": 50
    },
    "segments": {
      "default_color": "#FFFFFF",
      "text_color": "#000000",
      "text_font_size": 16,
      "border_color": "#E0E0E0",
      "border_width": 1
    },
    "segment_overrides": {
      "0": {
        "color": "#FFD700",
        "text_color": "#000000"
      },
      "2": {
        "color": "#FF6B6B",
        "text_color": "#FFFFFF"
      }
    }
  }
}

Using Themes with Wheels

When creating a wheel, you can reference a theme:

{
  "name": "Daily Spin",
  "description": "Spin once per day",
  "config": {
    "segments": [
      { "reward_id": "550e8400-e29b-41d4-a716-446655440000", "probability": 3, "label": "100 Gems" },
      { "reward_id": null, "probability": 5, "label": "Try Again" }
    ],
    "frequency": {
      "type": "daily_limit",
      "value": 1
    },
    "theme_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "theme_overrides": {
      "wheel": {
        "background_color": "#FF6B6B"
      }
    }
  }
}

Theme Overrides:

  • theme_id - Reference to a theme configuration
  • theme_overrides - Optional partial overrides that merge with the base theme
  • Only specified fields in theme_overrides will override the base theme

Listing Themes

Get all themes for a tenant:

GET /v1/wheel-themes?tenant_id=${tenant_id}

Query Parameters:

  • theme_type - Filter by type (template or custom)
  • status - Filter by status (active or archived)

System Templates

Phoenix provides system templates you can use as starting points:

GET /v1/wheel-themes/templates

These templates are pre-configured themes you can reference or customize.

Admin Endpoints

Admins can manage wheel definitions via the admin API.

Create Wheel

POST /v1/wheels?tenant_id=${tenant_id}

List Wheels

GET /v1/wheels?tenant_id=${tenant_id}

Update Wheel

PUT /v1/wheels/{wheel_id}?tenant_id=${tenant_id}

Delete Wheel

DELETE /v1/wheels/{wheel_id}?tenant_id=${tenant_id}

Reward Tracking

Winning spins create orders that appear in the user's order history. Use the Query Gateway orders API (see Reward Grants):

GET /v1/orders/user/{user_id}
Authorization: Bearer <JWT>

Filter or display orders from source_type or metadata to show wheel wins.

Best Practices

  1. Probability Design: Use simple ratios (e.g., 1, 2, 3) rather than percentages for easier adjustment
  2. No-Win Segments: Include segments with reward_id: null to create "try again" experiences
  3. Frequency Limits: Start with daily limits to encourage daily engagement
  4. Date Ranges: Use for seasonal or promotional wheels
  5. Themes: Create reusable themes for consistent branding across multiple wheels
  6. Theme Overrides: Use theme overrides for wheel-specific customizations while maintaining base theme consistency
  7. Error Handling: Always handle frequency limit errors gracefully in your UI

On this page