Multiplayer API Logo

Multiplayer API

Core Cells

Multiplayer API for
Game Developers

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.

Powerful Features

Everything you need to build, deploy, and scale multiplayer games

Per-Game API Keys

Each project gets a unique key for secure access and complete data isolation between games.

Shared User Base

Players register once and can access multiple games with a single account.

Multiplayer Logic

Built-in support for matchmaking, real-time game sessions, and player state management.

API Endpoints

Comprehensive REST API for multiplayer game development

Method
Endpoint
Description

Game Players

POST
/php/game_players.php
Register new Player with API key, returns Player private key
PUT
/php/game_players.php
Authenticate Player with API key and Player private key
GET
/php/game_players.php
List all Players with API key and API private key

Game Data

GET
/php/game_data.php
Get Game data (requires API key)
PUT
/php/game_data.php
Update Game data (requires API key, API private key)
GET
/php/game_data.php
Get Player data (requires API key, Player private key)
PUT
/php/game_data.php
Update Player data (requires API key, Player private key)

Server Data

GET
/php/time.php
Get server time (requires API key)

Game Rooms

POST
/php/game_room.php/rooms
Create a new game room (requires API key, Player private key)
GET
/php/game_room.php/rooms
List all available game rooms (requires API key)
POST
/php/game_room.php/rooms/{ID}/join
Join an existing room (requires API key, Player private key)
GET
/php/game_room.php/rooms/players
List all players in current room (requires API key, Player private key)
POST
/php/game_room.php/rooms/leave
Leave the current game room (requires API key, Player private key)
POST
/php/game_room.php/players/heartbeat
Send player activity heartbeat (requires API key, Player private key)
POST
/php/game_room.php/actions
Submit a new game action (requires API key, Player private key)
GET
/php/game_room.php/actions/poll
Get actions result (requires API key, Player private key)
GET
/php/game_room.php/actions/pending
View all pending actions (host only, requires API key, Player private key)
POST
/php/game_room.php/actions/{ID}/complete
Complete or reject action (host only, requires API key, Player private key)

SDK for Developers

Integrate our API into your game with our easy-to-use SDKs

