Player Management API

Essential player balance management APIs designed for games with 60M+ players. Optimized for high-volume operations with intelligent caching, circuit breakers, and streaming capabilities for massive scale.

Individual Player Lookup

Get player balance by email with 5-minute caching and 200/minute rate limits for high-volume games.

Batch Operations

Process up to 100K players per request with memory-efficient chunked processing and optimized queries.

Full Player Streaming

Stream all players for data exports and analytics with configurable chunk sizes up to 50K per chunk.

Game-Wide Analytics

Real-time game statistics including player counts, balance totals, and recent activity with SQL optimization.

Circuit Breaker Protection

Built-in protection against database overload with automatic failover and degraded service modes.

Cache Invalidation

Real-time cache invalidation webhooks to keep player data fresh across all balance changes.

Individual Player Lookup

The most common operation - get player balance by email. Optimized for 60M+ player games with aggressive caching.

📈 Performance Specs

  • Rate Limit: 2000/minute per game
  • Cache TTL: 5 minutes for individual lookups
  • Response Time: ~50ms (cached), ~200ms (DB)
  • Circuit Breaker: 5 failures, 30s reset

🔍 Use Cases

  • • Player login balance display
  • • Purchase eligibility checks
  • • Real-time balance updates
  • • Customer support lookups
Individual Player Balance Lookup
// GET /api/player-balances/player/by-email/{email}
const SDK_KEY = 'ivsdk_your_64_character_key_here';

async function getPlayerBalance(playerEmail) {
  const response = await fetch(
    `https://invo.network/api/player-balances/player/by-email/${encodeURIComponent(playerEmail)}`,
    {
      method: 'GET',
      headers: {
        'X-Game-Secret-Key': SDK_KEY,
        'Content-Type': 'application/json'
      }
    }
  );
  
  if (response.ok) {
    return await response.json();
  } else {
    throw new Error(`API Error: ${response.status}`);
  }
}

// Example usage
try {
  const playerData = await getPlayerBalance('player@example.com');
  console.log('Player Balance:', playerData);
  
  // Response structure:
  // {
  //   "player": {
  //     "player_id": 12345,
  //     "player_name": "PlayerOne",
  //     "player_email": "player@example.com",
  //     "date_joined": "2024-01-15T10:30:00Z"
  //   },
  //   "balances": [
  //     {
  //       "currency_id": 1,
  //       "currency_name": "Gold Coins",
  //       "available_balance": "1500.00",
  //       "reserved_balance": "50.00",
  //       "total_balance": "1550.00"
  //     }
  //   ],
  //   "summary": {
  //     "total_value": "1550.00",
  //     "currency_count": 1,
  //     "has_funds": true
  //   },
  //   "last_updated": "2024-12-09T15:30:45Z"
  // }
  
} catch (error) {
  console.error('Failed to get player balance:', error);
}

Batch Player Operations

Process up to 100K players per request with memory-efficient chunked processing. Essential for analytics and bulk operations.

100K
Max Players per Batch
5K
Chunk Size for Processing
50/min
Rate Limit per Game
Batch Player Balance Operations
// POST /api/player-balances/batch
async function getBatchPlayerBalances(playerEmails = [], playerIds = []) {
  const totalRequested = playerEmails.length + playerIds.length;
  
  if (totalRequested > 100000) {
    throw new Error('Batch too large. Maximum 100,000 players per request.');
  }
  
  const response = await fetch('https://invo.network/api/player-balances/batch', {
    method: 'POST',
    headers: {
      'X-Game-Secret-Key': SDK_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      player_emails: playerEmails,
      player_ids: playerIds
    })
  });
  
  return await response.json();
}

// Example: Batch lookup for analytics
const guildMemberEmails = [
  'leader@guild.com',
  'member1@guild.com', 
  'member2@guild.com',
  // ... up to 100K emails
];

