REST API conventions
The cross-cutting rules every endpoint follows, covering auth surfaces, versioning, idempotency, errors, rate limits, SSE, and the OpenAPI spec locations.
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/v1 endpoint with full parameter, body, and response schemas.
Server API reference
Every /server/v1 endpoint, same shape.
This page documents the rules that apply across all endpoints: versioning policy, idempotency contract, error envelope, rate limits, SSE behavior. 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-server-sdk (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 does not force the
other to bump. Their specs ship as separate OpenAPI files 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 does not need to do anything; it is baked into the request layer of all first-party SDKs:
X-Kraty-SDK: @kraty/sdk/0.0.1
X-Kraty-SDK: @kraty/server-sdk/0.0.1
X-Kraty-SDK: kraty-server-sdk/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, so accidentally recycling keys cannot
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, which the Flutter SDK honours automatically.
SSE specifics (/sdk/v1/event-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.eventLeaderboards.live(id) in TypeScript and Flutter,
EventLeaderboards.LiveAsync(id) in Unity.
OpenAPI specs
Kraty publishes OpenAPI 3.1 specs for both API surfaces. The generated reference pages embed these specs directly:
- Client SDK surface: the
/sdk/v1endpoints your game client calls; see SDK API reference. - Server surface: the
/server/v1endpoints your studio backend calls; see Server API reference.
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 do not ship yet.
The portal's API explorer has every endpoint with try-it-now.