C# / Unity SDK

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.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace michitai
{
    /// <summary>
    /// Provides a client for interacting with the MICHITAI Game API.
    /// Handles authentication, player management, game rooms, 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>
        /// 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();

            T response;

            try
            {
                response = JsonSerializer.Deserialize<T>(str, _jsonOptions)!;
            }
            catch(JsonException)
            {
                _logger?.Warn(str);

                throw;
            }

            return response;
        }

        /// <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"),
                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", $"&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", $"&api_private_token={_apiPrivateToken}"));
        }

        /// <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"));
        }

        /// <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", $"&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", $"&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", $"&game_player_token={playerToken}"),
                data
            );
        }

        /// <summary>
        /// Retrieves the current server time.
        /// </summary>
        /// <returns>Task containing the server time in various formats.</returns>
        public Task<ServerTimeResponse> GetServerTime()
        {
            return Send<ServerTimeResponse>(
                HttpMethod.Get,
                Url("time.php")
            );
        }

        /// <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/players/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
                }
            );
        }
    }



    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 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 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 class RoomPlayersResponse
    {
        public bool Success { get; set; }
        public required List<RoomPlayer> Players { get; set; }
        public required string Last_updated { get; set; }
    }

    public class RoomLeaveResponse
    {
        public bool Success { 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
    {
        public bool Success { 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 string? 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 required string 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
    }
}

Example Usage

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 michitai;

public class Game
{
    private static GameSDK? sdk;

    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");
        //sdk = new GameSDK("YOUR_API_TOKEN", "YOUR_API_PRIVATE_TOKEN", logger:new ConsoleLogger()); // for console logging
        //sdk = new GameSDK("YOUR_API_TOKEN", "YOUR_API_PRIVATE_TOKEN", logger:new UnityLogger()); // for unity logging
        Console.WriteLine("[INIT] SDK initialized\n");

        // 2️⃣ Register Player
        Console.WriteLine("[PLAYER] Registering new player...");
        var reg = await sdk.RegisterPlayer("TestPlayer", new
        {
            level = 1,
            score = 0,
            inventory = new[] { "sword", "shield" }
        });

        string playerToken = reg.Private_key;
        int playerId = int.Parse(reg.Player_id);

        Console.WriteLine($"[PLAYER] Registered: ID={playerId}, Token={playerToken}\n");

        // 3️⃣ Authenticate Player
        Console.WriteLine("[PLAYER] Authenticating player...");
        var auth = await sdk.AuthenticatePlayer(playerToken);

        if (auth.Success)
        {
            var pdata = auth.Player.Player_data;
            int level = pdata.ContainsKey("level") ? ((JsonElement)pdata["level"]).GetInt32() : 0;

            Console.WriteLine($"[PLAYER] Authenticated: {auth.Player.Player_name} (Level={level})\n");
        }
        else
        {
            Console.WriteLine("[PLAYER] Authentication failed\n");
        }

        // 4️⃣ List all players
        Console.WriteLine("[ADMIN] Fetching all players...");
        var allPlayers = await sdk.GetAllPlayers();
        Console.WriteLine($"[ADMIN] Total players: {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 },
            last_updated = DateTime.UtcNow.ToString("o")
        });
        Console.WriteLine($"[GAME] {updateGame.Message} at {updateGame.Updated_at}\n");

        // 7️⃣ Get player-specific data
        Console.WriteLine("[PLAYER] Loading player data...");
        var playerData = await sdk.GetPlayerData(playerToken);

        var pDataDict = playerData.Data;
        int playerLevel = pDataDict.ContainsKey("level") ? ((JsonElement)pDataDict["level"]).GetInt32() : 0;
        int playerScore = pDataDict.ContainsKey("score") ? ((JsonElement)pDataDict["score"]).GetInt32() : 0;
        string[] inventory = pDataDict.ContainsKey("inventory")
            ? JsonSerializer.Deserialize<string[]>(((JsonElement)pDataDict["inventory"]).GetRawText())!
            : new string[0];

        Console.WriteLine($"[PLAYER] Level={playerLevel}, Score={playerScore}, Inventory=[{string.Join(", ", inventory)}]\n");

        // 8️⃣ Update player data
        Console.WriteLine("[PLAYER] Updating player progress...");
        var updatedPlayer = await sdk.UpdatePlayerData(playerToken, new
        {
            level = 2,
            score = 100,
            inventory = new[] { "sword", "shield", "potion" },
            last_played = DateTime.UtcNow.ToString("o")
        });
        Console.WriteLine($"[PLAYER] {updatedPlayer.Message} at {updatedPlayer.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");
        }

        // 🔟 Create a new room
        Console.WriteLine("[ROOM] Creating a new game room...");
        var roomCreate = await sdk.CreateRoomAsync(
            playerToken,
            "Test 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️⃣ Join the room (as another player would)
        Console.WriteLine("[ROOM] Joining the room...");
        var joinRoom = await sdk.JoinRoomAsync(
            playerToken,
            roomId,
            "test123"
        );
        Console.WriteLine($"[ROOM] {joinRoom.Message}\n");

        // 1️⃣3️⃣ List players in the room
        Console.WriteLine("[ROOM] Fetching room players...");
        var roomPlayers = await sdk.GetRoomPlayersAsync(playerToken);
        Console.WriteLine($"[ROOM] Players in room ({roomPlayers.Players.Count}):");
        foreach (var player in roomPlayers.Players)
        {
            Console.WriteLine($" - {player.Player_name} (ID: {player.Player_id}, Host: {player.Is_host == 1}, Online: {player.Is_online})");
        }
        Console.WriteLine();

        // 1️⃣4️⃣ Send a heartbeat
        Console.WriteLine("[ROOM] Sending heartbeat...");
        var heartbeat = await sdk.SendHeartbeatAsync(playerToken);
        Console.WriteLine($"[ROOM] Heartbeat status: {heartbeat.Status}\n");

        // 1️⃣5️⃣ Submit an action
        Console.WriteLine("[ACTION] Submitting move action...");
        var action = await sdk.SubmitActionAsync(
            playerToken,
            "move",
            new { x = 10, y = 20 }
        );
        string actionId = action.Action_id;
        Console.WriteLine($"[ACTION] Action submitted: ID={actionId}, Status={action.Status}\n");

        // 1️⃣6️⃣ Check pending actions
        Console.WriteLine("[ACTION] Checking for pending actions...");
        var pendingActions = await sdk.GetPendingActionsAsync(playerToken);
        if (pendingActions.Actions != null && pendingActions.Actions.Count > 0)
        {
            foreach (var pendingAction in pendingActions.Actions)
            {
                Console.WriteLine($"[ACTION] Pending: {pendingAction.Action_type}, " +
                               $"Request: {pendingAction.Request_data}");
            }
        }
        else
        {
            Console.WriteLine("[ACTION] No pending actions found\n");
        }

        // 1️⃣7️⃣ Complete an action (simulating server response)
        if (!string.IsNullOrEmpty(actionId))
        {
            Console.WriteLine($"[ACTION] Completing action {actionId}...");
            var completeAction = await sdk.CompleteActionAsync(
                actionId,
                playerToken,
                new ActionCompleteRequest(ActionStatus.Completed,
                new { success = true, message = "Moved successfully" })
            );
            Console.WriteLine($"[ACTION] {completeAction.Message}\n");
        }

        // 1️⃣8️⃣ Poll for completed actions
        Console.WriteLine("[ACTION] Polling for completed actions...");
        var completedActions = await sdk.PollActionsAsync(playerToken);
        if (completedActions.Actions != null && completedActions.Actions.Count > 0)
        {
            foreach (var completedAction in completedActions.Actions)
            {
                Console.WriteLine($"[ACTION] Completed: {completedAction.Action_type}, " +
                               $"Result: {completedAction.Response_data}");
            }
        }
        else
        {
            Console.WriteLine("[ACTION] No completed actions found\n");
        }

        // 1️⃣9️⃣ Leave the room
        Console.WriteLine("[ROOM] Leaving the room...");
        var leaveRoom = await sdk.LeaveRoomAsync(playerToken);
        Console.WriteLine($"[ROOM] {leaveRoom.Message}\n");

        Console.WriteLine("=== Demo Complete ===");
    }



    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
}
How to use this example
  1. Create a new C# console application in Visual Studio or your preferred IDE
  2. Add the downloaded SDK.cs file to your project
  3. Copy this example code into your Program.cs file
  4. Replace your-api-key-here with your actual API key
  5. Run the application to see the SDK in action