try {
  const batchResult = await getBatchPlayerBalances(guildMemberEmails);
  
  console.log(`Found ${batchResult.summary.total_found} of ${batchResult.summary.total_requested} players`);
  
  // Process results
  batchResult.results.forEach(result => {
    if (result.error) {
      console.log(`Player not found: ${result.player.player_email}`);
    } else {
      console.log(`${result.player.player_name}: ${result.total_value} total value`);
    }
  });
  
  // Response structure:
  // {
  //   "results": [
  //     {
  //       "player": {
  //         "player_id": 12345,
  //         "player_email": "leader@guild.com",
  //         "player_name": "GuildLeader"
  //       },
  //       "balances": [...],
  //       "total_value": "5000.00"
  //     },
  //     {
  //       "player": {"player_email": "notfound@guild.com"},
  //       "error": "Player not found",
  //       "balances": []
  //     }
  //   ],
  //   "summary": {
  //     "total_requested": 3,
  //     "total_found": 2,
  //     "total_not_found": 1
  //   },
  //   "retrieved_at": "2024-12-09T15:30:45Z"
  // }
  
} catch (error) {
  console.error('Batch request failed:', error);
}

Full Player Data Streaming

Stream ALL players for games with 60M+ players. Essential for data exports, analytics, and full game backups.

⚡ Streaming Features

Performance

  • • Up to 50K players per chunk
  • • Memory-efficient processing
  • • Progress logging every 10K players
  • • Automatic session cleanup

Controls

  • • Resume from specific player ID
  • • Include/exclude zero balances
  • • Rate limited: 5/hour per game
  • • Real-time JSON streaming
Full Player Data Streaming
// GET /api/player-balances/stream-all
async function streamAllPlayers(options = {}) {
  const params = new URLSearchParams({
    last_player_id: options.lastPlayerId || 0,
    chunk_size: Math.min(options.chunkSize || 10000, 50000),
    include_zero_balances: options.includeZeroBalances || false
  });
  
  const response = await fetch(
    `https://invo.network/api/player-balances/stream-all?${params}`,
    {
      method: 'GET',
      headers: {
        'X-Game-Secret-Key': SDK_KEY,
        'Content-Type': 'application/json'
      }
    }
  );
  
  if (!response.ok) {
    throw new Error(`Stream failed: ${response.status}`);
  }
  
  // Handle streaming response
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  
  try {
    while (true) {
      const { done, value } = await reader.read();
      
      if (done) break;
      
      buffer += decoder.decode(value, { stream: true });
      
      // Process complete JSON objects as they arrive
      const lines = buffer.split('\n');
      buffer = lines.pop(); // Keep incomplete line in buffer
      
      for (const line of lines) {
        if (line.trim()) {
          try {
            const data = JSON.parse(line);
            console.log('Received player chunk:', data);
          } catch (e) {
            // Ignore parsing errors for partial data
          }
        }
      }
    }
  } finally {
    reader.releaseLock();
  }
}

// Example: Stream with progress tracking
let totalProcessed = 0;
let lastPlayerId = 0;

async function exportAllPlayers() {
  const batchSize = 25000; // 25K players per chunk
  
  console.log('Starting full player export...');
  
  try {
    await streamAllPlayers({
      lastPlayerId: lastPlayerId,
      chunkSize: batchSize,
      includeZeroBalances: false // Only players with funds
    });
    
    console.log(`Export completed. Total processed: ${totalProcessed}`);
    
  } catch (error) {
    console.error('Export failed:', error);
    console.log(`Resume from player ID: ${lastPlayerId}`);
  }
}

// Stream response format:
// {
//   "status": "streaming",
//   "data": [
//     {
//       "player_id": 12345,
//       "player_email": "player@example.com",
//       "player_name": "PlayerName",
//       "balances": [
//         {
//           "currency_id": 1,
//           "currency_name": "Gold Coins",
//           "available": "1500.00",
//           "total": "1550.00"
//         }
//       ],
//       "total_value": "1550.00"
//     }
//   ],
//   "total_streamed": 25000,
//   "last_player_id": 67890
// }

