Player tools
Live-ops tools for when something goes wrong in production.
The Player lookup screen (inside any game's nav) is the single search for everything Kraty knows about a player: profile snapshot, recent attempts, reward and crate grants, lobby memberships. It's also where you fix things when they break.
The four admin actions
Every action is permission-gated, audited (with reason captured where applicable), and idempotent where possible. Use them sparingly — they bypass the normal player flow, so reach for them only to fix broken state, not as a routine operation.
Force-complete an attempt
When a player's attempt is stuck in in_progress (client crashed
between progress reports, network partition, etc.), force-complete from
the attempt row.
You can optionally override the final score. The leaderboard catches up with the new score automatically. Rewards are not auto-rolled — chain with Retry roll if the attempt should produce a grant.
- Permission:
attempt.force_complete(GAME_ADMIN,GAME_DEBUGGER) - Audit:
attempt.force_completewith the reason in metadata.
Retry the reward roll
When the original roll failed or never happened (e.g., after a force-complete), re-run the reward pipeline from the attempt row.
The route refuses to roll a second time if any grants are already linked to the attempt — there's no duplicate-rewards path.
- Permission:
grant.retry_reward_roll(GAME_ADMIN,GAME_DEBUGGER) - Audit:
grant.retry_reward_rollwith{ grantCount }in metadata.
Issue a manual grant
Make-goods, promotional one-offs, and recovery from edge cases. Compose
the grant's contents as JSON in the Issue a manual grant card.
The grant fires a normal grant.created webhook so your studio backend
picks it up the same way as an event-completion grant.
- Permission:
grant.manual_create(GAME_ADMIN) - Audit:
grant.manual_createwith the reason and the external player id in metadata.
Force-claim a grant
Use after verifying server-side that a claim succeeded but the ack never came back (network hiccup mid-deploy on the receiver, etc.). Marks the grant claimed regardless of current status.
- Permission:
grant.manual_force_claim(GAME_ADMIN,GAME_DEBUGGER) - Audit:
grant.manual_force_claimwith full before/after diff.
From your backend (without the portal)
For support workflows that need to read player state programmatically
— a customer-service tool, a fraud-review job, a Slack bot — call the
player profile endpoint directly with a server_integration API
key:
GET /server/v1/players/{externalPlayerId}
Authorization: Bearer <your-client-sdk-key>Returns the same shape the portal's Player lookup uses:
{
"data": {
"player": {
"id": "...",
"externalPlayerId": "alice_42",
"firstSeenAt": "...",
"lastSeenAt": "...",
"lastContextSnapshot": { "country": "PT", "level": 7 }
},
"attempts": [/* recent N */],
"grants": [/* recent N */],
"lobbies": [/* recent N */],
"summary": {
"attemptCount": 12,
"attemptsCompleted": 8,
"attemptsExpired": 1,
"grantsPending": 2,
"grantsClaimed": 9,
"lobbiesActive": 0
}
}
}The endpoint is read-only — no force-complete, no manual grant,
no force-claim from the server side. Those mutations need a member
session through the portal so they leave an actor trail. If your
support team needs to issue make-goods programmatically, use
POST /server/v1/players/:p/grants (which already
audits the grant and fires the same grant.created webhook).
- Auth:
server_integrationAPI key (aclient_sdkkey gets 403). - Scope: the key's
(studio, game)— the path doesn't carry them. - Limits:
?limit=caps each of the three lists (1–200, default 50).
When NOT to use these
These tools are for fixing broken state. They aren't for routine operations:
- For periodic make-goods, generate them server-to-server via
POST /server/v1/players/:p/grants, not by hand in the portal. - If an attempt regularly needs force-completing, the event config is probably wrong (e.g., target unreachable) — fix the config rather than the symptom.
- If grants regularly need force-claiming, your webhook receiver is unreliable — fix retries on your side instead of papering over with manual claims.
Walkthrough: resolve a "lost my reward" support ticket
Your support engineer gets a ticket: "I finished the daily race but the gold never arrived." The full path from ticket to resolution, all auditable:
- Open the game's Players tab and search for the player by their external id (or the email they signed up with — both are indexed).
- The profile shows their last reported context, ban status,
and totals. Open the Attempts card and find the one
they're complaining about. Its status is one of:
completedwith grants attached → look at the Grants card; ifpendingthey're waiting for the SDKcollectAll()call. Click Force-claim to deliver immediately.completedwith no grants attached → the reward roll failed. Click Retry reward roll — idempotent, so the fix lands exactly once even if you double-click.in_progressorexpired→ the attempt never completed server-side. Click Force-complete with an override score and a reason; the engine rolls rewards as if the attempt finished normally.
- For a make-good on top of recovering the original payout,
open the Wallet card and click + Credit. Enter the
currency key, amount, and an audit reason. The credit appears
instantly and emits a
wallet.changedwebhook to your CRM. - Every action lands in the studio Audit log with the actor, timestamp, and before/after diff. Linking the audit row in your ticketing system gives you a permanent paper trail.
Walkthrough: merge a guest player into an authenticated account
When a guest who's been playing locally signs in with a social provider for the first time, you usually want their progress to follow them — not start over. Run this from your backend, not the portal:
// `@kraty/server-sdk` — server_integration key required.
await server.players.merge('guest_device_abc', 'auth_user_42');What happens, in one transaction:
- Attempts and grants are reassigned to the authenticated id.
- Item quantities are summed (
guest had 2 potions, authed had 1 → 3). - Wallet balances are summed.
- Lobby seats are re-pointed.
- The guest's
externalPlayerIdis anonymised so the slot can be reused on the next guest signup from the same device.
A single player.merged webhook fires with the full counts so
your analytics pipeline can stitch the two journeys together.