Multiplayer API – Core Cells

Use our REST API directly from any platform or language. All endpoints return JSON responses with consistent error handling.

POST /v1/php/game_players.php?api_token=YOUR_API_KEY

Description: Creates a new player in the game. Returns a player_id and private_key needed for future requests.

Request Body:
{
  "player_name": "TestPlayer",
  "player_data": {
    "level": 1,
    "score": 0,
    "inventory": ["sword","shield"]
  }
}
Response:
{
  "success": true,
  "player_id": "7",
  "private_key": "46702c9b906e3361c26dbcd605ee9183",
  "player_name": "TestPlayer",
  "game_id": 4
}
PUT /v1/php/game_players.php?api_token=YOUR_API_KEY&game_player_token=PLAYER_PRIVATE_KEY

Description: Updates player info such as active status. This does not change player data like level or inventory (those are in /game_data.php).

Request Body:
{}
Response:
{
  "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"
  }
}
GET /v1/php/game_players.php?api_token=YOUR_API_KEY&api_private_token=YOUR_API_PRIVATE_TOKEN

Description: Retrieves a list of all players in the game. Useful for admin dashboards or multiplayer matchmaking.

Request Body:
{}
Response:
{
  "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"}
}
GET /v1/php/game_data.php?api_token=YOUR_API_KEY

Description: Retrieves the global game data, including text, settings, and last update timestamp. Used to sync clients with the server.

Request Body:
{}
Response:
{
  "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"
  }
}
PUT /v1/php/game_data.php?api_token=YOUR_API_KEY&api_private_token=YOUR_API_PRIVATE_TOKEN

Description: Updates global game data. For example, changing settings or max players. Requires API key authentication.

Request Body:
{
  "game_settings": {
    "difficulty": "hard",
    "max_players": 10
  },
  "last_updated": "2025-01-13T12:00:00Z"
}
Response:
{
  "success": true,
  "message": "Game data updated successfully",
  "updated_at": "2026-01-13 14:24:23"
}
GET /v1/php/game_data.php?api_token=YOUR_API_KEY&game_player_token=PLAYER_PRIVATE_KEY

Description: Retrieves a specific player's data using their private_key. Includes level, score, and inventory.

Request Body:
{}
Response:
{
  "success": true,
  "type": "player",
  "player_id": 7,
  "player_name": "TestPlayer",
  "data": {
    "level": 1,
    "score": 0,
    "inventory": ["sword","shield"]
  }
}
PUT /v1/php/game_data.php?api_token=YOUR_API_KEY&game_player_token=PLAYER_PRIVATE_KEY

Description: Updates a specific player's data like level, score, inventory, and last played timestamp.

Request Body:
{
  "level": 2,
  "score": 100,
  "inventory": ["sword","shield","potion"],
  "last_played": "2025-01-13T12:30:00Z"
}
Response:
{
  "success": true,
  "message": "Player data updated successfully",
  "updated_at": "2026-01-13 14:27:10"
}
GET /v1/php/time.php?api_key=YOUR_API_KEY

Description: Retrieves the current server time in multiple formats including UTC timestamp and human-readable format.