Game-Wide Analytics

Get essential game statistics optimized for 60M+ player games with SQL-optimized queries and 10-minute caching.

Game-Wide Analytics & Statistics
// GET /api/player-balances/game-summary
async function getGameSummary() {
  const response = await fetch('https://invo.network/api/player-balances/game-summary', {
    method: 'GET',
    headers: {
      'X-Game-Secret-Key': SDK_KEY,
      'Content-Type': 'application/json'
    }
  });
  
  return await response.json();
}

// Example usage for dashboard
try {
  const summary = await getGameSummary();
  
  console.log('Game Analytics:');
  console.log(`Total Players: ${summary.players.total_players.toLocaleString()}`);
  console.log(`New Players (24h): ${summary.players.new_players_24h}`);
  console.log(`Transactions (24h): ${summary.activity.transactions_24h}`);
  
  // Economy breakdown by currency
  summary.economy.forEach(currency => {
    console.log(`\n${currency.currency_name}:`);
    console.log(`  Funded Players: ${currency.funded_players.toLocaleString()}`);
    console.log(`  Total Balance: ${currency.total_balance}`);
    console.log(`  Average Balance: ${currency.average_balance}`);
  });
  
  // Response structure:
  // {
  //   "players": {
  //     "total_players": 62500000,
  //     "new_players_24h": 125000
  //   },
  //   "economy": [
  //     {
  //       "currency_name": "Gold Coins",
  //       "funded_players": 45000000,
  //       "total_balance": "2500000000.00",
  //       "average_balance": "55.56"
  //     },
  //     {
  //       "currency_name": "Premium Gems", 
  //       "funded_players": 15000000,
  //       "total_balance": "500000000.00",
  //       "average_balance": "33.33"
  //     }
  //   ],
  //   "activity": {
  //     "transactions_24h": 850000
  //   },
  //   "generated_at": "2024-12-09T15:30:45Z"
  // }
  
} catch (error) {
  console.error('Failed to get game summary:', error);
}

Real-Time Cache Management

Keep player data fresh with webhook-based cache invalidation. Call after every balance change to maintain data consistency.

⚡ Cache Strategy

  • • Individual lookups: 5-minute TTL
  • • Batch requests: 1-minute TTL
  • • Game summary: 10-minute TTL
  • • Webhook invalidation: Immediate

🔄 Invalidation Events

  • • Currency purchases completed
  • • Transfer transactions processed
  • • Item purchases made
  • • Manual balance adjustments
Cache Invalidation Webhooks
// POST /api/player-balances/webhook/balance-updated
async function invalidatePlayerCache(gameId, playerId, playerEmail = null) {
  const response = await fetch('https://invo.network/api/player-balances/webhook/balance-updated', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      game_id: gameId,
      player_id: playerId,
      player_email: playerEmail
    })
  });
  
  return await response.json();
}

// Example: Invalidate cache after currency purchase
async function processCurrencyPurchase(playerId, amount) {
  try {
    // 1. Process the purchase (your existing logic)
    const purchase = await completePurchaseTransaction(playerId, amount);
    
    // 2. Invalidate cache immediately
    await invalidatePlayerCache(
      purchase.game_id,
      purchase.player_id, 
      purchase.player_email
    );
    
    console.log('Purchase completed and cache invalidated');
    return purchase;
    
  } catch (error) {
    console.error('Purchase or cache invalidation failed:', error);
    throw error;
  }
}

// Example: Batch cache invalidation after transfers
async function invalidateBatchTransferCaches(transferResults) {
  const invalidationPromises = transferResults.map(result => {
    if (result.success) {
      return Promise.all([
        // Invalidate sender cache
        invalidatePlayerCache(result.from_game_id, result.from_player_id, result.from_player_email),
        // Invalidate receiver cache  
        invalidatePlayerCache(result.to_game_id, result.to_player_id, result.to_player_email)
      ]);
    }
  });
  
  await Promise.all(invalidationPromises);
  console.log('All transfer caches invalidated');
}

