Phoenix Gamification
Streaks

Querying Streak Results

Use the Query Gateway to retrieve user streak progress for profile screens, achievement displays, or analytics dashboards. Covers REST endpoints, authentication, and response formats.

Querying Streak Results

Use the Query Gateway to retrieve user streak progress for profile screens, achievement displays, or analytics dashboards. This guide covers REST endpoints, authentication, and response formats.


1. Authentication

Query Gateway uses JWT only. Send Authorization: Bearer <JWT>. Tenant and user are resolved from the token. Paths do not include tenant_id.


2. Endpoint Overview

Use CaseMethod & Path
List streak definitionsGET /v1/streaks/definitions
Get all streaks for a userGET /v1/streaks/user/{user_id}
Get specific streak for userGET /v1/streaks/user/{user_id}/{definition_id}

3. List Streak Definitions

Retrieve all active streak definitions for a tenant. Useful for displaying available streaks to users.

GET /v1/streaks/definitions

Response

{
  "definitions": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "tenant_id": "tenant-prod-001",
      "name": "Daily Login Streak",
      "description": "Track consecutive days users log in",
      "config": {
        "event_types": ["user.login"],
        "window": {
          "type": "calendar",
          "period": "daily",
          "timezone": "UTC",
          "reset_time": "00:00"
        },
        "condition": {
          "type": "count",
          "min": 1
        },
        "milestones": [
          { "threshold": 7, "reward_item_id": null, "repeatable": false },
          { "threshold": 30, "reward_item_id": null, "repeatable": false }
        ]
      },
      "status": "active"
    }
  ]
}

Only active definitions are returned. Use this to populate streak selection UIs or onboarding flows.


4. Get All Streaks for a User

Retrieve all streak progress for a specific user. Ideal for profile pages showing all active streaks.

GET /v1/streaks/user/{user_id}

Response

{
  "streaks": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "definition_id": "550e8400-e29b-41d4-a716-446655440000",
      "definition_name": "Daily Login Streak",
      "tenant_id": "tenant-prod-001",
      "user_id": "user-12345",
      "current_count": 15,
      "current_window_key": "2025-01-15",
      "current_window_progress": {
        "count": 1,
        "sum": 0,
        "distinct": []
      },
      "current_window_satisfied": true,
      "last_activity_at": "2025-01-15T08:30:00Z",
      "seconds_until_window_close": 57600,
      "next_milestone": 30,
      "earned_milestones": [7],
      "window": {
        "type": "calendar",
        "period": "daily",
        "timezone": "UTC",
        "reset_time": "00:00"
      },
      "condition": {
        "type": "count",
        "min": 1
      },
      "milestones": [
        { "threshold": 7, "reward_item_id": null, "repeatable": false },
        { "threshold": 30, "reward_item_id": null, "repeatable": false }
      ]
    }
  ],
  "total": 1
}

Response Fields

FieldDescription
current_countConsecutive windows where condition was satisfied
current_window_keyIdentifier for the current time window (e.g., 2025-01-15)
current_window_progressProgress toward satisfying the current window condition
current_window_satisfiedWhether the current window condition is already met
last_activity_atTimestamp of the user's last qualifying event
seconds_until_window_closeTime remaining until window closes (null if not calculated)
next_milestoneNext milestone threshold to reach (null if all earned)
earned_milestonesArray of milestone thresholds the user has already unlocked
windowWindow configuration (type, period, timezone, reset_time)
conditionCondition configuration (type, min) for progress calculation
milestonesAll milestone definitions with thresholds and rewards

5. Get Specific Streak for a User

Retrieve detailed progress for a single streak. Use this for dedicated streak detail screens.

GET /v1/streaks/user/{user_id}/{definition_id}

Response

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "definition_id": "550e8400-e29b-41d4-a716-446655440000",
  "definition_name": "Daily Login Streak",
  "tenant_id": "tenant-prod-001",
  "user_id": "user-12345",
  "current_count": 15,
  "current_window_key": "2025-01-15",
  "current_window_progress": {
    "count": 1,
    "sum": 0,
    "distinct": []
  },
  "current_window_satisfied": true,
  "last_activity_at": "2025-01-15T08:30:00Z",
  "seconds_until_window_close": 57600,
  "next_milestone": 30,
  "earned_milestones": [7],
  "window": {
    "type": "calendar",
    "period": "daily",
    "timezone": "UTC",
    "reset_time": "00:00"
  },
  "condition": {
    "type": "count",
    "min": 1
  },
  "milestones": [
    { "threshold": 7, "reward_item_id": null, "repeatable": false },
    { "threshold": 30, "reward_item_id": null, "repeatable": false }
  ]
}