Request Body:
{}
Response:
{
  "success": true,
  "utc": "2025-01-14T16:24:00+00:00",
  "timestamp": 1736864640,
  "readable": "2025-01-14 16:24:00 UTC"
}
POST /v1/php/game_room.php/rooms?api_token=YOUR_API_TOKEN&game_player_token=YOUR_PLAYER_TOKEN

Description: Creates a new game room. The creating player automatically becomes the host.

Request Body:
{
  "room_name": "My Game Room",
  "password": "secret123",
  "max_players": 4
}
Response:
{
  "success": true,
  "room_id": "dc3723848639139113ca240958ba0bf8",
  "room_name": "My Game Room",
  "is_host": true
}
GET /v1/php/game_room.php/rooms?api_token=YOUR_API_TOKEN

Description: Returns list of currently available game rooms (not full, public/visible).

Request Body:
{}
Response:
{
  "success": true,
  "rooms": [
    {
      "room_id": "dc3723848639139113ca240958ba0bf8",
      "room_name": "My Game Room",
      "max_players": 4,
      "current_players": 1,
      "has_password": true
    }
  ]
}
POST /v1/php/game_room.php/rooms/{room_id}/join?api_token=YOUR_API_TOKEN&game_player_token=YOUR_PLAYER_TOKEN

Description: Join an existing room by ID. Password is required if the room is protected.

Request Body:
{
  "password": "secret123"
}
Response:
{
  "success": true,
  "room_id": "dc3723848639139113ca240958ba0bf8",
  "message": "Successfully joined the room"
}
GET /v1/php/game_room.php/players?api_token=YOUR_API_TOKEN&game_player_token=YOUR_PLAYER_TOKEN

Description: Returns list of players currently in the same room as you.

Request Body:
{}
Response:
{
  "success": true,
  "players": [
    {
      "player_id": "1",
      "player_name": "TestPlayer",
      "is_host": false,
      "is_online": true
    }
  ],
  "last_updated": "2026-01-15T16:06:42Z"
}
POST /v1/php/game_room.php/rooms/leave?api_token=YOUR_API_TOKEN&game_player_token=YOUR_PLAYER_TOKEN

Description: Leave the current room. If you were the host, a new host may be assigned automatically.

Request Body:
{}
Response:
{
  "success": true,
  "message": "Successfully left the room"
}
POST /v1/php/game_room.php/players/heartbeat?api_token=YOUR_API_TOKEN&game_player_token=YOUR_PLAYER_TOKEN

Description: Updates player's last activity timestamp (used to determine online status).

Request Body:
{}
Response:
{
  "success": true,
  "status": "ok"
}
POST /v1/php/game_room.php/actions?api_token=YOUR_API_TOKEN&game_player_token=PLAYER_PRIVATE_KEY

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.

Request Body:
{
  "action_type": "move",
  "request_data": {
    "x": 10,
    "y": 20
  }
}
Response:
{
  "success": true,
  "action_id": "1c2bbd859e36dc7d7e5e9b4f263c88ce",
  "status": "pending"
}
GET /v1/php/game_room.php/actions/poll?api_token=YOUR_API_TOKEN&game_player_token=PLAYER_PRIVATE_KEY

Description: Retrieves recently completed/processed actions from the room. Clients should poll this endpoint regularly to receive updates on action results.

Request Body:
{}
Response:
{
  "success": true,
  "actions": [
    {
      "action_id": "1c2bbd859e36dc7d7e5e9b4f263c88ce",
      "action_type": "move",
      "response_data": "{\"success\":true,\"message\":\"Moved successfully\"}",
      "status": "completed"
    }
  ]
}
GET /v1/php/game_room.php/actions/pending?api_token=YOUR_API_TOKEN&game_player_token=PLAYER_PRIVATE_KEY

Description: Returns list of currently pending actions in the room. Typically used by the host or server-side logic to process/approve actions.

Request Body:
{}
Response:
{
  "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"
    }
  ]
}
POST /v1/php/game_room.php/actions/{action_id}/complete?api_token=YOUR_API_TOKEN&game_player_token=PLAYER_PRIVATE_KEY

Description: Marks a pending action as completed and attaches the result/response data. Usually called by the room host or authoritative server.

Request Body:
{
  "status": "completed",
  "response_data": {
    "success": true,
    "message": "Moved successfully"
  }
}
Response:
{
  "success": true,
  "message": "Action completed"
}
Error Response (401 Unauthorized):

Description: Shows what happens when a request is sent with an invalid or missing API key.

Response:
{
  "success": false,
  "error": {
    "code": "unauthorized",
    "message": "Invalid or missing API key"
  }
}