Core Cells
A scalable, secure REST API to connect any game to a shared multiplayer backend. Manage users, project keys, JSON game states, matchmaking, and RTS sessions — all from one unified interface.
Everything you need to build, deploy, and scale multiplayer games
Each project gets a unique key for secure access and complete data isolation between games.
Players register once and can access multiple games with a single account.
Built-in support for matchmaking, real-time game sessions, and player state management.
Comprehensive REST API for multiplayer game development
Integrate our API into your game with our easy-to-use SDKs
Seamlessly integrate the API into your Unity projects with our C# SDK. Handles authentication, HTTP requests, JSON parsing, and project key management.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace michitai
{
/// <summary>
/// Interface for API responses that have success/error fields.
/// </summary>
public interface IApiResponse
{
bool Success { get; set; }
string? Error { get; set; }
}
/// <summary>
/// Provides a client for interacting with the MICHITAI Game API.
/// Handles authentication, player management, game rooms, matchmaking, and actions.
/// </summary>
public class GameSDK
{
private readonly string _apiToken;
private readonly string _apiPrivateToken;
private readonly string _baseUrl;
private static readonly HttpClient _http = new HttpClient();
private ILogger? _logger;
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
/// <summary>
/// Matchmaking join request actions.
/// </summary>
public enum MatchmakingRequestAction
{
Approve,
Reject
}
/// <summary>
/// Initializes a new instance of the GameSDK class.
/// </summary>
/// <param name="apiToken">The public API token for authentication.</param>
/// <param name="apiPrivateToken">The private API token for privileged operations.</param>
/// <param name="baseUrl">Base URL of the API (default: https://api.michitai.com/v1/php/).</param>
public GameSDK(string apiToken, string apiPrivateToken, string baseUrl = "https://api.michitai.com/v1/php/", ILogger? logger = null)
{
_apiToken = apiToken;
_apiPrivateToken = apiPrivateToken;
_baseUrl = baseUrl.EndsWith("/") ? baseUrl : baseUrl + "/";
_logger = logger;
}
/// <summary>
/// Constructs a URL for API requests with the base URL, endpoint, and authentication token.
/// </summary>
/// <param name="endpoint">The API endpoint path.</param>
/// <param name="extra">Additional query string parameters (optional).</param>
/// <returns>Fully constructed URL string with authentication.</returns>
private string Url(string endpoint, string extra = "")
{
return $"{_baseUrl}{endpoint}?api_token={_apiToken}{extra}";
}
/// <summary>
/// Sends an HTTP request to the API and deserializes the JSON response.
/// </summary>
/// <typeparam name="T">The type to deserialize the response into.</typeparam>
/// <param name="method">The HTTP method (GET, POST, PUT, etc.).</param>
/// <param name="url">The URL to send the request to.</param>
/// <param name="body">The request body (optional). Will be serialized to JSON if provided.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the deserialized response.</returns>
private async Task<T> Send<T>(HttpMethod method, string url, object? body = null) where T : class
{
var req = new HttpRequestMessage(method, url);
if (body != null)
{
string json = JsonSerializer.Serialize(body, _jsonOptions);
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
}
var res = await _http.SendAsync(req);
string str = await res.Content.ReadAsStringAsync();
// Log the raw response for debugging
_logger?.Log($"API Response: {str}");
T response;
try
{
response = JsonSerializer.Deserialize<T>(str, _jsonOptions)!;
// Check if the response indicates an error
if (response is IApiResponse apiResponse && !apiResponse.Success)
{
_logger?.Error($"API Error: {str}");
throw new ApiException(apiResponse.Error ?? "Unknown API error", str);
}
}
catch(JsonException ex)
{
_logger?.Warn($"JSON Deserialization Error: {ex.Message}. Response: {str}");
// Try to parse as error response
try
{
var errorResponse = JsonSerializer.Deserialize<ErrorResponse>(str, _jsonOptions);
if (errorResponse != null && !errorResponse.Success)
{
throw new ApiException(errorResponse.Error ?? "API error", str);
}
}
catch
{
// If we can't parse as error response, throw the original JSON exception
throw new ApiException($"JSON deserialization failed: {ex.Message}", str);
}
throw;
}
return response;
}
/// <summary>
/// Interface for API responses that have success/error fields.
/// </summary>
private interface IApiResponse
{
bool Success { get; set; }
string? Error { get; set; }
}
/// <summary>
/// Registers a new player in the game.
/// </summary>
/// <param name="name">Display name for the player.</param>
/// <param name="playerData">Additional player data as an object (will be serialized to JSON).</param>
/// <returns>Task containing player registration response with player ID and private key.</returns>
public Task<PlayerRegisterResponse> RegisterPlayer(string name, object playerData)
{
return Send<PlayerRegisterResponse>(
HttpMethod.Post,
Url("game_players.php/register"),
new { player_name = name, player_data = playerData }
);
}
/// <summary>
/// Authenticates a player using their player token.
/// </summary>
/// <param name="playerToken">The player's authentication token.</param>
/// <returns>Task containing player authentication response with player information.</returns>
public Task<PlayerAuthResponse> AuthenticatePlayer(string playerToken)
{
return Send<PlayerAuthResponse>(
HttpMethod.Put,
Url("game_players.php/login", $"&game_player_token={playerToken}")
);
}
/// <summary>
/// Retrieves a list of all players (requires private API token).
/// </summary>
/// <returns>Task containing list of players and total count.</returns>
public Task<PlayerListResponse> GetAllPlayers()
{
return Send<PlayerListResponse>(HttpMethod.Get, Url("game_players.php/list", $"&api_private_token={_apiPrivateToken}"));
}
/// <summary>
/// Sends a heartbeat to keep the player's session alive and update last_heartbeat.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the heartbeat response.</returns>
public Task<PlayerHeartbeatResponse> SendPlayerHeartbeatAsync(string gamePlayerToken)
{
return Send<PlayerHeartbeatResponse>(
HttpMethod.Post,
Url("game_players.php/heartbeat", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Logs out a player and updates last_logout timestamp. Sets is_active to 0.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the logout response.</returns>
public Task<PlayerLogoutResponse> LogoutPlayerAsync(string gamePlayerToken)
{
return Send<PlayerLogoutResponse>(
HttpMethod.Post,
Url("game_players.php/logout", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Retrieves game data for the current game.
/// </summary>
/// <returns>Task containing the game data response.</returns>
public Task<GameDataResponse> GetGameData()
{
return Send<GameDataResponse>(HttpMethod.Get, Url("game_data.php/game/get"));
}
/// <summary>
/// Updates the game data (requires private API token).
/// </summary>
/// <param name="data">The game data to update.</param>
/// <returns>Task indicating success or failure of the update.</returns>
public Task<SuccessResponse> UpdateGameData(object data)
{
return Send<SuccessResponse>(HttpMethod.Put, Url("game_data.php/game/update", $"&api_private_token={_apiPrivateToken}"), data);
}
/// <summary>
/// Retrieves data for a specific player.
/// </summary>
/// <param name="playerToken">The player's authentication token.</param>
/// <returns>Task containing the player's data.</returns>
public Task<PlayerDataResponse> GetPlayerData(string playerToken)
{
return Send<PlayerDataResponse>(
HttpMethod.Get,
Url("game_data.php/player/get", $"&game_player_token={playerToken}")
);
}
/// <summary>
/// Updates data for a specific player.
/// </summary>
/// <param name="playerToken">The player's authentication token.</param>
/// <param name="data">The data to update for the player.</param>
/// <returns>Task indicating success or failure of the update.</returns>
public Task<SuccessResponse> UpdatePlayerData(string playerToken, object data)
{
return Send<SuccessResponse>(
HttpMethod.Put,
Url("game_data.php/player/update", $"&game_player_token={playerToken}"),
data
);
}
/// <summary>
/// Retrieves current server time.
/// </summary>
/// <returns>Task containing server time in various formats.</returns>
public Task<ServerTimeResponse> GetServerTime()
{
return Send<ServerTimeResponse>(
HttpMethod.Get,
Url("time.php")
);
}
/// <summary>
/// Retrieves server time with UTC offset adjustment.
/// </summary>
/// <param name="utcOffset">The UTC offset in hours (e.g., +1, -2).</param>
/// <returns>Task containing server time with offset information.</returns>
public Task<ServerTimeWithOffsetResponse> GetServerTimeWithOffset(int utcOffset)
{
return Send<ServerTimeWithOffsetResponse>(
HttpMethod.Get,
Url("time.php", $"&utc={utcOffset:+#;-#}")
);
}
/// <summary>
/// Creates a new game room.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="roomName">Name for the new room.</param>
/// <param name="password">Optional password for the room.</param>
/// <param name="maxPlayers">Maximum number of players allowed in the room (default: 4).</param>
/// <returns>Task containing the room creation response.</returns>
public Task<RoomCreateResponse> CreateRoomAsync(
string gamePlayerToken,
string roomName,
string? password = null,
int maxPlayers = 4)
{
return Send<RoomCreateResponse>(
HttpMethod.Post,
Url("game_room.php/rooms", $"&game_player_token={gamePlayerToken}"),
new
{
room_name = roomName,
password = password,
max_players = maxPlayers
}
);
}
/// <summary>
/// Retrieves a list of all available game rooms.
/// </summary>
/// <returns>Task containing the list of rooms.</returns>
public Task<RoomListResponse> GetRoomsAsync()
{
return Send<RoomListResponse>(
HttpMethod.Get,
Url("game_room.php/rooms")
);
}
/// <summary>
/// Joins an existing game room.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="roomId">ID of the room to join.</param>
/// <param name="password">Room password if required.</param>
/// <returns>Task containing the join room response.</returns>
public Task<RoomJoinResponse> JoinRoomAsync(
string gamePlayerToken,
string roomId,
string? password = null)
{
return Send<RoomJoinResponse>(
HttpMethod.Post,
Url($"game_room.php/rooms/{roomId}/join", $"&game_player_token={gamePlayerToken}"),
password != null ? new { password = password } : new { password = "" }
);
}
/// <summary>
/// Leaves the current room.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task indicating success or failure of leaving the room.</returns>
public Task<RoomLeaveResponse> LeaveRoomAsync(string gamePlayerToken)
{
return Send<RoomLeaveResponse>(
HttpMethod.Post,
Url("game_room.php/rooms/leave", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Retrieves a list of players in the current room.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the list of players in the room.</returns>
public Task<RoomPlayersResponse> GetRoomPlayersAsync(string gamePlayerToken)
{
return Send<RoomPlayersResponse>(
HttpMethod.Get,
Url("game_room.php/players", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Sends a heartbeat to keep the player's session alive.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the heartbeat response.</returns>
public Task<HeartbeatResponse> SendHeartbeatAsync(string gamePlayerToken)
{
return Send<HeartbeatResponse>(
HttpMethod.Post,
Url("game_room.php/heartbeat", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Submits a new game action.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="actionType">Type of the action being submitted.</param>
/// <param name="requestData">Data associated with the action.</param>
/// <returns>Task containing the action submission response.</returns>
public Task<ActionSubmitResponse> SubmitActionAsync(
string gamePlayerToken,
string actionType,
object requestData)
{
return Send<ActionSubmitResponse>(
HttpMethod.Post,
Url("game_room.php/actions", $"&game_player_token={gamePlayerToken}"),
new
{
action_type = actionType,
request_data = requestData
}
);
}
/// <summary>
/// Polls for new actions that need to be processed.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing any pending actions.</returns>
public Task<ActionPollResponse> PollActionsAsync(string gamePlayerToken)
{
return Send<ActionPollResponse>(
HttpMethod.Get,
Url("game_room.php/actions/poll", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Retrieves a list of pending actions for the player.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the list of pending actions.</returns>
public Task<ActionPendingResponse> GetPendingActionsAsync(string gamePlayerToken)
{
return Send<ActionPendingResponse>(
HttpMethod.Get,
Url("game_room.php/actions/pending", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Marks an action as completed.
/// </summary>
/// <param name="actionId">ID of the action to complete.</param>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="request">The completion request details.</param>
/// <returns>Task indicating success or failure of the completion.</returns>
public Task<ActionCompleteResponse> CompleteActionAsync(
string actionId,
string gamePlayerToken,
ActionCompleteRequest request)
{
return Send<ActionCompleteResponse>(
HttpMethod.Post,
Url($"game_room.php/actions/{actionId}/complete", $"&game_player_token={gamePlayerToken}"),
new
{
status = request.Status,
response_data = request.Response_data
}
);
}
/// <summary>
/// Sends updates to specific players in the room.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="request">The update request containing target players and data.</param>
/// <returns>Task containing the update response.</returns>
public Task<UpdatePlayersResponse> UpdatePlayersAsync(
string gamePlayerToken,
UpdatePlayersRequest request)
{
return Send<UpdatePlayersResponse>(
HttpMethod.Post,
Url("game_room.php/updates", $"&game_player_token={gamePlayerToken}"),
new
{
targetPlayerIds = request.TargetPlayerIds,
type = request.Type,
dataJson = request.DataJson
}
);
}
/// <summary>
/// Polls for player updates in the room.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="lastUpdateId">Optional last update ID to get updates after this point.</param>
/// <returns>Task containing the poll updates response.</returns>
public Task<PollUpdatesResponse> PollUpdatesAsync(
string gamePlayerToken,
string? lastUpdateId = null)
{
string extra = $"&game_player_token={gamePlayerToken}";
if (!string.IsNullOrEmpty(lastUpdateId))
{
extra += $"&lastUpdateId={lastUpdateId}";
}
return Send<PollUpdatesResponse>(
HttpMethod.Get,
Url("game_room.php/updates/poll", extra)
);
}
/// <summary>
/// Gets the current room information for the player.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the current room response.</returns>
public Task<CurrentRoomResponse> GetCurrentRoomAsync(string gamePlayerToken)
{
return Send<CurrentRoomResponse>(
HttpMethod.Get,
Url("game_room.php/current", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Lists all available matchmaking lobbies.
/// </summary>
/// <returns>Task containing the list of available matchmaking lobbies.</returns>
public Task<MatchmakingListResponse> GetMatchmakingLobbiesAsync()
{
return Send<MatchmakingListResponse>(
HttpMethod.Get,
Url("matchmaking.php/list")
);
}
/// <summary>
/// Creates a new matchmaking lobby.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="maxPlayers">Maximum number of players allowed (2-16).</param>
/// <param name="strictFull">Whether the game can only start when the lobby is full.</param>
/// <param name="joinByRequests">Whether players can only join via host approval.</param>
/// <param name="extraJsonString">Additional host-defined criteria (rank, level, etc.).</param>
/// <returns>Task containing the matchmaking lobby creation response.</returns>
public Task<MatchmakingCreateResponse> CreateMatchmakingLobbyAsync(
string gamePlayerToken,
int maxPlayers = 4,
bool strictFull = false,
bool joinByRequests = false,
object? extraJsonString = null)
{
return Send<MatchmakingCreateResponse>(
HttpMethod.Post,
Url("matchmaking.php/create", $"&game_player_token={gamePlayerToken}"),
new
{
maxPlayers = maxPlayers,
strictFull = strictFull,
joinByRequests = joinByRequests,
extraJsonString = extraJsonString
}
);
}
/// <summary>
/// Requests to join a matchmaking lobby that requires host approval.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="matchmakingId">ID of the matchmaking lobby to request to join.</param>
/// <returns>Task containing the join request response.</returns>
public Task<MatchmakingJoinRequestResponse> RequestToJoinMatchmakingAsync(
string gamePlayerToken,
string matchmakingId)
{
return Send<MatchmakingJoinRequestResponse>(
HttpMethod.Post,
Url($"matchmaking.php/{matchmakingId}/request", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Responds to a matchmaking join request (approve or reject).
/// </summary>
/// <param name="gamePlayerToken">The host player's authentication token.</param>
/// <param name="matchmakingId">ID of the matchmaking lobby.</param>
/// <param name="action">Action to take: Approve or Reject.</param>
/// <returns>Task containing the response to the join request.</returns>
public Task<MatchmakingRequestResponse> RespondToJoinRequestAsync(
string gamePlayerToken,
string matchmakingId,
MatchmakingRequestAction action)
{
return Send<MatchmakingRequestResponse>(
HttpMethod.Post,
Url($"matchmaking.php/{matchmakingId}/response", $"&game_player_token={gamePlayerToken}"),
new { action = action.ToString().ToLower() }
);
}
/// <summary>
/// Checks the status of a matchmaking join request.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="requestId">ID of the join request to check.</param>
/// <returns>Task containing the join request status.</returns>
public Task<MatchmakingRequestStatusResponse> CheckJoinRequestStatusAsync(
string gamePlayerToken,
string requestId)
{
return Send<MatchmakingRequestStatusResponse>(
HttpMethod.Get,
Url($"matchmaking.php/{requestId}/status", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Gets the current player's matchmaking status and lobby information.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the current matchmaking status.</returns>
public Task<MatchmakingCurrentResponse> GetCurrentMatchmakingStatusAsync(string gamePlayerToken)
{
return Send<MatchmakingCurrentResponse>(
HttpMethod.Get,
Url("matchmaking.php/current", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Joins a matchmaking lobby directly (only works if lobby doesn't require approval).
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <param name="matchmakingId">ID of the matchmaking lobby to join.</param>
/// <returns>Task containing the direct join response.</returns>
public Task<MatchmakingDirectJoinResponse> JoinMatchmakingDirectlyAsync(
string gamePlayerToken,
string matchmakingId)
{
return Send<MatchmakingDirectJoinResponse>(
HttpMethod.Post,
Url($"matchmaking.php/{matchmakingId}/join", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Leaves the current matchmaking lobby.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task indicating success or failure of leaving the matchmaking lobby.</returns>
public Task<MatchmakingLeaveResponse> LeaveMatchmakingAsync(string gamePlayerToken)
{
return Send<MatchmakingLeaveResponse>(
HttpMethod.Post,
Url("matchmaking.php/leave", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Lists all players in the current matchmaking lobby.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the list of players in the matchmaking lobby.</returns>
public Task<MatchmakingPlayersResponse> GetMatchmakingPlayersAsync(string gamePlayerToken)
{
return Send<MatchmakingPlayersResponse>(
HttpMethod.Get,
Url("matchmaking.php/players", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Sends a heartbeat to keep the player active in the matchmaking lobby.
/// </summary>
/// <param name="gamePlayerToken">The player's authentication token.</param>
/// <returns>Task containing the heartbeat response.</returns>
public Task<MatchmakingHeartbeatResponse> SendMatchmakingHeartbeatAsync(string gamePlayerToken)
{
return Send<MatchmakingHeartbeatResponse>(
HttpMethod.Post,
Url("matchmaking.php/heartbeat", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Removes the matchmaking lobby (host only).
/// </summary>
/// <param name="gamePlayerToken">The host player's authentication token.</param>
/// <returns>Task indicating success or failure of removing the matchmaking lobby.</returns>
public Task<MatchmakingRemoveResponse> RemoveMatchmakingLobbyAsync(string gamePlayerToken)
{
return Send<MatchmakingRemoveResponse>(
HttpMethod.Post,
Url("matchmaking.php/remove", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Starts a game from matchmaking lobby (host only).
/// </summary>
/// <param name="gamePlayerToken">The host player's authentication token.</param>
/// <returns>Task containing game start response.</returns>
public Task<MatchmakingStartResponse> StartGameFromMatchmakingAsync(string gamePlayerToken)
{
return Send<MatchmakingStartResponse>(
HttpMethod.Post,
Url("matchmaking.php/start", $"&game_player_token={gamePlayerToken}")
);
}
/// <summary>
/// Gets leaderboard with custom sorting options.
/// </summary>
/// <param name="sortBy">Array of field names to sort by (e.g., ["level"], ["level", "score"]).</param>
/// <param name="limit">Maximum number of players to return (1-1000).</param>
/// <returns>Task containing leaderboard response with ranked players.</returns>
public Task<LeaderboardResponse> GetLeaderboardAsync(string[] sortBy, int limit = 10)
{
return Send<LeaderboardResponse>(
HttpMethod.Post,
Url("leaderboard.php"),
new { sortBy = sortBy, limit = limit }
);
}
}
public interface ILogger
{
void Log(string message);
void Warn(string message);
void Error(string message);
}
public class PlayerRegisterResponse
{
public bool Success { get; set; }
public required string Player_id { get; set; }
public required string Private_key { get; set; }
public required string Player_name { get; set; }
public int Game_id { get; set; }
}
public class PlayerAuthResponse
{
public bool Success { get; set; }
public required PlayerInfo Player { get; set; }
}
public class PlayerListResponse
{
public bool Success { get; set; }
public int Count { get; set; }
public required List<PlayerShort> Players { get; set; }
}
public class PlayerShort
{
public int Id { get; set; }
public required string Player_name { get; set; }
public int Is_active { get; set; }
public required string Last_login { get; set; }
public required string Created_at { get; set; }
}
public class PlayerInfo
{
public int Id { get; set; }
public int Game_id { get; set; }
public required string Player_name { get; set; }
public required Dictionary<string, object> Player_data { get; set; }
public int Is_active { get; set; }
public required string Last_login { get; set; }
public required string Created_at { get; set; }
public required string Updated_at { get; set; }
}
public class PlayerHeartbeatResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
public required string Last_heartbeat { get; set; }
}
public class PlayerLogoutResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
public required string Last_logout { get; set; }
}
public class GameDataResponse
{
public bool Success { get; set; }
public required string Type { get; set; }
public int Game_id { get; set; }
public required Dictionary<string, object> Data { get; set; }
}
public class PlayerDataResponse
{
public bool Success { get; set; }
public required string Type { get; set; }
public int Player_id { get; set; }
public required string Player_name { get; set; }
public required Dictionary<string, object> Data { get; set; }
}
public class SuccessResponse
{
public bool Success { get; set; }
public required string Message { get; set; }
public required string Updated_at { get; set; }
}
public class ServerTimeResponse
{
public bool Success { get; set; }
public required string Utc { get; set; }
public long Timestamp { get; set; }
public required string Readable { get; set; }
}
public class ServerTimeWithOffsetResponse
{
public bool Success { get; set; }
public required string Utc { get; set; }
public long Timestamp { get; set; }
public required string Readable { get; set; }
public required TimeOffset Offset { get; set; }
}
public class TimeOffset
{
public int Offset_hours { get; set; }
public required string Offset_string { get; set; }
public required string Original_utc { get; set; }
public long Original_timestamp { get; set; }
}
public class RoomCreateResponse
{
public bool Success { get; set; }
public required string Room_id { get; set; }
public required string Room_name { get; set; }
public bool Is_host { get; set; }
}
public class RoomShort
{
public required string Room_id { get; set; }
public required string Room_name { get; set; }
public int Max_players { get; set; }
public int Current_players { get; set; }
public int Has_password { get; set; }
}
public class RoomListResponse
{
public bool Success { get; set; }
public required List<RoomShort> Rooms { get; set; }
}
public class RoomJoinResponse
{
public bool Success { get; set; }
public required string Room_id { get; set; }
public required string Message { get; set; }
}
public class RoomPlayer
{
public required string Player_id { get; set; }
public required string Player_name { get; set; }
public int Is_host { get; set; }
public int Is_online { get; set; }
public required string Last_heartbeat { get; set; }
}
public class RoomPlayersResponse
{
public bool Success { get; set; }
public required List<RoomPlayer> Players { get; set; }
public required string Last_updated { get; set; }
}
public class RoomLeaveResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
}
public class HeartbeatResponse
{
public bool Success { get; set; }
public required string Status { get; set; }
}
public class ActionSubmitResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Action_id { get; set; }
public required string Status { get; set; }
}
public class ActionInfo
{
public required string Action_id { get; set; }
public required string Action_type { get; set; }
public object? Response_data { get; set; }
public required string Status { get; set; }
}
public class ActionPollResponse
{
public bool Success { get; set; }
public required List<ActionInfo> Actions { get; set; }
}
public class PendingAction
{
public required string Action_id { get; set; }
public required string Player_id { get; set; }
public required string Action_type { get; set; }
public object? Request_data { get; set; }
public required string Created_at { get; set; }
public required string Player_name { get; set; }
}
public class ActionPendingResponse
{
public bool Success { get; set; }
public required List<PendingAction> Actions { get; set; }
}
public class ActionCompleteRequest
{
public ActionStatus Status { get; set; }
public object? Response_data { get; set; }
public ActionCompleteRequest(ActionStatus status, object? responseData = null)
{
Status = status;
Response_data = responseData;
}
}
public class ActionCompleteResponse
{
public bool Success { get; set; }
public required string Message { get; set; }
}
public enum ActionStatus
{
Pending,
Processing,
Completed,
Failed,
Read
}
public class UpdatePlayersRequest
{
public object? TargetPlayerIds { get; set; }
public required string Type { get; set; }
public required object DataJson { get; set; }
[SetsRequiredMembers]
public UpdatePlayersRequest(object targetPlayerIds, string type, object dataJson)
{
TargetPlayerIds = targetPlayerIds;
Type = type;
DataJson = dataJson;
}
}
public class UpdatePlayersResponse
{
public bool Success { get; set; }
public int Updates_sent { get; set; }
public required List<string> Update_ids { get; set; }
public required List<string> Target_players { get; set; }
}
public class PlayerUpdate
{
public required string Update_id { get; set; }
public required string From_player_id { get; set; }
public required string Type { get; set; }
public required object Data_json { get; set; }
public required string Created_at { get; set; }
}
public class PollUpdatesResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required List<PlayerUpdate> Updates { get; set; }
public required string Last_update_id { get; set; }
}
public class CurrentRoomInfo
{
public required string Room_id { get; set; }
public required string Room_name { get; set; }
public bool Is_host { get; set; }
public bool Is_online { get; set; }
public int Max_players { get; set; }
public int Current_players { get; set; }
public bool Has_password { get; set; }
public bool Is_active { get; set; }
public required string Player_name { get; set; }
public required string Joined_at { get; set; }
public required string Last_heartbeat { get; set; }
public required string Room_created_at { get; set; }
public required string Room_last_activity { get; set; }
}
public class CurrentRoomResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public bool In_room { get; set; }
public CurrentRoomInfo? Room { get; set; }
public List<object>? Pending_actions { get; set; }
public List<object>? Pending_updates { get; set; }
}
public class MatchmakingListResponse
{
public bool Success { get; set; }
public required List<MatchmakingLobby> Lobbies { get; set; }
}
public class MatchmakingLobby
{
public required string Matchmaking_id { get; set; }
public int Host_player_id { get; set; }
public int Max_players { get; set; }
public int Strict_full { get; set; }
public object? Extra_json_string { get; set; }
public required string Created_at { get; set; }
public required string Last_heartbeat { get; set; }
public int Current_players { get; set; }
public required string Host_name { get; set; }
}
public class MatchmakingCreateResponse
{
public bool Success { get; set; }
public required string Matchmaking_id { get; set; }
public int Max_players { get; set; }
public bool Strict_full { get; set; }
public bool Join_by_requests { get; set; }
public bool Is_host { get; set; }
}
public class MatchmakingJoinRequestResponse
{
public bool Success { get; set; }
public required string Request_id { get; set; }
public required string Message { get; set; }
}
public class MatchmakingRequestResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
public required string Request_id { get; set; }
public required string Action { get; set; }
}
public class MatchmakingRequestStatusResponse
{
public bool Success { get; set; }
public required MatchmakingRequestInfo Request { get; set; }
}
public class MatchmakingRequestInfo
{
public required string Request_id { get; set; }
public required string Matchmaking_id { get; set; }
public required string Status { get; set; }
public required string Requested_at { get; set; }
public string? Responded_at { get; set; }
public int? Responded_by { get; set; }
public string? Responder_name { get; set; }
public bool Join_by_requests { get; set; }
}
public class MatchmakingCurrentResponse
{
public bool Success { get; set; }
public bool In_matchmaking { get; set; }
public MatchmakingInfo? Matchmaking { get; set; }
public required List<object> Pending_requests { get; set; }
}
public class MatchmakingInfo
{
public required string Matchmaking_id { get; set; }
public bool Is_host { get; set; }
public int Max_players { get; set; }
public int Current_players { get; set; }
public bool Strict_full { get; set; }
public bool Join_by_requests { get; set; }
public object? Extra_json_string { get; set; }
public required string Joined_at { get; set; }
public required string Player_status { get; set; }
public required string Last_heartbeat { get; set; }
public required string Lobby_heartbeat { get; set; }
public bool Is_started { get; set; }
public object? Started_at { get; set; }
}
public class MatchmakingDirectJoinResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
public required string Matchmaking_id { get; set; }
}
public class MatchmakingLeaveResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
}
public class MatchmakingPlayersResponse
{
public bool Success { get; set; }
public required List<MatchmakingPlayer> Players { get; set; }
}
public class MatchmakingPlayer
{
public int Player_id { get; set; }
public required string Joined_at { get; set; }
public required string Last_heartbeat { get; set; }
public required string Status { get; set; }
public required string Player_name { get; set; }
public int Seconds_since_heartbeat { get; set; }
public int Is_host { get; set; }
}
public class MatchmakingHeartbeatResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public string? Status { get; set; } // Made optional for error responses
}
public class MatchmakingRemoveResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Message { get; set; }
}
public class MatchmakingStartResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
public required string Room_id { get; set; }
public required string Room_name { get; set; }
public int Players_transferred { get; set; }
public required string Message { get; set; }
}
public class LeaderboardResponse
{
public bool Success { get; set; }
public required List<LeaderboardPlayer> Leaderboard { get; set; }
public int Total { get; set; }
public required string[] Sort_by { get; set; }
public int Limit { get; set; }
}
public class LeaderboardPlayer
{
public int Rank { get; set; }
public int Player_id { get; set; }
public required string Player_name { get; set; }
public required Dictionary<string, object> Player_data { get; set; }
}
// Exception and Error Handling Classes
public class ApiException : Exception
{
public string? ErrorResponse { get; }
public string? ApiError { get; }
public ApiException(string message, string? errorResponse = null) : base(message)
{
ErrorResponse = errorResponse;
ApiError = message;
}
public ApiException(string message, string? errorResponse, Exception innerException) : base(message, innerException)
{
ErrorResponse = errorResponse;
ApiError = message;
}
}
public class ErrorResponse : IApiResponse
{
public bool Success { get; set; }
public string? Error { get; set; }
}
}
Here's a complete example of how to use the michitai SDK in your C# application. This example demonstrates common operations like fetching game data, updating player information, and handling authentication.
using System;
using System.Threading.Tasks;
using System.Text.Json;
using System.Collections.Generic;
using michitai;
public class Game
{
private static GameSDK? sdk;
private static Dictionary<string, PlayerInfo> players = new Dictionary<string, PlayerInfo>();
public static async Task Main()
{
Console.WriteLine("=== MICHITAI Game SDK Usage Example ===\n");
// 1️⃣ Initialize SDK
sdk = new GameSDK("YOUR_API_TOKEN", "YOUR_API_PRIVATE_TOKEN");
Console.WriteLine("[INIT] SDK initialized\n");
// 2️⃣ Register Multiple Players for Matchmaking Demo
Console.WriteLine("[PLAYERS] Registering multiple players for matchmaking demo...");
// Register Host Player
var hostReg = await RegisterPlayer("GameHost", new { level = 10, rank = "gold", role = "host" });
string hostToken = hostReg.Private_key;
string hostId = hostReg.Player_id;
Console.WriteLine($"[HOST] Registered: ID={hostId}, Token={hostToken.Substring(0, 8)}...\n");
// Register Multiple Players
var player1Reg = await RegisterPlayer("Player1", new { level = 8, rank = "silver", role = "player" });
var player2Reg = await RegisterPlayer("Player2", new { level = 12, rank = "gold", role = "player" });
var player3Reg = await RegisterPlayer("Player3", new { level = 6, rank = "bronze", role = "player" });
players["host"] = new PlayerInfo { Id = hostId, Token = hostToken, Name = "GameHost" };
players["player1"] = new PlayerInfo { Id = player1Reg.Player_id, Token = player1Reg.Private_key, Name = "Player1" };
players["player2"] = new PlayerInfo { Id = player2Reg.Player_id, Token = player2Reg.Private_key, Name = "Player2" };
players["player3"] = new PlayerInfo { Id = player3Reg.Player_id, Token = player3Reg.Private_key, Name = "Player3" };
Console.WriteLine($"[PLAYERS] Total registered: {players.Count} players\n");
// 3️⃣ Authenticate All Players
Console.WriteLine("[AUTH] Authenticating all players...");
foreach (var kvp in players)
{
var auth = await sdk.AuthenticatePlayer(kvp.Value.Token);
if (auth.Success)
{
Console.WriteLine($"[AUTH] {kvp.Value.Name} authenticated successfully");
}
}
Console.WriteLine();
// 4️⃣ List all players (admin view)
Console.WriteLine("[ADMIN] Fetching all players...");
var allPlayers = await sdk.GetAllPlayers();
Console.WriteLine($"[ADMIN] Total players in database: {allPlayers.Count}");
foreach (var p in allPlayers.Players)
{
Console.WriteLine($" - ID={p.Id}, Name={p.Player_name}, Active={p.Is_active}");
}
Console.WriteLine();
// 5️⃣ Get global game data
Console.WriteLine("[GAME] Loading game data...");
var gameData = await sdk.GetGameData();
Console.WriteLine($"[GAME] Game ID={gameData.Game_id}, Settings={gameData.Data.Count}\n");
// 6️⃣ Update global game data
Console.WriteLine("[GAME] Updating game settings...");
var updateGame = await sdk.UpdateGameData(new
{
game_settings = new { difficulty = "hard", max_players = 10, matchmaking_enabled = true },
last_updated = DateTime.UtcNow.ToString("o")
});
Console.WriteLine($"[GAME] {updateGame.Message} at {updateGame.Updated_at}\n");
// 7️⃣ Get player-specific data for host
Console.WriteLine("[HOST] Loading host player data...");
var hostData = await sdk.GetPlayerData(hostToken);
Console.WriteLine($"[HOST] {hostData.Player_name} - Level: {GetJsonValue(hostData.Data, "level")}, Rank: {GetJsonValue(hostData.Data, "rank")}\n");
// 8️⃣ Update host player data
Console.WriteLine("[HOST] Updating host progress...");
var updatedHost = await sdk.UpdatePlayerData(hostToken, new
{
level = 15,
rank = "platinum",
last_played = DateTime.UtcNow.ToString("o"),
matchmaking_status = "ready"
});
Console.WriteLine($"[HOST] {updatedHost.Message} at {updatedHost.Updated_at}\n");
// 9️⃣ Get server time
Console.WriteLine("[SERVER] Getting server time...");
var serverTime = await sdk.GetServerTime();
if (serverTime.Success)
{
Console.WriteLine($"[SERVER] Server time (UTC): {serverTime.Utc}");
Console.WriteLine($"[SERVER] Timestamp: {serverTime.Timestamp}");
Console.WriteLine($"[SERVER] Readable: {serverTime.Readable}\n");
}
else
{
Console.WriteLine("[SERVER] Failed to get server time\n");
}
// 9️⃣1️⃣ Get server time with +1 hour offset
Console.WriteLine("[SERVER] Getting server time with +1 hour offset...");
var serverTimePlus1 = await sdk.GetServerTimeWithOffset(1);
if (serverTimePlus1.Success)
{
Console.WriteLine($"[SERVER] Server time (+1h): {serverTimePlus1.Utc}");
Console.WriteLine($"[SERVER] Timestamp: {serverTimePlus1.Timestamp}");
Console.WriteLine($"[SERVER] Readable: {serverTimePlus1.Readable}");
Console.WriteLine($"[SERVER] Offset: {serverTimePlus1.Offset.Offset_hours}h ({serverTimePlus1.Offset.Offset_string})");
Console.WriteLine($"[SERVER] Original UTC: {serverTimePlus1.Offset.Original_utc}\n");
}
else
{
Console.WriteLine("[SERVER] Failed to get server time with +1 offset\n");
}
// 9️⃣2️⃣ Get server time with -2 hours offset
Console.WriteLine("[SERVER] Getting server time with -2 hours offset...");
var serverTimeMinus2 = await sdk.GetServerTimeWithOffset(-2);
if (serverTimeMinus2.Success)
{
Console.WriteLine($"[SERVER] Server time (-2h): {serverTimeMinus2.Utc}");
Console.WriteLine($"[SERVER] Timestamp: {serverTimeMinus2.Timestamp}");
Console.WriteLine($"[SERVER] Readable: {serverTimeMinus2.Readable}");
Console.WriteLine($"[SERVER] Offset: {serverTimeMinus2.Offset.Offset_hours}h ({serverTimeMinus2.Offset.Offset_string})");
Console.WriteLine($"[SERVER] Original UTC: {serverTimeMinus2.Offset.Original_utc}\n");
}
else
{
Console.WriteLine("[SERVER] Failed to get server time with -2 offset\n");
}
// 🔟 Create a traditional room (for comparison)
Console.WriteLine("[ROOM] Creating a traditional game room...");
var roomCreate = await sdk.CreateRoomAsync(
hostToken,
"Traditional Room",
"test123",
4
);
string roomId = roomCreate.Room_id;
Console.WriteLine($"[ROOM] Created room: ID={roomId}, Name={roomCreate.Room_name}, Is Host={roomCreate.Is_host}\n");
// 1️⃣1️⃣ List all available rooms
Console.WriteLine("[ROOM] Fetching available rooms...");
var rooms = await sdk.GetRoomsAsync();
Console.WriteLine($"[ROOM] Found {rooms.Rooms.Count} room(s):");
foreach (var room in rooms.Rooms)
{
Console.WriteLine($" - ID: {room.Room_id}, Name: {room.Room_name}, Players: {room.Current_players}/{room.Max_players}");
}
Console.WriteLine();
// 1️⃣2️⃣ Leave the traditional room (cleanup)
Console.WriteLine("[ROOM] Leaving the traditional game room...");
try
{
// Send heartbeat before leaving room
Console.WriteLine("[ROOM] Sending heartbeat before leaving room...");
var heartbeat = await sdk.SendPlayerHeartbeatAsync(hostToken);
if (heartbeat.Success)
{
Console.WriteLine($"[ROOM] Heartbeat sent: {heartbeat.Message}");
}
var leaveRoom = await sdk.LeaveRoomAsync(hostToken);
Console.WriteLine($"[ROOM] {leaveRoom.Message}");
// Logout after leaving room
Console.WriteLine("[ROOM] Logging out after leaving room...");
var logout = await sdk.LogoutPlayerAsync(hostToken);
if (logout.Success)
{
Console.WriteLine($"[ROOM] {logout.Message}");
}
}
catch (ApiException ex)
{
Console.WriteLine($"[ROOM] Leave error: {ex.ApiError}");
}
// ==================== MATCHMAKING DEMO ====================
// 2️⃣4️⃣ List Matchmaking Lobbies
Console.WriteLine("[MATCHMAKING] Listing available matchmaking lobbies...");
var lobbies = await sdk.GetMatchmakingLobbiesAsync();
Console.WriteLine($"[MATCHMAKING] Found {lobbies.Lobbies.Count} lobby(ies):");
foreach (var lobby in lobbies.Lobbies)
{
Console.WriteLine($" - ID: {lobby.Matchmaking_id}, Host: {lobby.Host_name}, Players: {lobby.Current_players}/{lobby.Max_players}, Strict: {lobby.Strict_full == 1}");
}
Console.WriteLine();
// 2️⃣5️⃣ Create Matchmaking Lobby
Console.WriteLine("[MATCHMAKING] Host creating new matchmaking lobby...");
var matchmakingCreate = await sdk.CreateMatchmakingLobbyAsync(
hostToken,
maxPlayers: 4,
strictFull: true,
joinByRequests: false,
extraJsonString: new { minLevel = 5, rank = "silver", gameMode = "competitive" }
);
string matchmakingId = matchmakingCreate.Matchmaking_id;
Console.WriteLine($"[MATCHMAKING] Lobby created: ID={matchmakingId}, Max Players={matchmakingCreate.Max_players}, Host={matchmakingCreate.Is_host}");
Console.WriteLine($"[MATCHMAKING] Settings: Strict Full={matchmakingCreate.Strict_full}, Join by Requests={matchmakingCreate.Join_by_requests}\n");
// 2️⃣6️⃣ Request to Join Matchmaking (Player 1)
Console.WriteLine("[MATCHMAKING] Player1 requesting to join matchmaking...");
var joinRequest = await sdk.RequestToJoinMatchmakingAsync(players["player1"].Token, matchmakingId);
Console.WriteLine($"[MATCHMAKING] Join request sent: ID={joinRequest.Request_id}, Message={joinRequest.Message}\n");
// 2️⃣7️⃣ Respond to Join Request (Host approves)
Console.WriteLine("[MATCHMAKING] Host responding to join request...");
try
{
var approveRequest = await sdk.RespondToJoinRequestAsync(hostToken, matchmakingId, GameSDK.MatchmakingRequestAction.Approve);
Console.WriteLine($"[MATCHMAKING] Response: {approveRequest.Message}, Action={approveRequest.Action}, Request ID={approveRequest.Request_id}\n");
}
catch (ApiException ex)
{
Console.WriteLine($"[MATCHMAKING] Error responding to request: {ex.ApiError}");
Console.WriteLine($"[MATCHMAKING] Server response: {ex.ErrorResponse}\n");
}
// 2️⃣8️⃣ Check Join Request Status (Player 1)
Console.WriteLine("[MATCHMAKING] Player1 checking join request status...");
var requestStatus = await sdk.CheckJoinRequestStatusAsync(players["player1"].Token, joinRequest.Request_id);
if (requestStatus.Success)
{
var req = requestStatus.Request;
Console.WriteLine($"[MATCHMAKING] Request Status: {req.Status}, Responded by: {req.Responder_name} at {req.Responded_at}");
}
Console.WriteLine();
// 2️⃣9️⃣ Get Current Matchmaking Status (Host)
Console.WriteLine("[MATCHMAKING] Host checking current matchmaking status...");
var currentMatchmaking = await sdk.GetCurrentMatchmakingStatusAsync(hostToken);
if (currentMatchmaking.Success && currentMatchmaking.In_matchmaking)
{
var mm = currentMatchmaking.Matchmaking;
Console.WriteLine($"[MATCHMAKING] Host Status: In Lobby={currentMatchmaking.In_matchmaking}, Is Host={mm.Is_host}");
Console.WriteLine($"[MATCHMAKING] Lobby: {mm.Matchmaking_id}, Players: {mm.Current_players}/{mm.Max_players}");
Console.WriteLine($"[MATCHMAKING] Settings: Strict={mm.Strict_full}, Approval={mm.Join_by_requests}, Started={mm.Is_started}");
}
Console.WriteLine();
// 3️⃣0️⃣ Join Matchmaking Directly (Player 2 - lobby allows direct join)
Console.WriteLine("[MATCHMAKING] Player2 joining directly (no approval required for this demo)...");
var directJoin = await sdk.JoinMatchmakingDirectlyAsync(players["player2"].Token, matchmakingId);
Console.WriteLine($"[MATCHMAKING] {directJoin.Message}, Lobby ID={directJoin.Matchmaking_id}\n");
// 3️⃣1️⃣ Leave Matchmaking (Player 1 leaves to rejoin)
Console.WriteLine("[MATCHMAKING] Player1 leaving matchmaking to test rejoin...");
try
{
var leaveMatchmaking = await sdk.LeaveMatchmakingAsync(players["player1"].Token);
Console.WriteLine($"[MATCHMAKING] {leaveMatchmaking.Message}\n");
}
catch (ApiException ex)
{
Console.WriteLine($"[MATCHMAKING] Leave error: {ex.ApiError}");
Console.WriteLine($"[MATCHMAKING] Note: Player1 was not in matchmaking lobby\n");
}
// 3️⃣2️⃣ List Matchmaking Players (Host view)
Console.WriteLine("[MATCHMAKING] Host listing players in matchmaking lobby...");
var matchmakingPlayers = await sdk.GetMatchmakingPlayersAsync(hostToken);
Console.WriteLine($"[MATCHMAKING] Players in lobby ({matchmakingPlayers.Players.Count}):");
foreach (var player in matchmakingPlayers.Players)
{
Console.WriteLine($" - {player.Player_name} (ID: {player.Player_id}, Host: {player.Is_host == 1}, Status: {player.Status}, Heartbeat: {player.Seconds_since_heartbeat}s ago)");
}
Console.WriteLine();
// 3️⃣3️⃣ Send Matchmaking Heartbeat (All players)
Console.WriteLine("[MATCHMAKING] All players sending heartbeats...");
foreach (var kvp in players)
{
if (kvp.Key != "player1") // Player1 left
{
try
{
var heartbeat = await sdk.SendMatchmakingHeartbeatAsync(kvp.Value.Token);
Console.WriteLine($"[MATCHMAKING] {kvp.Value.Name} heartbeat: {heartbeat.Status ?? "N/A"}");
}
catch (ApiException ex)
{
Console.WriteLine($"[MATCHMAKING] {kvp.Value.Name} heartbeat error: {ex.ApiError}");
}
}
}
Console.WriteLine();
// Player1 rejoins
Console.WriteLine("[MATCHMAKING] Player1 rejoining matchmaking...");
var rejoin = await sdk.JoinMatchmakingDirectlyAsync(players["player1"].Token, matchmakingId);
Console.WriteLine($"[MATCHMAKING] {rejoin.Message}\n");
// 3️⃣4️⃣ Remove Matchmaking Lobby (Host removes - cleanup)
Console.WriteLine("[MATCHMAKING] Host removing matchmaking lobby (cleanup test)...");
var removeLobby = await sdk.RemoveMatchmakingLobbyAsync(hostToken);
Console.WriteLine($"[MATCHMAKING] {removeLobby.Message}\n");
// Create new lobby for game start demo
Console.WriteLine("[MATCHMAKING] Creating new lobby for game start demo...");
var newLobby = await sdk.CreateMatchmakingLobbyAsync(
hostToken,
maxPlayers: 3,
strictFull: false,
joinByRequests: false
);
string newMatchmakingId = newLobby.Matchmaking_id;
Console.WriteLine($"[MATCHMAKING] New lobby created: {newMatchmakingId}\n");
// All players join the new lobby
Console.WriteLine("[MATCHMAKING] All players joining new lobby...");
foreach (var kvp in players)
{
if (kvp.Key != "host") // Host is already in
{
try
{
var join = await sdk.JoinMatchmakingDirectlyAsync(kvp.Value.Token, newMatchmakingId);
Console.WriteLine($"[MATCHMAKING] {kvp.Value.Name}: {join.Message}");
}
catch (ApiException ex)
{
Console.WriteLine($"[MATCHMAKING] {kvp.Value.Name}: Join error - {ex.ApiError}");
}
}
}
Console.WriteLine();
// 3️⃣5️⃣ Start Game from Matchmaking (Host starts the game)
Console.WriteLine("[MATCHMAKING] Host starting game from matchmaking lobby...");
var startGame = await sdk.StartGameFromMatchmakingAsync(hostToken);
Console.WriteLine($"[MATCHMAKING] {startGame.Message}");
Console.WriteLine($"[MATCHMAKING] Game Room Created: ID={startGame.Room_id}, Name={startGame.Room_name}");
Console.WriteLine($"[MATCHMAKING] Players Transferred: {startGame.Players_transferred}\n");
// ==================== POST-MATCHMAKING GAME ROOM DEMO ====================
// Now the host is also a room host - demonstrate room functionality
Console.WriteLine("[POST-MATCHMAKING] Demonstrating room functionality with matchmaking host...");
// 1️⃣2️⃣ List all available rooms (should include the new game room)
Console.WriteLine("[ROOM] Fetching available rooms after matchmaking...");
var postRooms = await sdk.GetRoomsAsync();
Console.WriteLine($"[ROOM] Found {postRooms.Rooms.Count} room(s):");
foreach (var room in postRooms.Rooms)
{
Console.WriteLine($" - ID: {room.Room_id}, Name: {room.Room_name}, Players: {room.Current_players}/{room.Max_players}");
}
Console.WriteLine();
// 1️⃣3️⃣ List players in the new game room
Console.WriteLine("[ROOM] Fetching players in the new game room...");
var newRoomPlayers = await sdk.GetRoomPlayersAsync(hostToken);
Console.WriteLine($"[ROOM] Players in new room ({newRoomPlayers.Players.Count}):");
foreach (var player in newRoomPlayers.Players)
{
Console.WriteLine($" - {player.Player_name} (ID: {player.Player_id}, Host: {player.Is_host == 1}, Online: {player.Is_online})");
}
Console.WriteLine();
// 1️⃣5️⃣ Submit actions from different players
Console.WriteLine("[ACTION] Players submitting actions in the new game room...");
foreach (var kvp in players)
{
try
{
var action = await sdk.SubmitActionAsync(
kvp.Value.Token,
"player_ready",
new { player_id = kvp.Value.Id, ready = true, timestamp = DateTime.UtcNow.ToString("o") }
);
Console.WriteLine($"[ACTION] {kvp.Value.Name} submitted ready action: {action.Action_id}");
}
catch (ApiException ex)
{
Console.WriteLine($"[ACTION] {kvp.Value.Name}: Action submit error - {ex.ApiError}");
}
}
Console.WriteLine();
// 1️⃣6️⃣ Check pending actions
Console.WriteLine("[ACTION] Checking for pending actions...");
var pendingActions = await sdk.GetPendingActionsAsync(hostToken);
if (pendingActions.Actions != null && pendingActions.Actions.Count > 0)
{
Console.WriteLine($"[ACTION] Found {pendingActions.Actions.Count} pending actions:");
foreach (var pendingAction in pendingActions.Actions)
{
Console.WriteLine($"[ACTION] - {pendingAction.Player_name}: {pendingAction.Action_type} at {pendingAction.Created_at}");
}
}
Console.WriteLine();
// 2️⃣0️⃣ Send game start update to all players
Console.WriteLine("[UPDATE] Host sending game start update to all players...");
var gameStartUpdate = await sdk.UpdatePlayersAsync(
hostToken,
new UpdatePlayersRequest(
"all",
"game_start",
new { game_mode = "competitive", start_time = DateTime.UtcNow.ToString("o") }
)
);
Console.WriteLine($"[UPDATE] Game start update sent to {gameStartUpdate.Updates_sent} players\n");
// 2️⃣2️⃣ Poll for game updates
Console.WriteLine("[UPDATE] Players polling for game updates...");
foreach (var kvp in players)
{
try
{
var pollUpdates = await sdk.PollUpdatesAsync(kvp.Value.Token);
if (pollUpdates.Updates.Count > 0)
{
Console.WriteLine($"[UPDATE] {kvp.Value.Name} received {pollUpdates.Updates.Count} update(s):");
foreach (var update in pollUpdates.Updates)
{
Console.WriteLine($"[UPDATE] - {update.Type} from {update.From_player_id} at {update.Created_at}");
}
}
else
{
Console.WriteLine($"[UPDATE] {kvp.Value.Name}: No new updates");
}
}
catch (ApiException ex)
{
Console.WriteLine($"[UPDATE] {kvp.Value.Name}: Poll error - {ex.ApiError}");
}
}
Console.WriteLine();
// 2️⃣3️⃣ Get current room information for all players
Console.WriteLine("[ROOM] All players checking current room status...");
foreach (var kvp in players)
{
try
{
var currentRoom = await sdk.GetCurrentRoomAsync(kvp.Value.Token);
if (currentRoom.Success && currentRoom.In_room && currentRoom.Room != null)
{
var room = currentRoom.Room;
Console.WriteLine($"[ROOM] {kvp.Value.Name}: In '{room.Room_name}' (Players: {room.Current_players}/{room.Max_players}, Host: {room.Is_host})");
}
else
{
Console.WriteLine($"[ROOM] {kvp.Value.Name}: Not in any room");
}
}
catch (ApiException ex)
{
Console.WriteLine($"[ROOM] {kvp.Value.Name}: Room status error - {ex.ApiError}");
}
}
Console.WriteLine();
// 2️⃣4️⃣ Leave the room (all players)
Console.WriteLine("[ROOM] All players leaving the game room...");
foreach (var kvp in players)
{
try
{
// Send heartbeat before leaving room
Console.WriteLine($"[ROOM] {kvp.Value.Name}: Sending heartbeat before leaving room...");
var heartbeat = await sdk.SendPlayerHeartbeatAsync(kvp.Value.Token);
if (heartbeat.Success)
{
Console.WriteLine($"[ROOM] {kvp.Value.Name}: Heartbeat sent: {heartbeat.Message}");
}
var leaveRoom = await sdk.LeaveRoomAsync(kvp.Value.Token);
Console.WriteLine($"[ROOM] {kvp.Value.Name}: {leaveRoom.Message}");
// Logout after leaving room
Console.WriteLine($"[ROOM] {kvp.Value.Name}: Logging out after leaving room...");
var logout = await sdk.LogoutPlayerAsync(kvp.Value.Token);
if (logout.Success)
{
Console.WriteLine($"[ROOM] {kvp.Value.Name}: {logout.Message}");
}
}
catch (ApiException ex)
{
Console.WriteLine($"[ROOM] {kvp.Value.Name}: Leave error - {ex.ApiError}");
}
}
Console.WriteLine();
// 2️⃣5️⃣ Leaderboards
Console.WriteLine("[LEADERBOARD] Testing leaderboard functionality...");
try
{
// Sort by level only
Console.WriteLine("[LEADERBOARD] Getting leaderboard sorted by level...");
var leaderboardLevel = await sdk.GetLeaderboardAsync(new[] { "level" }, 10);
if (leaderboardLevel.Success)
{
Console.WriteLine($"[LEADERBOARD] Success! Total players: {leaderboardLevel.Total}");
Console.WriteLine($"[LEADERBOARD] Sorted by: {string.Join(", ", leaderboardLevel.Sort_by)}");
Console.WriteLine("[LEADERBOARD] Top players:");
foreach (var player in leaderboardLevel.Leaderboard.Take(5))
{
Console.WriteLine($"[LEADERBOARD] Rank {player.Rank}: {player.Player_name} (ID: {player.Player_id})");
if (player.Player_data != null)
{
Console.WriteLine($"[LEADERBOARD] Level: {player.Player_data.GetValueOrDefault("level", "N/A")}");
Console.WriteLine($"[LEADERBOARD] Rank: {player.Player_data.GetValueOrDefault("rank", "N/A")}");
}
}
}
else
{
Console.WriteLine("[LEADERBOARD] Failed to get leaderboard by level");
}
Console.WriteLine();
// Sort by level then score
Console.WriteLine("[LEADERBOARD] Getting leaderboard sorted by level, then score...");
var leaderboardLevelScore = await sdk.GetLeaderboardAsync(new[] { "level", "score" }, 10);
if (leaderboardLevelScore.Success)
{
Console.WriteLine($"[LEADERBOARD] Success! Total players: {leaderboardLevelScore.Total}");
Console.WriteLine($"[LEADERBOARD] Sorted by: {string.Join(", ", leaderboardLevelScore.Sort_by)}");
Console.WriteLine("[LEADERBOARD] Top players:");
foreach (var player in leaderboardLevelScore.Leaderboard.Take(5))
{
Console.WriteLine($"[LEADERBOARD] Rank {player.Rank}: {player.Player_name} (ID: {player.Player_id})");
if (player.Player_data != null)
{
Console.WriteLine($"[LEADERBOARD] Level: {player.Player_data.GetValueOrDefault("level", "N/A")}");
Console.WriteLine($"[LEADERBOARD] Score: {player.Player_data.GetValueOrDefault("score", "N/A")}");
var inventory = player.Player_data.GetValueOrDefault("inventory", null);
if (inventory != null && inventory.ToString() != "")
{
Console.WriteLine($"[LEADERBOARD] Inventory: {inventory}");
}
}
}
}
else
{
Console.WriteLine("[LEADERBOARD] Failed to get leaderboard by level and score");
}
Console.WriteLine();
// Sort by score then level
Console.WriteLine("[LEADERBOARD] Getting leaderboard sorted by score, then level...");
var leaderboardScoreLevel = await sdk.GetLeaderboardAsync(new[] { "score", "level" }, 10);
if (leaderboardScoreLevel.Success)
{
Console.WriteLine($"[LEADERBOARD] Success! Total players: {leaderboardScoreLevel.Total}");
Console.WriteLine($"[LEADERBOARD] Sorted by: {string.Join(", ", leaderboardScoreLevel.Sort_by)}");
Console.WriteLine("[LEADERBOARD] Top players:");
foreach (var player in leaderboardScoreLevel.Leaderboard.Take(5))
{
Console.WriteLine($"[LEADERBOARD] Rank {player.Rank}: {player.Player_name} (ID: {player.Player_id})");
if (player.Player_data != null)
{
Console.WriteLine($"[LEADERBOARD] Score: {player.Player_data.GetValueOrDefault("score", "N/A")}");
Console.WriteLine($"[LEADERBOARD] Level: {player.Player_data.GetValueOrDefault("level", "N/A")}");
var inventory = player.Player_data.GetValueOrDefault("inventory", null);
if (inventory != null && inventory.ToString() != "")
{
Console.WriteLine($"[LEADERBOARD] Inventory: {inventory}");
}
}
}
}
else
{
Console.WriteLine("[LEADERBOARD] Failed to get leaderboard by score and level");
}
}
catch (ApiException ex)
{
Console.WriteLine($"[LEADERBOARD] Error: {ex.ApiError}");
}
Console.WriteLine();
Console.WriteLine("=== Complete Matchmaking & Game Demo ===");
Console.WriteLine("✅ Multiple players registered and authenticated");
Console.WriteLine("✅ Matchmaking lobby created with custom settings");
Console.WriteLine("✅ Join requests and approval system demonstrated");
Console.WriteLine("✅ Direct join functionality tested");
Console.WriteLine("✅ Player management and heartbeats working");
Console.WriteLine("✅ Game started from matchmaking lobby");
Console.WriteLine("✅ Host transitioned from matchmaking to room host");
Console.WriteLine("✅ Full game room functionality demonstrated");
Console.WriteLine("✅ Leaderboard system with multiple sorting options tested");
}
private static async Task<PlayerRegisterResponse> RegisterPlayer(string name, object playerData)
{
Console.WriteLine($"[PLAYER] Registering {name}...");
var reg = await sdk.RegisterPlayer(name, playerData);
Console.WriteLine($"[PLAYER] {name} registered: ID={reg.Player_id}");
return reg;
}
private static string GetJsonValue(Dictionary<string, object> data, string key)
{
if (data.ContainsKey(key))
{
return data[key].ToString();
}
return "N/A";
}
private class PlayerInfo
{
public required string Id { get; set; }
public required string Token { get; set; }
public required string Name { get; set; }
}
public class ConsoleLogger : ILogger
{
public virtual void Error(string message)
{
Console.WriteLine($"[Error] {message}");
}
public virtual void Log(string message)
{
Console.WriteLine($"[Log] {message}");
}
public virtual void Warn(string message)
{
Console.WriteLine($"[Warning] {message}");
}
}
#if UNITY_EDITOR
public class UnityLogger : ILogger
{
public virtual void Error(string message)
{
Debug.Log($"[Error] {message}");
}
public virtual void Log(string message)
{
Debug.LogWarning($"[Log] {message}");
}
public virtual void Warn(string message)
{
Debug.LogError($"[Warning] {message}");
}
}
#endif
}
SDK.cs file to your projectProgram.cs fileyour-api-key-here with your actual API keyUse our REST API directly from any platform or language. All endpoints return JSON responses with consistent error handling.
Description: Creates a new player in the game. Returns a player_id and private_key needed for future requests.
{
"player_name": "TestPlayer",
"player_data": {
"level": 1,
"score": 0,
"inventory": ["sword","shield"]
}
}
{
"success": true,
"player_id": "7",
"private_key": "46702c9b906e3361c26dbcd605ee9183",
"player_name": "TestPlayer",
"game_id": 4
}
Description: Updates player info such as active status. This does not change player data like level or inventory (those are in /game_data.php).
{}
{
"success": true,
"player": {
"id": 7,
"game_id": 4,
"player_name": "TestPlayer",
"player_data": {
"level": 1,
"score": 0,
"inventory": ["sword","shield"]
},
"is_active": 1,
"last_login": null,
"created_at": "2026-01-13 14:21:16",
"updated_at": "2026-01-13 14:21:16"
}
}
Description: Update player heartbeat to track activity and maintain online status.
{}
{
"success": true,
"message": "Heartbeat updated",
"last_heartbeat": "2026-03-13 09:46:13"
}
Description: Logout player and update last_logout timestamp. Sets is_active to 0.
{}
{
"success": true,
"message": "Player logged out successfully",
"last_logout": "2026-03-13 09:46:37"
}
Description: Retrieves a list of all players in the game. Useful for admin dashboards or multiplayer matchmaking.
{}
{
"success": true,
"count": 7,
"players": [
{"id":3,"player_name":"TestPlayer","is_active":1,"last_login":null,"created_at":"2026-01-13 12:30:47"},
{"id":7,"player_name":"TestPlayer","is_active":1,"last_login":"2026-01-13 14:22:33","created_at":"2026-01-13 14:21:16"}
}
Description: Retrieves the global game data, including text, settings, and last update timestamp. Used to sync clients with the server.
{}
{
"success": true,
"type": "game",
"game_id": 4,
"data": {
"text": "hello world",
"game_settings": {
"difficulty": "hard",
"max_players": 10
},
"last_updated": "2025-01-13T12:00:00Z"
}
}
Description: Updates global game data. For example, changing settings or max players. Requires API key authentication.
{
"game_settings": {
"difficulty": "hard",
"max_players": 10
},
"last_updated": "2025-01-13T12:00:00Z"
}
{
"success": true,
"message": "Game data updated successfully",
"updated_at": "2026-01-13 14:24:23"
}
Description: Retrieves a specific player's data using their private_key. Includes level, score, and inventory.
{}
{
"success": true,
"type": "player",
"player_id": 7,
"player_name": "TestPlayer",
"data": {
"level": 1,
"score": 0,
"inventory": ["sword","shield"]
}
}
Description: Updates a specific player's data like level, score, inventory, and last played timestamp.
{
"level": 2,
"score": 100,
"inventory": ["sword","shield","potion"],
"last_played": "2025-01-13T12:30:00Z"
}
{
"success": true,
"message": "Player data updated successfully",
"updated_at": "2026-01-13 14:27:10"
}
Description: Retrieves current server time in multiple formats including UTC timestamp and human-readable format.
{}
{
"success": true,
"utc": "2026-03-13T14:01:49+00:00",
"timestamp": 1773410509,
"readable": "2026-03-13 14:01:49 UTC"
}
Description: Retrieves server time with +1 hour offset adjustment.
{}
{
"success": true,
"utc": "2026-03-13T15:04:17+00:00",
"timestamp": 1773414257,
"readable": "2026-03-13 15:04:17 UTC",
"offset": {
"offset_hours": 1,
"offset_string": "+1",
"original_utc": "2026-03-13T14:04:17+00:00",
"original_timestamp": 1773410657
}
}
Description: Retrieves server time with -2 hours offset adjustment.
{}
{
"success": true,
"utc": "2026-03-13T12:05:13+00:00",
"timestamp": 1773403513,
"readable": "2026-03-13 12:05:13 UTC",
"offset": {
"offset_hours": -2,
"offset_string": "-2",
"original_utc": "2026-03-13T14:05:13+00:00",
"original_timestamp": 1773410713
}
}
Description: Lists all available matchmaking lobbies that are not full and not started.
{}
{
"success": true,
"lobbies": [
{
"matchmaking_id": "15b2b6e5f0ba44b5eef77705d120861f",
"host_player_id": 62,
"max_players": 4,
"strict_full": 1,
"extra_json_string": {"minLevel":10,"rank":"gold"},
"created_at": "2026-03-10 15:16:58",
"last_heartbeat": "2026-03-10 15:16:58",
"current_players": 1,
"host_name": "TestPlayer"
}
]
}
Description: Creates a new matchmaking lobby. The creating player automatically becomes the host.
{
"maxPlayers": 4,
"strictFull": true,
"joinByRequests": true,
"extraJsonString": {"minLevel":10,"rank":"gold"}
}
{
"success": true,
"matchmaking_id": "636b3ffc9b30dc9c918d8a49661df078",
"max_players": 4,
"strict_full": true,
"join_by_requests": true,
"is_host": true
}
Description: Requests to join a matchmaking lobby that requires host approval.
{}
{
"success": true,
"request_id": "82334acd88f0af6a1f4747bbe755263a",
"message": "Join request sent to host"
}
Description: Host responds to a join request (approve or reject).
{
"action": "approve"
}
{
"success": true,
"message": "Join request approved successfully",
"request_id": "f4d90025b5de54e6b1a83940cffb4490",
"action": "approve"
}
Description: Host rejects a join request.
{
"action": "reject"
}
{
"success": true,
"message": "Join request rejected successfully",
"request_id": "f4d90025b5de54e6b1a83940cffb4490",
"action": "reject"
}
Description: Checks the status of a join request.
{}
{
"success": true,
"request": {
"request_id": "f4d90025b5de54e6b1a83940cffb4490",
"matchmaking_id": "15b2b6e5f0ba44b5eef77705d120861f",
"status": "approved",
"requested_at": "2026-03-10 16:10:23",
"responded_at": "2026-03-10 16:18:43",
"responded_by": 62,
"responder_name": "TestPlayer",
"join_by_requests": true
}
}
Description: Gets the current player's matchmaking status and lobby information.
{}
{
"success": true,
"in_matchmaking": true,
"matchmaking": {
"matchmaking_id": "636b3ffc9b30dc9c918d8a49661df078",
"is_host": true,
"max_players": 4,
"current_players": 1,
"strict_full": true,
"join_by_requests": false,
"extra_json_string": {"minLevel":10,"rank":"gold"},
"joined_at": "2026-03-06 17:23:53",
"player_status": "active",
"last_heartbeat": "2026-03-06 17:23:53",
"lobby_heartbeat": "2026-03-06 17:24:37",
"is_started": false,
"started_at": null
},
"pending_requests": []
}
Description: Joins a matchmaking lobby directly (only works if lobby doesn't require approval).
{}
{
"success": true,
"matchmaking_id": "15b2b6e5f0ba44b5eef77705d120861f",
"message": "Successfully joined matchmaking lobby"
}
Description: Leaves the current matchmaking lobby.
{}
{
"success": true,
"message": "Successfully left matchmaking lobby"
}
Description: Lists all players in the current matchmaking lobby.
{}
{
"success": true,
"players": [
{
"player_id": 47,
"joined_at": "2026-03-06 17:23:53",
"last_heartbeat": "2026-03-06 17:23:53",
"status": "active",
"player_name": "TestPlayer",
"seconds_since_heartbeat": 726,
"is_host": 1
},
{
"player_id": 46,
"joined_at": "2026-03-06 17:35:01",
"last_heartbeat": "2026-03-06 17:35:01",
"status": "active",
"player_name": "TestPlayer",
"seconds_since_heartbeat": 58,
"is_host": 0
}
],
"last_updated": "2026-03-06T17:35:59+00:00"
}
Description: Sends a heartbeat to keep the player active in the matchmaking lobby.
{}
{
"success": true,
"status": "ok"
}
Description: Removes the matchmaking lobby (host only).
{}
{
"success": true,
"message": "Matchmaking lobby removed successfully"
}
Description: Starts a game from the matchmaking lobby (host only).
{}
{
"success": true,
"room_id": "c899e32506d44823d486585b247eafe5",
"room_name": "Game from Matchmaking 15b2b6",
"players_transferred": 2,
"message": "Game started successfully"
}
Description: Creates a new game room. The creating player automatically becomes the host.
{
"room_name": "My Game Room",
"password": "secret123",
"max_players": 4
}
{
"success": true,
"room_id": "dc3723848639139113ca240958ba0bf8",
"room_name": "My Game Room",
"is_host": true
}
Description: Returns list of currently available game rooms (not full, public/visible).
{}
{
"success": true,
"rooms": [
{
"room_id": "dc3723848639139113ca240958ba0bf8",
"room_name": "My Game Room",
"max_players": 4,
"current_players": 1,
"has_password": true
}
]
}
Description: Join an existing room by ID. Password is required if the room is protected.
{
"password": "secret123"
}
{
"success": true,
"room_id": "dc3723848639139113ca240958ba0bf8",
"message": "Successfully joined the room"
}
Description: Returns list of players currently in the same room as you.
{}
{
"success": true,
"players": [
{
"player_id": "1",
"player_name": "TestPlayer",
"is_host": false,
"is_online": true
}
],
"last_updated": "2026-01-15T16:06:42Z"
}
Description: Leave the current room. If you were the host, a new host may be assigned automatically.
{}
{
"success": true,
"message": "Successfully left the room"
}
Description: Updates player's last activity timestamp (used to determine online status).
{}
{
"success": true,
"status": "ok"
}
Description: Submits a new player action to the room's action queue (movement, attack, item use, etc.). The action is initially marked as pending and awaits server/host processing.
{
"action_type": "move",
"request_data": {
"x": 10,
"y": 20
}
}
{
"success": true,
"action_id": "1c2bbd859e36dc7d7e5e9b4f263c88ce",
"status": "pending"
}
Description: Retrieves recently completed/processed actions from the room. Clients should poll this endpoint regularly to receive updates on action results.
{}
{
"success": true,
"actions": [
{
"action_id": "1c2bbd859e36dc7d7e5e9b4f263c88ce",
"action_type": "move",
"response_data": "{\"success\":true,\"message\":\"Moved successfully\"}",
"status": "completed"
}
]
}
Description: Returns list of currently pending actions in the room. Typically used by the host or server-side logic to process/approve actions.
{}
{
"success": true,
"actions": [
{
"action_id": "1c2bbd859e36dc7d7e5e9b4f263c88ce",
"player_id": "1",
"action_type": "move",
"request_data": "{\"x\":10,\"y\":20}",
"created_at": "2026-01-15 16:10:43",
"player_name": "TestPlayer"
}
]
}
Description: Marks a pending action as completed and attaches the result/response data. Usually called by the room host or authoritative server.
{
"status": "completed",
"response_data": {
"success": true,
"message": "Moved successfully"
}
}
{
"success": true,
"message": "Action completed"
}
Description: Send updates to all players in the room (host only).
{
"targetPlayerIds": "all",
"type": "play_animation",
"dataJson": {
"animation": "victory",
"duration": 2.0
}
}
{
"success": true,
"updates_sent": 1,
"update_ids": ["ddb19c9d8722073762f5db33ff13712a"],
"target_players": ["47"]
}
Description: Send updates to specific players in the room (host only).
{
"targetPlayerIds": ["47"],
"type": "spawn_effect",
"dataJson": {
"effect": "explosion",
"position": {
"x": 10,
"y": 20
}
}
}
{
"success": true,
"updates_sent": 1,
"update_ids": ["377bfa1d4c56c3f72d9c87b0c081e6e8"],
"target_players": ["47"]
}
Description: Poll for updates sent to the current player.
{}
{
"success": true,
"updates": [
{
"update_id": "a28388775fcf9478c6926cbe44f9d3ed",
"from_player_id": "48",
"type": "play_animation",
"data_json": {
"animation": "victory",
"duration": 2
},
"created_at": "2026-03-09 10:51:40"
},
{
"update_id": "f26cbcdab3939b968f148edf68a9fe54",
"from_player_id": "48",
"type": "play_animation",
"data_json": {
"animation": "victory",
"duration": 2
},
"created_at": "2026-03-09 10:53:58"
}
],
"last_update_id": "f26cbcdab3939b968f148edf68a9fe54"
}
Description: Poll for updates after a specific update ID (incremental polling).
{}
{
"success": true,
"updates": [
{
"update_id": "f26cbcdab3939b968f148edf68a9fe54",
"from_player_id": "48",
"type": "play_animation",
"data_json": {
"animation": "victory",
"duration": 2
},
"created_at": "2026-03-09 10:53:58"
}
],
"last_update_id": "f26cbcdab3939b968f148edf68a9fe54"
}
Description: Get current game room status and player information.
{}
{
"success": true,
"in_room": true,
"room": {
"room_id": "05f157893b1237f7699e548d045904ab",
"room_name": "Game from Matchmaking 636b3f",
"is_host": false,
"is_online": true,
"max_players": 4,
"current_players": 2,
"has_password": false,
"is_active": true,
"player_name": "TestPlayer",
"joined_at": "2026-03-06 17:50:35",
"last_heartbeat": "2026-03-06 18:02:35",
"room_created_at": "2026-03-06 17:50:35",
"room_last_activity": "2026-03-06 18:04:58"
},
"pending_actions": [],
"pending_updates": []
}
Description: Get leaderboard sorted by player level (descending).
{
"sortBy": ["level"],
"limit": 10
}
{
"success": true,
"leaderboard": [
{
"rank": 1,
"player_id": 33,
"player_name": "GameHost",
"player_data": {
"level": 15,
"rank": "platinum",
"role": "host",
"last_played": "2026-03-13T14:28:06.9900113Z",
"matchmaking_status": "ready"
}
},
{
"rank": 2,
"player_id": 35,
"player_name": "Player2",
"player_data": {
"level": 12,
"rank": "gold",
"role": "player"
}
},
{
"rank": 3,
"player_id": 34,
"player_name": "Player1",
"player_data": {
"level": 8,
"rank": "silver",
"role": "player"
}
},
{
"rank": 4,
"player_id": 36,
"player_name": "Player3",
"player_data": {
"level": 6,
"rank": "bronze",
"role": "player"
}
}
],
"total": 4,
"sort_by": ["level"],
"limit": 10
}
Description: Get leaderboard sorted by level first, then score as tie-breaker.
{
"sortBy": ["level", "score"],
"limit": 10
}
{
"success": true,
"leaderboard": [
{
"rank": 1,
"player_id": 40,
"player_name": "TestPlayer4",
"player_data": {
"level": 20,
"score": 1000,
"inventory": []
}
},
{
"rank": 2,
"player_id": 37,
"player_name": "TestPlayer1",
"player_data": {
"level": 15,
"score": 4500,
"inventory": ["sword"]
}
},
{
"rank": 3,
"player_id": 38,
"player_name": "TestPlayer2",
"player_data": {
"level": 15,
"score": 2000,
"inventory": ["sword", "shield"]
}
},
{
"rank": 4,
"player_id": 41,
"player_name": "TestPlayer5",
"player_data": {
"level": 12,
"inventory": ["staff"]
}
},
{
"rank": 5,
"player_id": 39,
"player_name": "TestPlayer3",
"player_data": {
"level": 8,
"score": 3000,
"inventory": ["bow"]
}
}
],
"total": 5,
"sort_by": ["level", "score"],
"limit": 10
}
Description: Get leaderboard sorted by score first, then level as tie-breaker.
{
"sortBy": ["score", "level"],
"limit": 10
}
{
"success": true,
"leaderboard": [
{
"rank": 1,
"player_id": 37,
"player_name": "TestPlayer1",
"player_data": {
"level": 15,
"score": 4500,
"inventory": ["sword"]
}
},
{
"rank": 2,
"player_id": 39,
"player_name": "TestPlayer3",
"player_data": {
"level": 8,
"score": 3000,
"inventory": ["bow"]
}
},
{
"rank": 3,
"player_id": 38,
"player_name": "TestPlayer2",
"player_data": {
"level": 15,
"score": 2000,
"inventory": ["sword", "shield"]
}
},
{
"rank": 4,
"player_id": 40,
"player_name": "TestPlayer4",
"player_data": {
"level": 20,
"score": 1000,
"inventory": []
}
},
{
"rank": 5,
"player_id": 41,
"player_name": "TestPlayer5",
"player_data": {
"level": 12,
"inventory": ["staff"]
}
}
],
"total": 5,
"sort_by": ["score", "level"],
"limit": 10
}
Description: Shows what happens when a request is sent with an invalid or missing API key.
{
"success": false,
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key"
}
}