The HTTP control plane: developer accounts, projects, player storage, health, and diagnostics. Realtime gameplay happens over the WebSocket protocol; everything else is here.
Base URL: https://spawnweaver.dev.
Authentication methods
| Method | Used by | How |
|---|---|---|
| Session cookie | Dashboard/account/project endpoints | Set by POST /api/auth/signup or /signin (HttpOnly cookie); send it with subsequent requests (curl -b jar.txt) |
| Project secret key | Storage (full access) | Authorization: Bearer sk_… — server-side callers only |
| Player token | Storage (own data only) | Authorization: Bearer <playerToken from the realtime welcome> |
| Admin API key | /api/admin/* |
Authorization: Bearer <Admin__ApiKey> — required; without a configured key the admin API is disabled (except local Development) |
| Public project key | /connect, /connect/stats |
?projectKey=pk_… query parameter |
Error shape (RFC 7807)
Failures return application/problem+json:
{
"title": "Unauthorized",
"status": 401,
"detail": "Sign in to create a project."
}
Validation failures add an errors map of field → messages:
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": { "name": ["Name is required."] }
}
Every response carries an X-Correlation-Id header — include it in bug reports.
Health
GET /health
No auth. Liveness/readiness probe for monitors, Docker, and tunnels.
curl https://spawnweaver.dev/health
# 200 -> { "status": "ok", "service": "Platform.Api", "version": "1.0.0.0" }
Auth & accounts
Developer accounts back the dashboard. Sign-up/sign-in are rate-limited per IP
(HTTP 429 when exceeded). With a real email provider configured, sign-up requires
email verification before sign-in; local/dev instances auto-verify.
POST /api/auth/signup
curl -c jar.txt -X POST https://spawnweaver.dev/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"email":"you@studio.com","displayName":"You","password":"supersecret123"}'
# 200 -> { "userId": "user_…", "email": "you@studio.com", "displayName": "You",
# "redirect": "/dashboard/onboarding" } (cookie set; dev instances)
# 200 with "redirect": "/dashboard/verify-pending" (hosted: check your inbox first)
Errors: 409 email already in use · 400 weak password (minimum length in the
message) or invalid email · 429 rate-limited.
POST /api/auth/signin
curl -c jar.txt -X POST https://spawnweaver.dev/api/auth/signin \
-H "Content-Type: application/json" \
-d '{"email":"you@studio.com","password":"supersecret123"}'
# 200 -> { "userId": "…", "email": "…", "displayName": "…", "redirect": "/dashboard" }
Errors: 401 incorrect email or password · 403 with code: "email_not_verified"
when verification is pending · 429 rate-limited.
GET /api/auth/verify?token=…
Confirms an emailed verification link. Redirects to /dashboard on success (signed
in), or to /dashboard/verify-pending?expired=1 for a stale token.
POST /api/auth/verify/resend
Body {"email": "…"}. Always returns 200 (no account enumeration); a new link is
sent only when the account exists and still needs verification.
POST /api/auth/signout
Ends the session. 200 -> { "redirect": "/dashboard/signin" }.
POST /api/auth/magic/request
Passwordless sign-in link by email. Body {"email": "…"}.
200 -> { "sent": true, "message": "Check your email…", "devLink": null } —
devLink carries the clickable link on non-production instances without a real email
provider. 429 when requesting links too quickly.
GET /api/auth/magic?token=…
Consumes a magic link; redirects to /dashboard (signed in) or
/dashboard/signin?error=link.
Account endpoints (session cookie required; 401 otherwise)
| Method & path | Body | Response |
|---|---|---|
GET /api/account |
— | 200 profile: id, email, displayName, createdAtUtc, lastLoginAtUtc, organizationId, organizationName |
PUT /api/account |
{"displayName": "…"} |
200, or 400 when empty |
POST /api/account/password |
{"currentPassword": "…", "newPassword": "…"} |
200, or 400 on wrong password / weak new one |
GET /api/account/sessions |
— | 200 { "sessions": [ { "id", "createdAtUtc", "lastSeenAtUtc", "expiresAtUtc", "current" } ] } |
POST /api/account/sessions/revoke-all |
— | 200 — revokes every session, then re-issues one for this browser |
Projects
Project management requires a signed-in developer (session cookie); projects belong to your workspace.
POST /api/projects
Creates a project and returns both keys. The secret key is returned exactly once — it is stored only as a hash and can never be retrieved again.
curl -b jar.txt -X POST https://spawnweaver.dev/api/projects \
-H "Content-Type: application/json" \
-d '{"name":"Duel Arena","gameType":"Arena1v1","multiplayerMode":"MatchmakingAndRooms","persistenceFeatures":["PlayerProfile"]}'
{
"id": "proj_…",
"name": "Duel Arena",
"publicKey": "pk_…",
"secretKey": "sk_…",
"createdAtUtc": "2026-07-29T10:00:00Z",
"slug": "duel-arena",
"organizationId": "org_…",
"gameType": "Arena1v1",
"multiplayerMode": "MatchmakingAndRooms",
"persistenceFeatures": ["PlayerProfile"],
"environment": "Development",
"recommendedSetup": { "exampleProject": "1v1 Matchmaking Arena", "steps": ["…"] }
}
201 Created. Only name is required; the optional onboarding fields (gameType,
multiplayerMode, persistenceFeatures, targetPlatform, environment) tailor the
recommendedSetup plan. Valid choices: GET /api/onboarding/options (no auth).
Errors: 401 not signed in · 400 missing/too-long name.
`GET /api/projects/
Project details for the owning workspace — never includes the secret key.
curl -b jar.txt https://spawnweaver.dev/api/projects/proj_xxx
# 200 -> { "id", "name", "publicKey", "isActive", "createdAtUtc", "slug",
# "organizationId", "gameType", "multiplayerMode", "persistenceFeatures", "environment" }
Errors: 401 not signed in · 404 unknown or not yours (existence is not
probeable).
`POST /api/projects/
Regenerates the secret key; returns the new plaintext once:
200 -> { "secretKey": "sk_…" }. The old key stops working immediately.
Errors: 401 / 404.
`POST /api/projects/
Rotates the public key: 200 -> { "publicKey": "pk_…" }. Disruptive — shipped
clients using the old key stop connecting. Errors: 401 / 404.
Player storage
/api/storage/{projectId}/players/{playerId}/keys[…] — persistent per-player
key-value data, the same data the SDK reads via storage_get(). Two accepted bearer
credentials:
- Secret key
sk_…— any player in the project (server-side tooling). - Player token (from the realtime welcome) — only when
{playerId}matches the token's own player; anything else is401.
`PUT /api/storage/
The request body is the raw JSON value to store.
curl -X PUT https://spawnweaver.dev/api/storage/proj_xxx/players/player_1/keys/score \
-H "Authorization: Bearer sk_xxx" -H "Content-Type: application/json" -d '42'
# 200 -> { "key": "score", "updatedAtUtc": "2026-07-29T10:00:00Z" }
Errors: 400 invalid/too-long key · 413 value over the size limit · 409 at the
stored-key quota · 401 bad credential.
`GET /api/storage/
curl https://spawnweaver.dev/api/storage/proj_xxx/players/player_1/keys/score \
-H "Authorization: Bearer sk_xxx"
# 200 -> { "key": "score", "value": "42", "updatedAtUtc": "…" }
# 404 when the key does not exist
value is the stored raw JSON as a string — parse it client-side.
`DELETE /api/storage/
204 No Content on success, 404 when the key didn't exist.
`GET /api/storage/
200 -> { "keys": ["score", "save"] } — the player's stored key names.
Quotas (defaults): 64 KB per value, 100 keys per player, 128-char keys. See Limits.
Realtime handshake & stats
GET /connect (WebSocket upgrade)
The realtime gateway. Query parameters: projectKey (required, pk_…),
playerToken, sdkVersion, engine (optional). On success the server completes the
WebSocket upgrade and sends session.welcome — full details in the
protocol reference.
Rejections: 400 not a WebSocket upgrade request · 401 missing/unknown/inactive
project key, or invalid/expired player token · 403 Origin not in the configured
allowlist · 429 per-project connection limit reached.
GET /connect/stats?projectKey=pk_…
Live counts for your project (key-scoped, never node-wide) — this powers the editor dock's live status:
curl "https://spawnweaver.dev/connect/stats?projectKey=pk_xxx"
# 200 -> { "activeConnections": 3, "activeRooms": 1 }
Errors: 401 missing/unknown/inactive key.
Admin API
Read-only diagnostics under /api/admin/*. Requires
Authorization: Bearer <key> matching the deployment's Admin__ApiKey. Fail-closed:
when no key is configured the admin API is disabled entirely (Development environments
excepted, for local convenience). All endpoints return 401 without a valid key.
curl https://your-domain/api/admin/metrics \
-H "Authorization: Bearer $ADMIN_API_KEY"
| Method & path | Returns |
|---|---|
GET /api/admin/projects |
Up to 100 project summaries: { "projects": [ { "id", "name", "isActive", "createdAtUtc" } ] } |
GET /api/admin/projects/{id} |
One project summary, or 404 |
GET /api/admin/realtime |
Live snapshot: activeConnections, activeRooms, per-connection and per-room details |
GET /api/admin/sessions |
Recent connection sessions (the session inspector's list) |
GET /api/admin/sessions/{connectionId} |
One session's full timeline — connect, auth, actions, rejections, disconnect reason; 404 unknown |
GET /api/admin/errors |
Protocol errors aggregated by code, with counts and affected sessions |
GET /api/admin/matchmaking |
Current matchmaking queue contents |
GET /api/admin/rooms/{roomId} |
One room's members, host, and metadata; 404 unknown |
GET /api/admin/logs?level=warning |
Recent server log entries (up to 200), optionally filtered by level |
GET /api/admin/metrics |
Metrics snapshot: connections, rooms, messages, errors |
GET /api/admin/feedback |
Feedback submitted via the landing page form (up to 200) |
These are the endpoints behind the dashboard's Debugger hub — see Debugging.
SDK installers
GET /install.ps1 · GET /install.sh
No auth. One-line installer scripts served with the deployment's own base URL baked
in; they download /sdk/spawnweaver.zip and extract it into the current Godot
project's addons/ folder.
iwr https://spawnweaver.dev/install.ps1 -UseBasicParsing | iex
curl -fsSL https://spawnweaver.dev/install.sh | bash
Conventions
- All timestamps are UTC ISO-8601 (
…AtUtcfields). - JSON field names are camelCase.
- Ids are prefixed strings:
proj_…,player_…,room_…,conn_…,org_…,user_…. - Keys:
pk_…is safe to ship in clients;sk_…must never leave your infrastructure — regenerate it immediately if it does.