Error Handling & Circuit Breakers

Robust error handling with circuit breaker protection to prevent cascade failures in high-volume games.

🔒 Circuit Breaker

  • • Fail threshold: 5 errors
  • • Reset timeout: 30 seconds
  • • Excludes: ValueError, KeyError
  • • State: Open/Closed/Half-Open

⚠️ Rate Limiting

  • • Individual: 2000/min per game
  • • Batch: 50/min per game
  • • Streaming: 5/hour per game
  • • Webhooks: 10K/min global

🏥 Health Monitoring

  • • Database connectivity
  • • Cache availability
  • • Circuit breaker status
  • • Service degradation alerts
Robust Error Handling & Retry Logic
// Comprehensive error handling for player management
class PlayerAPIClient {
  constructor(sdkKey) {
    this.sdkKey = sdkKey;
    this.baseURL = 'https://invo.network/api/player-balances';
  }
  
  async request(endpoint, options = {}) {
    const response = await fetch(`${this.baseURL}${endpoint}`, {
      ...options,
      headers: {
        'X-Game-Secret-Key': this.sdkKey,
        'Content-Type': 'application/json',
        ...options.headers
      }
    });
    
    // Handle specific error cases
    switch (response.status) {
      case 200:
        return await response.json();
        
      case 404:
        throw new PlayerNotFoundError('Player not found');
        
      case 429:
        const retryAfter = response.headers.get('Retry-After') || 60;
        throw new RateLimitError(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
        
      case 503:
        const errorData = await response.json();
        if (errorData.error === 'service_unavailable') {
          throw new CircuitBreakerError('Service temporarily unavailable due to circuit breaker');
        }
        throw new ServiceError('Service unavailable');
        
      case 401:
        throw new AuthenticationError('Invalid SDK key');
        
      case 400:
        const badRequestData = await response.json();
        throw new ValidationError(badRequestData.message);
        
      default:
        throw new APIError(`API request failed with status ${response.status}`);
    }
  }
  
  async getPlayerBalanceWithRetry(playerEmail, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await this.request(`/player/by-email/${encodeURIComponent(playerEmail)}`);
        
      } catch (error) {
        if (error instanceof CircuitBreakerError && attempt < maxRetries) {
          // Wait before retry for circuit breaker
          await new Promise(resolve => setTimeout(resolve, 30000));
          continue;
        }
        
        if (error instanceof RateLimitError && attempt < maxRetries) {
          // Exponential backoff for rate limits
          const delay = Math.pow(2, attempt) * 1000;
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
        
        throw error; // Re-throw if max retries exceeded
      }
    }
  }
}

// Custom error classes
class PlayerNotFoundError extends Error {}
class RateLimitError extends Error {}
class CircuitBreakerError extends Error {}
class AuthenticationError extends Error {}
class ValidationError extends Error {}
class ServiceError extends Error {}
class APIError extends Error {}

// Usage with error handling
const client = new PlayerAPIClient('ivsdk_your_key_here');

try {
  const playerData = await client.getPlayerBalanceWithRetry('player@example.com');
  console.log('Player data retrieved successfully:', playerData);
  
} catch (error) {
  if (error instanceof PlayerNotFoundError) {
    console.log('Player not found - might be a new user');
  } else if (error instanceof RateLimitError) {
    console.log('Rate limited - implementing backoff');
  } else if (error instanceof CircuitBreakerError) {
    console.log('Service degraded - using cached data or fallback');
  } else {
    console.error('Unexpected error:', error.message);
  }
}

Performance Optimization Tips

Best practices for optimal performance when managing millions of players.

⚡ Query Optimization

  • Cache First: Always check cache before API calls
  • Batch Wisely: Use batch endpoint for 100+ players
  • Stream Large Exports: Use streaming for full data exports
  • Invalidate Immediately: Clear cache after balance changes

🎯 Integration Patterns

  • Server-Side Only: Never call from client code
  • Connection Pooling: Reuse HTTP connections
  • Async Processing: Use Promise.all for parallel requests
  • Circuit Breaker Aware: Handle degraded service gracefully

