Developer API

Integration guide for telemetry ingestion, model upload, and export endpoints.

MessagePack-first API

Authentication

Event ingestion uses x-public-token. This token is generated per application and is meant for game clients.

Protected API routes (for example model upload) use x-api-key or Authorization: Bearer <secret_api_key>.

Ingestion still accepts a legacy fallback where x-api-key contains the public token, but use x-public-token for new clients.

POST /api/events

  • - Content type must be application/msgpack.
  • - Explicit JSON requests are rejected with 415.
  • - Payload can be a single event object or a list (batch).
  • - Batch validation is all-or-nothing: one bad item rejects the full batch.
  • - app_id must match the application attached to the token.
  • - Successful queue response is 200 {"status":"queued"}.

Required Headers

x-public-token: YOUR_PUBLIC_TOKEN
content-type: application/msgpack

Event Schemas

Event type is inferred from payload fields.

1. Player Identity (Create_Player)

{
  "event_name": "Create_Player",
  "app_id": "72ab8f81-ef36-4712-a046-399a62b9afaa",
  "build_id": "Win64_Shipping_1.0.2",
  "player_id": "player_uuid",
  "region": "EU-West",
  "hardware": { "gpu": "RTX 4090", "ram": "32GB" },
  "timestamp": "2026-02-12T10:00:00Z"
}

2. Funnel Step

Use ISO 8601 UTC timestamp format, same as other event types.

{
  "funnel_step_name": "Tutorial_Start",
  "app_id": "72ab8f81-ef36-4712-a046-399a62b9afaa",
  "build_id": "Win64_Shipping_1.0.2",
  "player_id": "player_uuid",
  "session_id": "session_uuid",
  "level_id": "Training_Grounds",
  "timestamp": "2026-02-12T10:00:00Z"
}

3. Gameplay Event

{
  "event_name": "Player_Jump",
  "app_id": "72ab8f81-ef36-4712-a046-399a62b9afaa",
  "build_id": "Win64_Shipping_1.0.2",
  "player_id": "player_uuid",
  "session_id": "session_uuid",
  "level_id": "Level_1",
  "position": [10.5, 0.0, -5.2],
  "custom_data": { "weapon": "Sword", "ammo": 10 },
  "input_method": "Gamepad",
  "timestamp": "2026-02-12T10:00:00Z"
}

4. Realtime Trajectory

positions must be a flat array: [x,y,z,x,y,z,...].

{
  "app_id": "72ab8f81-ef36-4712-a046-399a62b9afaa",
  "build_id": "Win64_Shipping_1.0.2",
  "player_id": "player_uuid",
  "session_id": "session_uuid",
  "status": "Gameplay",
  "input_method": "Gamepad",
  "fps": { "mean": 60, "low_5": 55, "low_1": 30 },
  "positions": [10.0, 0.0, 1.0, 10.1, 0.0, 1.1, 10.2, 0.0, 1.2],
  "timestamp_start": "2026-02-12T10:00:00Z",
  "timestamp_end": "2026-02-12T10:00:05Z"
}

Other Endpoints

Route Auth Notes
POST /api/models x-api-key or Bearer secret key Multipart upload with app_name, level_name, and model_file.
POST /api/export No header required Triggers background NDJSON export from in-memory buffers.
POST /api/player|funnel|realtime x-public-token Compatibility aliases; same ingestion logic as /api/events.
POST /api/sql/select x-api-key Currently disabled by design. Returns 501 sql_unavailable.

Response Codes

Status Meaning
200 Queued successfully ({"status":"queued"})
400 Invalid payload, empty payload, or schema format error
401 Missing or invalid token/key
403 App scope mismatch or forbidden protected action
415 Explicit JSON content type is not accepted on ingestion routes
429 Rate limited (default limit: 10 requests/second per source IP)
501 /api/sql/select disabled

Integration Examples

Python (requests + msgpack)

import msgpack
import requests
from datetime import datetime, timezone

url = "http://localhost:4000/api/events"
headers = {
    "x-public-token": "YOUR_PUBLIC_TOKEN",
    "Content-Type": "application/msgpack"
}

event = {
    "event_name": "Player_Jump",
    "app_id": "72ab8f81-ef36-4712-a046-399a62b9afaa",
    "build_id": "Win64_Shipping_1.0.2",
    "player_id": "player_uuid",
    "session_id": "session_uuid",
    "level_id": "Level_1",
    "position": [10.5, 0.0, -5.2],
    "custom_data": {"weapon": "Sword", "ammo": 10},
    "input_method": "Gamepad",
    "timestamp": datetime.now(timezone.utc).isoformat()
}

body = msgpack.packb(event, use_bin_type=True)
response = requests.post(url, data=body, headers=headers, timeout=10)
print(response.status_code, response.text)

Unity (C#)

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using MessagePack;

[MessagePackObject]
public class GameplayEvent {
    [Key("event_name")] public string event_name;
    [Key("app_id")] public string app_id;
    [Key("build_id")] public string build_id;
    [Key("player_id")] public string player_id;
    [Key("session_id")] public string session_id;
    [Key("level_id")] public string level_id;
    [Key("position")] public float[] position;
    [Key("custom_data")] public object custom_data;
    [Key("input_method")] public string input_method;
    [Key("timestamp")] public string timestamp;
}

public class TelemetrySender : MonoBehaviour {
    public IEnumerator SendEvent() {
        var payload = new GameplayEvent {
            event_name = "Player_Jump",
            app_id = "72ab8f81-ef36-4712-a046-399a62b9afaa",
            build_id = "Win64_Shipping_1.0.2",
            player_id = "player_uuid",
            session_id = "session_uuid",
            level_id = "Level_1",
            position = new float[] { 10.5f, 0.0f, -5.2f },
            custom_data = new { weapon = "Sword", ammo = 10 },
            input_method = "Gamepad",
            timestamp = System.DateTime.UtcNow.ToString("o")
        };

        byte[] body = MessagePackSerializer.Serialize(payload);

        using var req = new UnityWebRequest("http://localhost:4000/api/events", "POST");
        req.uploadHandler = new UploadHandlerRaw(body);
        req.downloadHandler = new DownloadHandlerBuffer();
        req.SetRequestHeader("x-public-token", "YOUR_PUBLIC_TOKEN");
        req.SetRequestHeader("Content-Type", "application/msgpack");
        yield return req.SendWebRequest();
        Debug.Log($"Status: {req.responseCode}");
    }
}

Troubleshooting

  • - Verify x-public-token and app_id belong to the same application.
  • - Ensure payload is MessagePack encoded, not JSON bytes.
  • - For realtime events, send positions as flat numeric array.
  • - If a batch fails, validate every item because one invalid record rejects the entire batch.
  • - For dashboard querying, use /dashboard/analytics (not /api/sql/select).