REST API conventions
The cross-cutting rules every endpoint follows — auth surfaces, versioning, idempotency, errors, rate limits, SSE, OpenAPI.
Kraty's REST API has three namespaces, each gated by a different credential. See Authentication for the full security model.
| Namespace | Audience | Credential | Surface |
|---|---|---|---|
/sdk/v1/* | Game clients | client_sdk API key + per-player secret on player-scoped routes | Events, leaderboards, lobbies, grants, inventory, wallet, catalog, player register |
/server/v1/* | Studio backend | server_integration API key | IAP fulfilment, manual grants, support lookups, external matchmaking pushes, migration |
/admin/v1/* | Portal operators | Member session cookie | Game / catalog / API key configuration, audit logs, force-claim |
All requests and responses are JSON. Errors follow the error envelope below.
Looking for a specific endpoint?
The endpoint reference is auto-generated from the OpenAPI specs:
- SDK API reference — every
/sdk/v1endpoint with full parameter, body, and response schemas. - Server API reference — every
/server/v1endpoint, same shape.
This page documents the rules that apply across all endpoints: versioning policy, idempotency contract, error envelope, rate limits, SSE behavior, OpenAPI spec locations. Read it once; the per-endpoint pages assume you already know these.
The server_integration key on /server/v1 can mint currency and
grant items. Never ship one in a game client — use
@kraty/server-sdk (Node) or
kraty-admin (Python) from a server
you control.
/admin/v1 — not part of the public contract
The admin surface is consumed by the portal UI; your tooling should generally avoid it. A handful of routes are useful for operator scripting:
GET /admin/v1/studios/:studioId/games/:gameId/players/:externalId— unified player lookup (attempts, grants, lobbies, inventory, wallet, audit ledgers)POST /admin/v1/studios/:studioId/games/:gameId/grants— manual grant via portalPOST /admin/v1/studios/:studioId/games/:gameId/grants/:id/force-claim— operator override for stuck grantsGET /admin/v1/health— public liveness probe
Versioning
The v1 prefix carries the major version — /sdk/v1/*,
/server/v1/*, /admin/v1/*. Three change classes:
| Change | Policy |
|---|---|
| Additive (new endpoint, new optional field, new enum value at the end of an open enum) | Lands in v1 without notice. SDK consumers using the spec for codegen pick the new field up when they regenerate. |
| Deprecation (an endpoint or field will be removed in a future major) | Stays in v1 but the route emits the Deprecation: true HTTP header (and Sunset: <RFC 7231 date> once the removal date is fixed). See Deprecation signals. |
| Breaking (removed field, renamed route, type change, enum value removed) | Lands under /sdk/v2, /server/v2, /admin/v2 etc. The corresponding v1 route enters the deprecation phase for at least 90 days before removal. |
The two surfaces (/sdk/v1 and /server/v1) version independently —
a breaking change to one doesn't force the other to bump. Their
specs ship as separate OpenAPI files
(openapi.sdk.json,
openapi.server.json)
so each can have its own codegen pipeline.
Deprecation signals
A deprecated route emits these headers on every response:
Deprecation: true
Sunset: Fri, 01 Jan 2027 00:00:00 GMT
Link: </sdk/v2/new-endpoint>; rel="successor-version"
X-Kraty-Deprecation-Reason: replaced by /sdk/v2/new-endpointWire your CI to fail when it sees Deprecation: true on a route
your studio depends on — that gives you the full sunset window to
migrate before the route goes away. The Link header points at the
replacement (when one exists), and X-Kraty-Deprecation-Reason
carries a free-form note typically pointing at a migration doc.
Sunset follows RFC 8594
with IMF-fixdate
formatting.
Client identification
Every SDK sends an X-Kraty-SDK: <name>/<version> header on every
request so the backend can attribute a given call to a specific SDK
build. Useful for:
- Diagnosing "is this client on an old SDK?" during incident response.
- Tracking adoption of new versions across your fleet.
- Emitting per-SDK telemetry when issuing deprecation warnings.
The SDK doesn't need to do anything — it's baked into the request layer of all five first-party SDKs:
X-Kraty-SDK: @kraty/sdk/0.0.1
X-Kraty-SDK: @kraty/server-sdk/0.0.1
X-Kraty-SDK: kraty-admin/0.0.1
X-Kraty-SDK: kraty-flutter/0.0.1
X-Kraty-SDK: app.kraty.sdk/0.0.1Studios integrating directly against the REST API (no SDK) are
encouraged to send their own X-Kraty-SDK value (<your-app>/<your-version>)
for the same observability benefit.
Error envelope
Every non-2xx response (and the special 202 lobby_forming) uses
this shape:
{
"error": {
"code": "insufficient_entry_cost",
"message": "not enough cash to enter — need 50",
"details": {
"resource": "cash",
"needed": 50,
"have": 30
}
}
}Codes are stable strings — match on error.code, not on
error.message. Full reference: Error codes.
Idempotency
All POSTs that create or mutate state accept an idempotencyKey
field on the JSON body. SDKs auto-stamp every write with a 16-byte
random key and preserve it across retries. Retry the same key with
the same body → cached response with Idempotent-Replay: true
header, no double effect.
Reusing the same key with a different body returns 409
idempotency_conflict — accidentally recycling keys can't silently
corrupt state.
Cache TTL is 24 hours per key.
Rate limits
| Scope | Cap | Bucket |
|---|---|---|
| SDK reads (per API key) | 600/min | sdk_read |
| SDK writes (per API key) | 120/min | sdk_write |
| Server API (per API key) | 1000/min | server_api |
| Admin API (per session) | 600/min | admin |
Every response includes RateLimit-Limit, RateLimit-Remaining,
and RateLimit-Reset headers so SDK code can back off before
hitting the wall. On 429 you also get Retry-After — the Flutter
SDK honours it automatically.
SSE specifics (/leaderboards/:id/stream)
The stream endpoint uses standard Server-Sent Events:
Content-Type: text/event-streamCache-Control: no-cache, no-transform- Comment lines starting with
:are 15s heartbeats — ignore them event: readyfires once after the subscription is wired (start posting progress only after this)event: score_updatefires per leaderboard mutationevent: closedis the final event when the server finalizes / closes
The official SDKs wrap all of this — kraty.leaderboards.live(id)
in TypeScript and Flutter, LeaderboardsClient.LiveAsync(id) in
Unity.
OpenAPI specs
Kraty publishes OpenAPI 3.1 specs for both API surfaces — use them with any codegen, or import into Postman / Insomnia / Bruno:
- Client SDK surface:
openapi.sdk.json— the/sdk/v1endpoints your game client calls. - Server surface:
openapi.server.json— the/server/v1endpoints your studio backend calls.
Prefer the official SDKs (TypeScript / Unity / Flutter, Node / Python) — they wrap retries, idempotency, and error typing. Generate from OpenAPI when you need a language we don't ship yet.
The portal's API explorer has every endpoint with try-it-now.