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
// 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.
// 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
// 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.
// 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
// 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
// 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 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
// 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 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-Keyheader 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