Kraty

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.

NamespaceAudienceCredentialSurface
/sdk/v1/*Game clientsclient_sdk API key + per-player secret on player-scoped routesEvents, leaderboards, lobbies, grants, inventory, wallet, catalog, player register
/server/v1/*Studio backendserver_integration API keyIAP fulfilment, manual grants, support lookups, external matchmaking pushes, migration
/admin/v1/*Portal operatorsMember session cookieGame / 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:

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 portal
  • POST /admin/v1/studios/:studioId/games/:gameId/grants/:id/force-claim — operator override for stuck grants
  • GET /admin/v1/health — public liveness probe

Versioning

The v1 prefix carries the major version/sdk/v1/*, /server/v1/*, /admin/v1/*. Three change classes:

ChangePolicy
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-endpoint

Wire 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.1

Studios 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

ScopeCapBucket
SDK reads (per API key)600/minsdk_read
SDK writes (per API key)120/minsdk_write
Server API (per API key)1000/minserver_api
Admin API (per session)600/minadmin

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-stream
  • Cache-Control: no-cache, no-transform
  • Comment lines starting with : are 15s heartbeats — ignore them
  • event: ready fires once after the subscription is wired (start posting progress only after this)
  • event: score_update fires per leaderboard mutation
  • event: closed is 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/v1 endpoints your game client calls.
  • Server surface: openapi.server.json — the /server/v1 endpoints 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.