Service Health Monitoring

Monitor API health and performance with built-in health check endpoints.

Rate Limits & Usage Guidelines

Essential limits to ensure optimal performance for all games on the network:

Per-Game Limits

  • Individual Lookups: 2,000/minute
  • Batch Requests: 50/minute
  • Game Summary: 100/minute
  • Full Streaming: 5/hour

Best Practices

  • • Cache responses for 5+ minutes when possible
  • • Use batch endpoint for 100+ concurrent lookups
  • • Implement exponential backoff for rate limit errors
  • • Monitor circuit breaker state for service health

Real-World Integration Examples

Common integration patterns for different game scenarios and player volumes.

🏰 Guild Management System

Guild Raid Eligibility Checker
// Guild balance checking for raid eligibility
class GuildManager {
  constructor(sdkKey) {
    this.apiClient = new PlayerAPIClient(sdkKey);
  }
  
  async checkRaidEligibility(guildMembers, minimumBalance = 1000) {
    // Use batch API for guild member lookup
    const memberEmails = guildMembers.map(member => member.email);
    
    try {
      const batchResult = await this.apiClient.request('/batch', {
        method: 'POST',
        body: JSON.stringify({ player_emails: memberEmails })
      });
      
      const eligibleMembers = [];
      const ineligibleMembers = [];
      
      batchResult.results.forEach(result => {
        if (result.error) {
          ineligibleMembers.push({
            email: result.player.player_email,
            reason: 'Player not found'
          });
          return;
        }
        
        const totalValue = parseFloat(result.total_value);
        if (totalValue >= minimumBalance) {
          eligibleMembers.push({
            ...result.player,
            totalValue
          });
        } else {
          ineligibleMembers.push({
            ...result.player,
            totalValue,
            reason: `Insufficient funds (needs ${minimumBalance}, has ${totalValue})`
          });
        }
      });
      
      return {
        eligible: eligibleMembers,
        ineligible: ineligibleMembers,
        eligibilityRate: (eligibleMembers.length / memberEmails.length) * 100
      };
      
    } catch (error) {
      console.error('Guild eligibility check failed:', error);
      throw error;
    }
  }
}

🏆 Leaderboard Generation

Wealth Leaderboard from 60M+ Players
// Generate wealth leaderboards from streaming data
class LeaderboardGenerator {
  constructor(sdkKey) {
    this.apiClient = new PlayerAPIClient(sdkKey);
  }
  
  async generateWealthLeaderboard(topN = 100) {
    const leaderboard = [];
    let processedCount = 0;
    
    console.log('Starting wealth leaderboard generation...');
    
    try {
      // Stream all players and track top performers
      const response = await fetch('https://invo.network/api/player-balances/stream-all?include_zero_balances=false', {
        headers: {
          'X-Game-Secret-Key': this.apiClient.sdkKey
        }
      });
      
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        const chunk = decoder.decode(value);
        const players = this.parseStreamChunk(chunk);
        
        players.forEach(player => {
          const totalValue = parseFloat(player.total_value);
          
          if (totalValue > 0) {
            leaderboard.push({
              playerId: player.player_id,
              playerName: player.player_name,
              totalValue: totalValue
            });
            
            // Keep only top N + buffer for efficiency
            if (leaderboard.length > topN * 2) {
              leaderboard.sort((a, b) => b.totalValue - a.totalValue);
              leaderboard.splice(topN);
            }
          }
          
          processedCount++;
          if (processedCount % 50000 === 0) {
            console.log(`Processed ${processedCount.toLocaleString()} players...`);
          }
        });
      }
      
      // Final sort and trim
      leaderboard.sort((a, b) => b.totalValue - a.totalValue);
      const finalLeaderboard = leaderboard.slice(0, topN).map((player, index) => ({
        rank: index + 1,
        ...player
      }));
      
      console.log(`Leaderboard generated from ${processedCount.toLocaleString()} players`);
      return finalLeaderboard;
      
    } catch (error) {
      console.error('Leaderboard generation failed:', error);
      throw error;
    }
  }
  
  parseStreamChunk(chunk) {
    // Parse streaming JSON data (simplified)
    try {
      return JSON.parse(chunk).data || [];
    } catch {
      return [];
    }
  }
}

