SlateFire developer docs
Everything you need to integrate SlateFire into your Godot 4 project — from the GDScript SDK to the raw HTTP API. Every method, parameter, and response shape documented below comes from the actual addons/slatefire/ source.
Install & setup
- Install the plugin. Drop the
addons/slatefire/directory into your Godot project, or install from the Asset Library. - Enable it. Go to Project → Project Settings → Plugins and toggle SlateFire on. The plugin registers a singleton named
SlateFire(extends Node) that is available everywhere in GDScript.
Configure
Call SlateFire.configure() once, typically in your main scene's _ready(). You need a publishable API key from the SlateFire dashboard.
pk_live_xxxxxxxx). Must not be empty.
opts.base_urlStringBase URL for API requests. Defaults to https://slatefire-guard.john-malmin.workers.dev/v1. Set this to https://api.slatefire.dev/v1 when the custom domain is live.
opts.auto_retrybool (default true)Auto-back-off and retry on rate_limited responses. Retries up to max_retries times.
opts.max_retriesint (default 3)Maximum retry attempts when auto-retry is active.
opts.request_timeout_sfloat (default 10.0)HTTP request timeout in seconds.
When configure() is called, the SDK automatically:
- Creates all sub-modules (
auth,leaderboards,saves,analytics,config) - Fetches remote config (returns local cache — no backend endpoint yet)
- Sets
saves.max_bytesfrom thesave_max_bytesconfig field (default 245,760 bytes / 240 KB)
set_player_token
Write endpoints (submit score, save data, delete saves) require a player token. Set it after your player authenticates. The SDK does not issue player tokens itself — tokens must be handed to the game client from your own trusted system (or hand-minted for testing).
x-player-token header on write requests.
auth.sign_in_anonymous(), auth.register_email(), etc. but the backend has no player-auth routes yet. These methods return not_implemented. Use set_player_token() with a pre-issued token for testing.
Make your first call
Here is the full getting-started flow — sign in anonymously, then submit a score and read the leaderboard. All three calls work against the live backend.
All working sign_in_anonymous, leaderboards.submit, leaderboards.top, saves.put, saves.get, and saves.delete all work against the live backend.
SDK Reference
Every public member of the SlateFire singleton and its sub-modules. Methods are tagged by implementation status against the live backend.
SlateFire (singleton)
The autoload registered by the plugin. Extends Node. Created automatically when the plugin is enabled — no manual instantiation.
Properties
| Property | Type | Description |
|---|---|---|
auth | SlateFireAuth | Player authentication sub-module. |
leaderboards | SlateFireLeaderboards | Leaderboard read/write sub-module. |
saves | SlateFireSaves | Cloud save sub-module. |
analytics | SlateFireAnalytics | Analytics event tracking sub-module. |
config | SlateFireConfig | Remote config sub-module (local-only for now). |
is_online | bool | Read-only. false when the SDK detects a connectivity failure. |
is_paused | bool | Read-only. true while writes are blocked due to a service_paused response. |
configure
Initializes the SDK. Creates sub-modules, fetches config, sets saves.max_bytes. Must be called before any other SDK method. The api_key must not be empty.
Options (all optional):
| Key | Type | Default | Description |
|---|---|---|---|
auto_retry | bool | true | Auto-backoff and retry on rate_limited responses for write operations. |
max_retries | int | 3 | Maximum retry attempts. |
request_timeout_s | float | 10.0 | HTTP request timeout in seconds. |
set_player_token
Sets the HMAC-signed player token sent as x-player-token on write requests. Also notifies SlateFire.auth via _on_token_changed().
Signals
| Signal | Arguments | Fired when |
|---|---|---|
service_paused | retry_after: int | Backend returns HTTP 503. Writes are blocked for retry_after seconds. |
service_resumed | — | Pause period expires. Writes may resume. |
request_failed | error: SlateFireError | Any error from an SDK network call. |
quota_warning | percent: int | Local usage estimate crosses 80% or 95% (not currently emitted by the SDK — reserved for future). |
quota_reached | kind: String | Backend returns HTTP 402 with mau_quota or write_quota. |
SlateFireResponse
Every await-able SDK call returns one of these.
| Property | Type | Description |
|---|---|---|
ok | bool | true on success, false on error. |
data | Variant | The response payload. Type depends on the call (Dictionary, Array, String, bool, null). null on error. |
error | SlateFireError | null when ok == true, otherwise a SlateFireError instance. |
SlateFireError
| Property | Type | Description |
|---|---|---|
code | String | Stable error code (see error reference). |
message | String | Human-readable message. |
status | int | HTTP status code; 0 for offline/timeout errors. |
retry_after | int | Seconds to wait before retrying. 0 when not applicable. |
SlateFire.auth Working
Player authentication. sign_in_anonymous() works end-to-end: it calls POST /v1/players/anonymous, gets back a signed player token, and automatically sets it on the HTTP client for all subsequent requests. Other sign-in methods (register_email, sign_in_email, etc.) return not_implemented — the backend doesn't have those routes yet.
Properties
| Property | Type | Description |
|---|---|---|
current_player | Dictionary | Currently signed-in player data. Empty dict when signed out. |
is_signed_in | bool | false until a sign-in method succeeds. |
Signals
| Signal | Arguments |
|---|---|
signed_in | player: Dictionary |
signed_out | — |
Methods
POSTs to /v1/players/anonymous. On success, automatically sets the returned player token via set_player_token(), populates current_player, sets is_signed_in = true, and emits signed_in.
not_implemented
not_implemented
Where provider is e.g. "steam", "google", "discord", "apple".
not_implemented
not_implemented
not_implemented
Clears current_player, sets is_signed_in = false, and emits signed_out. Works locally only — no network call.
not_implemented
SlateFire.leaderboards
Submit and read leaderboard scores. Boards are created in the developer dashboard. Methods below are tagged with their backend status.
Submits a score for the current player. The server keeps the best score per player (uses ON CONFLICT ... DO UPDATE SET score = MAX(score, excluded.score)).
| Parameter | Type | Description |
|---|---|---|
board_id | String | The leaderboard ID (created in the dashboard). |
score | int | The score to submit. |
metadata | Dictionary | Optional metadata (currently ignored by the backend — stored and returned as empty {} in the response). |
Success: data is a Dictionary with rank, player_id, display_name, score, metadata, is_current_player.
Errors: invalid_api_key, unauthorized, rate_limited, mau_quota, write_quota, service_paused, score_required (400 if score is missing or not a number).
Returns the top entries for a leaderboard. The count parameter is accepted by the SDK but the backend hard-codes a LIMIT 100 and ignores the count — you always get up to 100 entries regardless of what you pass.
Success: data is an Array of Dictionaries with rank, player_id, display_name, score, metadata, is_current_player.
Errors: invalid_api_key, not_found.
not_implemented — no backend endpoint exists.
not_implemented — no backend endpoint exists.
not_implemented — no friends/social system exists on the backend.
SlateFire.saves
Per-player key/value JSON blobs. The SDK caches reads to user://slatefire/saves/ for offline access.
| Property | Type | Description |
|---|---|---|
max_bytes | int | Per-slot size limit for the current plan (default 245760 / 240 KB). Set automatically from config on configure(). |
Writes a JSON save slot. The SDK serializes the Dictionary to JSON, checks the byte size against max_bytes before uploading, and returns save_too_large immediately if it exceeds the limit.
| Parameter | Type | Description |
|---|---|---|
key | String | Save slot identifier (e.g. "profile", "level_7_state"). |
data | Dictionary | JSON-serializable save data. |
expected_version | int (default -1) | Optimistic concurrency version. Not yet supported by the backend — currently ignored. |
Success: data is a Dictionary with key, data, version, updated_at.
Errors: save_too_large (413, checked client-side before request), invalid_api_key, unauthorized, rate_limited, service_paused.
SaveSlot shape, but version will be 0 and updated_at will be empty until the backend adds an envelope.
Reads a save slot. On success, the SDK caches the result to user://slatefire/saves/{key}.json. On failure, it falls back to the cached copy if available.
Success: data is a Dictionary with key, data (parsed JSON Dictionary), version, updated_at. Returns ok: true, data: null if the slot doesn't exist? Actually, the backend returns 404 for missing saves, which the SDK propagates as an error. If cached, it returns the cached copy.
Errors: invalid_api_key, not_found (404).
not_implemented — no backend endpoint exists to list saves.
Deletes a save slot. Also removes the local cache. On success, data is true.
Errors: invalid_api_key, unauthorized, not_found.
SlateFire.analytics Coming soon
Fire-and-forget event tracking. Events queue to user://slatefire/analytics/queue.json but are never flushed — the backend has no analytics endpoint yet.
Appends an event to the local queue with the current timestamp. No network call. The queue persists across sessions.
No-op. Stubbed for future use.
SlateFire.config Local only
Remote feature flags and game balance values. The backend has no config endpoint — config.fetch() returns whatever is cached locally (an empty Dictionary until you populate it).
Signals
| Signal | Arguments | |
|---|---|---|
updated | — | Emitted after each fetch() call. |
Returns a copy of the cached flags. data is a Dictionary.
Each getter checks the cached flags. If the key is missing or the type doesn't match, the default is returned.
HTTP API Reference
For developers integrating without the GDScript SDK, or who want to understand the wire protocol. All endpoints are under the base URL https://slatefire-guard.john-malmin.workers.dev/v1.
Authentication
Every request requires x-api-key (the publishable key). Write operations (POST, PUT, DELETE) also require x-player-token (a signed HMAC token identifying the player).
CORS is open (access-control-allow-origin: *) so browsers can call the API directly.
token as the x-player-token header on all subsequent write requests.x-player-token: signed player token
Content-Type: application/json
The server uses INSERT ... ON CONFLICT DO UPDATE SET score = MAX(score, excluded.score) — only best scores are kept.
count parameter is not supported.x-player-token: signed player token
Content-Type: application/octet-stream
Server-internal key format: {projectId}/{playerId}/{key}. The SDK handles this transparently.
application/octet-stream. No metadata envelope. Requires the player token so saves are properly scoped per-player.x-player-token: signed player token
Returns 404 if the slot doesn't exist. Server-internal key format: {projectId}/{playerId}/{key} — same as the write path, so reads and writes address the same slot.
x-player-token: signed player token
Error codes
The backend returns error responses as JSON with an error field. The SDK maps these to SlateFireError.code values.
| error.code | HTTP status | Meaning |
|---|---|---|
invalid_api_key | 401 | The x-api-key is missing or doesn't match any project. |
unauthorized | 401 | Write request without a valid x-player-token. |
rate_limited | 429 | Too many writes too fast. Includes retry-after header. The SDK auto-retries by default. |
mau_quota | 402 | Monthly active-player cap reached for the project's plan. |
write_quota | 402 | Monthly write cap reached for the project's plan. |
save_too_large | 413 | Save data exceeds the plan's per-slot limit or the absolute 8 MB ceiling. The SDK also checks this client-side before uploading. |
body_too_large | 413 | Request body exceeds the absolute 8 MB limit (enforced before any plan check). |
service_paused | 503 | Backend is temporarily read-only (panic flag active for free-tier projects). Includes retry-after. The SDK caches this and blocks writes without network calls. |
not_found | 404 | The requested resource (save slot, leaderboard) doesn't exist. |
score_required | 400 | Leaderboard submit request body is missing or has a non-numeric score. |
conflict | 409 | Defined in the SDK for future save version conflicts; not yet returned by the backend. |
offline | 0 | Network error / no connectivity. SDK-only error code. |
timeout | 0 | Request exceeded request_timeout_s. SDK-only error code. |
not_implemented | 0 | Returned by SDK methods whose backend routes don't exist yet. |
Concepts
API key vs Player token
SlateFire uses a two-key authentication model:
| Publishable API key | Player token | |
|---|---|---|
| What it is | A project-level key from the dashboard (e.g. pk_live_xxxxxxxx) | An HMAC-SHA256 signed token identifying the current player |
| Where it goes | x-api-key header (every request) | x-player-token header (write requests only) |
| Required for | All reads and writes | Writes only (POST, PUT, DELETE) |
| Set via | SlateFire.configure("pk_...") | SlateFire.set_player_token("...") |
| Who issues it | SlateFire dashboard | Your own trusted system (hand-minted for testing; future auth.sign_in_* methods will issue them automatically) |
Why two keys? The API key identifies the project. The player token identifies who is acting. Reads (leaderboard top, save load) need only the project key. Writes (score submit, save put) need both, so the server can enforce per-player rate limits, MAU counting, and panic gating.
Plan limits
Every project has a plan that governs resource limits. The backend enforces them server-side — these are walls, not suggestions.
| Limit | Hobby (free) | Indie ($19/mo) | Studio ($99/mo) |
|---|---|---|---|
| MAU / month | 5,000 | 50,000 | 250,000 |
| Writes / month | 2,000,000 | 50,000,000 | 500,000,000 |
| Max save size | 256 KB | 1 MB | 4 MB |
| Write rate (per-player) | 5 / sec, burst 20 | 20 / sec, burst 60 | 50 / sec, burst 150 |
| Panic bypass | No (free projects go read-only during a panic event) | Yes | Yes |
There is also a hard global ceiling of 8 MB per request body, enforced before any plan check. Beyond that, a global monthly write budget of 20 million acts as a circuit breaker — if crossed, the panic flag is set automatically and free-tier projects are blocked from writing (the cron backstop auto-recovers when the count drops below 50% of budget).