Game engines and raw HTTP
Integrate from Unity, Godot, or any HTTP client. The SDK is JavaScript, but the API is plain REST with header auth and a full OpenAPI spec.
The @triggair/sdk package is the fastest path for a JavaScript or TypeScript game, but the backend itself is language-neutral: every feature is a plain HTTP+JSON call, so Unity (C#), Godot (GDScript or C#), or any HTTP client can use the whole surface. The base URL is https://api.triggair.com, and the complete machine-readable contract lives at /openapi.json (every endpoint, request, and response). This guide shows the core loop over raw HTTP so you can build a native client for any engine.
The two credentials
Every call carries the publishable key in an X-Triggair-Key header (tg_pk_…, safe in a client, origin-checked by CORS). Player-scoped calls (saves, score submits, inventory, and so on) also carry a player token as Authorization: Bearer <token>. Public calls (reading a leaderboard, config) need only the key. That is the entire auth model; there is nothing browser-specific about it.
X-Triggair-Key: tg_pk_your_key
Authorization: Bearer <playerToken> # only on player-scoped calls
Content-Type: application/json Get a player token
Generate a random device id once, persist it (PlayerPrefs on Unity, a user:// ConfigFile on Godot), and exchange it for a 24h token via POST /v1/players/anonymous. The response is { player_id, token, expires_in }. Cache the token and re-mint when it nears expiry or when any call returns 401. The same call refreshes silently; an existing device is never quota- or challenge-gated.
curl -X POST https://api.triggair.com/v1/players/anonymous \
-H "X-Triggair-Key: tg_pk_your_key" \
-H "Content-Type: application/json" \
-d '{"device_id":"a4f3...generated-and-persisted-once"}'
# -> { "player_id": "plr_...", "token": "...", "expires_in": 86400 } The core loop over HTTP
With a token in hand, saves and leaderboards are one call each. A save is a JSON blob under a slot name; pass the version you last read as If-Match for a conflict-safe write (a stale version returns save_conflict). A score submit returns your new best and the period key.
# conflict-safe cloud save (omit If-Match for last-write-wins)
curl -X PUT https://api.triggair.com/v1/saves/slot1 \
-H "X-Triggair-Key: tg_pk_your_key" \
-H "Authorization: Bearer <playerToken>" \
-H "Content-Type: application/json" \
-H 'If-Match: "3"' \
-d '{"level":4,"coins":120}'
# submit a score
curl -X POST https://api.triggair.com/v1/leaderboards/high/scores \
-H "X-Triggair-Key: tg_pk_your_key" \
-H "Authorization: Bearer <playerToken>" \
-H "Content-Type: application/json" \
-d '{"score":1200}'
# -> { "ok": true, "best_score": 1200, "period_key": "2026-07" } Unity (C#)
UnityWebRequest speaks the API directly. Persist the device id in PlayerPrefs, mint a token once per session, and attach the two headers. For realtime, System.Net.WebSockets.ClientWebSocket works on standalone and mobile (use a WebGL-safe socket like NativeWebSocket for browser builds).
const string ApiBase = "https://api.triggair.com";
const string Key = "tg_pk_your_key";
string DeviceId() {
var d = PlayerPrefs.GetString("tg_device", "");
if (string.IsNullOrEmpty(d)) {
d = System.Guid.NewGuid().ToString("N");
PlayerPrefs.SetString("tg_device", d);
}
return d;
}
[System.Serializable] class TokenResp { public string player_id; public string token; public int expires_in; }
IEnumerator Login(System.Action<string> done) {
var body = "{\"device_id\":\"" + DeviceId() + "\"}";
using var req = new UnityWebRequest(ApiBase + "/v1/players/anonymous", "POST");
req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(body));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("X-Triggair-Key", Key);
req.SetRequestHeader("Content-Type", "application/json");
yield return req.SendWebRequest();
done(JsonUtility.FromJson<TokenResp>(req.downloadHandler.text).token);
}
IEnumerator SubmitScore(string token, int score) {
using var req = new UnityWebRequest(ApiBase + "/v1/leaderboards/high/scores", "POST");
req.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes("{\"score\":" + score + "}"));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("X-Triggair-Key", Key);
req.SetRequestHeader("Authorization", "Bearer " + token);
req.SetRequestHeader("Content-Type", "application/json");
yield return req.SendWebRequest();
} Godot (GDScript)
The HTTPRequest node covers REST; WebSocketPeer covers realtime. Persist the device id in a user:// ConfigFile. This is Godot 4 syntax.
const API_BASE := "https://api.triggair.com"
const KEY := "tg_pk_your_key"
func device_id() -> String:
var cfg := ConfigFile.new()
cfg.load("user://triggair.cfg")
var d: String = cfg.get_value("auth", "device", "")
if d == "":
d = Crypto.new().generate_random_bytes(16).hex_encode()
cfg.set_value("auth", "device", d)
cfg.save("user://triggair.cfg")
return d
func login() -> String:
var http := HTTPRequest.new()
add_child(http)
var headers := ["X-Triggair-Key: " + KEY, "Content-Type: application/json"]
http.request(API_BASE + "/v1/players/anonymous", headers, HTTPClient.METHOD_POST, JSON.stringify({ "device_id": device_id() }))
var res = await http.request_completed
return JSON.parse_string(res[3].get_string_from_utf8()).token
func submit_score(token: String, score: int) -> void:
var http := HTTPRequest.new()
add_child(http)
var headers := ["X-Triggair-Key: " + KEY, "Authorization: Bearer " + token, "Content-Type: application/json"]
http.request(API_BASE + "/v1/leaderboards/high/scores", headers, HTTPClient.METHOD_POST, JSON.stringify({ "score": score }))
await http.request_completed Realtime rooms
Realtime is a standard WebSocket. Because a WebSocket handshake can't send custom headers, the key and player token travel as query params instead, which works identically from any engine. Connect Unity with ClientWebSocket and Godot with WebSocketPeer. Note that live server-backed rooms are an Indie-plan-and-up feature; the Free and Dev tiers don't include them.
wss://api.triggair.com/v1/realtime/rooms/lobby?key=tg_pk_your_key&token=<playerToken>
# Unity: System.Net.WebSockets.ClientWebSocket Godot: WebSocketPeer What the SDK does that you now do yourself
The raw calls above work, but the JavaScript SDK also bundles the resilience you should re-create in a native client: persist and cache the token and refresh it once on a 401 (share one in-flight mint so concurrent calls don't stampede); retry 429 and 5xx and network errors with backoff, honoring the Retry-After header; send If-Match on saves for conflict-safe writes; put an Idempotency-Key on writes so a retried request can't double-apply; and read the error envelope { code, message, agentHint, requestId } on every non-2xx, since agentHint tells you (or your agent) exactly how to fix the call. None of this is large; it is the difference between a working client and a robust one.
Let an agent build it
You don't have to write the client by hand. Point a coding agent at /openapi.json (the full typed contract), /llms-full.txt (recipes and these guides), and this page, and ask it to scaffold a C# or GDScript client for the endpoints you need. In a typed language you can also codegen a client straight from the OpenAPI spec (NSwag or openapi-generator for C#), then add the token and retry layer above.
- Getting started
Keys, anonymous identity, and the one-import integration loop.
- Player accounts and login
Optional email/password login on top of anonymous-first, so a player keeps one identity across devices.
- Customizing account emails
Override the HTML of the emails Triggair sends your players (confirm signup, reset password, and more), per game, from the dashboard, API, or MCP.
- Reliability: retries, idempotency and offline
How the SDK survives dropped connections and retries: the durable outbox, idempotency keys, and the error contract.
- Built for coding agents
llms.txt, an MCP server, self-fixing errors, and a one-command self-test, so an agent can integrate the backend itself.