🎧 Customer Support Tools

Customer Support Player Lookup
// Customer support player lookup with comprehensive info
class CustomerSupportAPI {
  constructor(sdkKey) {
    this.apiClient = new PlayerAPIClient(sdkKey);
  }
  
  async getPlayerSupportInfo(playerEmail) {
    try {
      // Get current balance
      const balanceData = await this.apiClient.request(
        `/player/by-email/${encodeURIComponent(playerEmail)}`
      );
      
      if (!balanceData.player) {
        return {
          found: false,
          message: 'Player not found in system'
        };
      }
      
      // Get game-wide context for comparison
      const gameSummary = await this.apiClient.request('/game-summary');
      
      // Calculate player percentiles
      const playerTotalValue = parseFloat(balanceData.summary.total_value);
      const avgBalance = parseFloat(gameSummary.economy[0]?.average_balance || 0);
      
      return {
        found: true,
        player: {
          ...balanceData.player,
          accountAge: this.calculateAccountAge(balanceData.player.date_joined),
          balances: balanceData.balances,
          totalValue: playerTotalValue,
          comparedToAverage: playerTotalValue / avgBalance,
          hasActiveBalances: balanceData.summary.has_funds,
          currencyCount: balanceData.summary.currency_count
        },
        gameContext: {
          totalPlayers: gameSummary.players.total_players,
          averageBalance: avgBalance,
          newPlayersToday: gameSummary.players.new_players_24h
        },
        supportNotes: this.generateSupportNotes(balanceData, gameSummary)
      };
      
    } catch (error) {
      if (error instanceof PlayerNotFoundError) {
        return {
          found: false,
          message: 'Player not found'
        };
      }
      throw error;
    }
  }
  
  calculateAccountAge(dateJoined) {
    if (!dateJoined) return 'Unknown';
    
    const joinDate = new Date(dateJoined);
    const now = new Date();
    const diffMs = now - joinDate;
    const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
    
    if (diffDays < 1) return 'Less than 1 day';
    if (diffDays < 30) return `${diffDays} days`;
    if (diffDays < 365) return `${Math.floor(diffDays / 30)} months`;
    return `${Math.floor(diffDays / 365)} years`;
  }
  
  generateSupportNotes(balanceData, gameSummary) {
    const notes = [];
    const totalValue = parseFloat(balanceData.summary.total_value);
    const avgBalance = parseFloat(gameSummary.economy[0]?.average_balance || 0);
    
    if (totalValue === 0) {
      notes.push('⚠️ Player has zero balance across all currencies');
    } else if (totalValue > avgBalance * 10) {
      notes.push('💎 High-value player (10x+ average balance)');
    } else if (totalValue < avgBalance * 0.1) {
      notes.push('📊 Below-average balance (less than 10% of average)');
    }
    
    if (balanceData.summary.currency_count > 1) {
      notes.push(`🪙 Uses ${balanceData.summary.currency_count} different currencies`);
    }
    
    return notes;
  }
}

Security & Best Practices

Essential security practices for managing player data at scale:

🔐 Authentication & Security

  • • Always use SDK key in X-Game-Secret-Key header on every request
  • • Never expose SDK keys in client-side code or logs
  • • Store SDK keys in secure environment variables
  • • Use HTTPS for all API communications
  • • Implement proper error handling to prevent data leaks

⚡ Performance & Reliability

  • • Implement client-side caching with appropriate TTLs
  • • Use batch endpoints for multiple player lookups
  • • Handle rate limits with exponential backoff
  • • Monitor circuit breaker status for service health
  • • Invalidate cache immediately after balance changes