Phoenix Gamification
Profiles

Querying Profiles

How to retrieve user profile information. Profiles are automatically created if they don't exist.

Querying Profiles

This guide explains how to retrieve user profile information.

Overview

The Query API provides a simple endpoint to get user profile information. Profiles are automatically created if they don't exist, so you can always query them.

Get User Profile

Get a user's profile information. If the profile doesn't exist, it will be created automatically.

Endpoint:

GET /v1/profiles/user/{user_id}

PUT same path to update profile (e.g. name, avatar).

Example Request:

GET /v1/profiles/user/user_123

Response:

{
  "tenant_id": "your_tenant",
  "user_id": "user_123",
  "name": "Brave Tiger 42",
  "avatar_url": "https://api.dicebear.com/9.x/adventurer/svg?seed=your_tenant_user_123",
  "created_at": "2026-01-28T10:00:00Z",
  "updated_at": "2026-01-28T10:00:00Z"
}

Profile Response Fields

FieldTypeDescription
tenant_idstringTenant identifier
user_idstringUser identifier
namestringDisplay name (auto-generated if not set)
avatar_urlstringAvatar image URL (auto-generated)
created_atdatetimeWhen profile was first created
updated_atdatetimeLast update timestamp

Automatic Profile Creation

If a profile doesn't exist when queried:

  • A random name is generated (e.g., "Brave Tiger 42")
  • An avatar URL is created based on tenant ID and user ID
  • The profile is created and returned immediately

This means you can always query a profile - it will exist after the first request.

JavaScript Example

class ProfileClient {
  constructor(apiUrl, jwt) {
    this.apiUrl = apiUrl;
    this.jwt = jwt;
  }

  async getProfile(userId) {
    const response = await fetch(
      `${this.apiUrl}/v1/profiles/user/${userId}`,
      {
        headers: {
          'Authorization': `Bearer ${this.jwt}`,
          'Content-Type': 'application/json'
        }
      }
    );
    
    if (!response.ok) {
      throw new Error('Failed to fetch profile');
    }
    
    return response.json();
  }

  async getMultipleProfiles(userIds) {
    // Fetch profiles in parallel (tenant from JWT)
    const promises = userIds.map(userId => 
      this.getProfile(userId).catch(err => {
        console.error(`Failed to fetch profile for ${userId}:`, err);
        return null;
      })
    );
    
    const profiles = await Promise.all(promises);
    return profiles.filter(p => p !== null);
  }
}

// Example usage
const profile = new ProfileClient('https://query.phoenix.example.com', userJwt);

// Get single profile (tenant from JWT)
const userProfile = await profile.getProfile('user_123');
console.log(`User: ${userProfile.name}`);
console.log(`Avatar: ${userProfile.avatar_url}`);

// Display profile in UI
document.getElementById('user-name').textContent = userProfile.name;
document.getElementById('user-avatar').src = userProfile.avatar_url;

// Get multiple profiles (e.g., for leaderboard)
const userIds = ['user_123', 'user_456', 'user_789'];
const profiles = await profile.getMultipleProfiles(userIds);
profiles.forEach(p => {
  console.log(`${p.user_id}: ${p.name}`);
});

Using Profiles in Your Application

Display User Info

async function displayUserProfile(userId) {
  const profile = await profileClient.getProfile(userId);
  
  // Update UI elements
  document.getElementById('user-name').textContent = profile.name;
  document.getElementById('user-avatar').src = profile.avatar_url;
  document.getElementById('user-avatar').alt = profile.name;
}

Leaderboard Display

async function displayLeaderboard(tenantId, leaderboard) {
  // Get all user IDs from leaderboard
  const userIds = leaderboard.entries.map(entry => entry.user_id);
  
  // Fetch all profiles
  const profiles = await profileClient.getMultipleProfiles(userIds);
  
  // Create a map for quick lookup
  const profileMap = new Map(profiles.map(p => [p.user_id, p]));
  
  // Display leaderboard with profile info
  leaderboard.entries.forEach(entry => {
    const userProfile = profileMap.get(entry.user_id);
    console.log(`${entry.rank}. ${userProfile?.name || 'Unknown'}: ${entry.score}`);
  });
}

Avatar Caching

Since avatars are consistent (same seed = same avatar), you can cache them:

const avatarCache = new Map();

function getAvatarUrl(tenantId, userId) {
  const key = `${tenantId}_${userId}`;
  
  if (avatarCache.has(key)) {
    return avatarCache.get(key);
  }
  
  // Avatar URL is deterministic based on tenant and user ID
  const avatarUrl = `https://api.dicebear.com/9.x/adventurer/svg?seed=${tenantId}_${userId}`;
  avatarCache.set(key, avatarUrl);
  
  return avatarUrl;
}

Best Practices

  1. Cache Profiles: Cache profile data to reduce API calls
  2. Batch Requests: When displaying multiple users (like in leaderboards), fetch profiles in parallel
  3. Handle Missing Profiles: Even though profiles auto-create, handle errors gracefully
  4. Avatar Optimization: Consider caching or preloading avatars for better performance
  5. Fallback Display: Show user ID or placeholder if profile fails to load

Error Handling

async function getProfileSafely(tenantId, userId) {
  try {
    const profile = await profileClient.getProfile(userId);
    return profile;
  } catch (error) {
    console.error('Failed to fetch profile:', error);
    // Return fallback profile
    return {
      tenant_id: tenantId,
      user_id: userId,
      name: `User ${userId}`,
      avatar_url: getAvatarUrl(tenantId, userId),
      created_at: new Date().toISOString(),
      updated_at: new Date().toISOString()
    };
  }
}

Next Steps

On this page