All necessary information for displaying progress bars, calculating time remaining, and showing milestones is included directly in the response—no need to fetch the definition separately.

Returns 404 if the user has no record for the specified streak definition.


6. Understanding Window Progress

The current_window_progress object shows how far along the user is in the current window:

For Count Conditions

{
  "count": 2,
  "sum": 0,
  "distinct": []
}

The user has triggered 2 qualifying events this window. If condition.min is 3, they need one more.

For Sum Conditions

{
  "count": 5,
  "sum": 350.5,
  "distinct": []
}

The user has accumulated 350.5 points from 5 events. If condition.min is 500, they need 149.5 more.

For Distinct Conditions

{
  "count": 3,
  "sum": 0,
  "distinct": ["arcade", "ranked", "casual"]
}

The user has played 3 distinct game modes. If condition.min is 5, they need 2 more unique modes.


7. Displaying Streak Information

Progress Bar Example

Calculate completion percentage for UI progress bars using the condition and current_window_progress fields:

function getProgressPercentage(streak) {
  const condition = streak.condition;
  const progress = streak.current_window_progress;

  switch (condition.type) {
    case "count":
      return Math.min(100, (progress.count / condition.min) * 100);
    case "sum":
      return Math.min(100, (progress.sum / condition.min) * 100);
    case "distinct":
      return Math.min(100, (progress.distinct.length / condition.min) * 100);
  }
}

Time Remaining

The seconds_until_window_close field provides the time remaining until the window closes. For calendar windows, this is calculated based on the current time and window configuration. For rolling windows, it's based on the last activity timestamp.

Display urgency when approaching the deadline, especially if current_window_satisfied is false:

function formatTimeRemaining(seconds) {
  if (seconds === null) return "Unknown";
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  return `${hours}h ${minutes}m`;
}

// Example usage
if (streak.seconds_until_window_close !== null) {
  const timeRemaining = formatTimeRemaining(streak.seconds_until_window_close);
  console.log(`Window closes in: ${timeRemaining}`);
}

Milestone Visualization

All milestone definitions are included in the milestones array. Display earned, next, and future milestones:

// Extract milestone thresholds
const milestoneThresholds = streak.milestones.map((m) => m.threshold);

// Show earned, next, and future milestones
const earned = streak.earned_milestones;
const next = streak.next_milestone;
const future = milestoneThresholds.filter(
  (m) => m > streak.current_count && !earned.includes(m)
);

// Display
earned.forEach((threshold) => console.log(`✅ ${threshold} days - Earned`));
if (next) console.log(`⭐ ${next} days - Current Goal`);
future.forEach((threshold) => console.log(`🔒 ${threshold} days - Locked`));

8. Error Reference

StatusCodeMeaning
400INVALID_INPUTMalformed parameters (e.g., invalid UUID format).
401UNAUTHORIZEDMissing or invalid JWT. Use Authorization: Bearer <JWT>.
403FORBIDDENAPI key lacks access to the tenant.
404NOT_FOUNDStreak definition not found or user has no streak record.
429RATE_LIMITEDRequest volume exceeded plan limits.
500INTERNAL_ERRORUnexpected server issue; retry or contact support.

Standard error envelope:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Streak not found for user 'user-12345' and definition '550e8400-...'",
    "status": 404
  }
}

9. Integration Tips

  • Cache streak definitions locally; they change infrequently.
  • Poll user streaks periodically (every 30–60 seconds) or after sending qualifying events.
  • Display current_window_satisfied prominently so users know when they're "safe" for the day.
  • Show countdown timers for window resets to create engagement urgency.
  • Celebrate milestone achievements with animations or special notifications.
  • Handle 404 gracefully—users may not have started a streak yet.

10. Real-Time Updates

For live streak updates without polling, subscribe to streak events via your event infrastructure:

Event TypePayload Includes
streak.incrementeduser_id, definition_id, new_count
streak.brokenuser_id, definition_id, final_count
streak.milestone_reacheduser_id, definition_id, threshold, reward_id
streak.at_riskuser_id, definition_id, current_count, window_ends_at

Use these events to push real-time notifications or update UI states instantly without requiring user-initiated refreshes.

With querying in place, your integration now covers streak creation, management, and retrieval. You're ready to build engaging streak experiences for your users.

On this page