Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rewards

This page describes how the rewards your game gives players are defined and granted in Open Play: chance-based reward tables, the daily login reward, quests, NPC rewards and VIP cards. Read it if you want a reward to be granted on the server, cheat-proof and only once.

What it does

The rewards module (rewards) does the job of “give the player money or an item” through a single trustworthy line. Every reward grant is a single database transaction: money is written to the wallet, the item is put into the inventory and the event is recorded. If the inventory is full or the currency would exceed its supply cap, nothing is granted (422).

Table rewards are deterministic: the same seed gives the same result. The seed is kept on the server; it does not appear in the response, the webhook event or the ledger record.

If the module is disabled, the endpoints return 404 with {"code":"module_disabled","module":"rewards"}. Enable the module on the Game settings › Modules page.

Where it lives in the panel

What you want to doPanel page
Set up a reward table, edit row chances and guaranteed rewards, see versionsReward tables, Reward table profile
Define the currency to use in a rewardCurrencies
Define the item to use in a rewardContent
Grant rewards with event-driven automationGame rules

Note

Panel help pages are not translated yet; the panel links above point to the Turkish source.

Data model (in plain words)

  • Reward table: key, name, rolls (how many draws are made, 1–10), entries (rows) and guaranteed (rewards given every time). Every registration becomes a new version; the current version is used at grant time and the version number is returned in the result.
  • Row (entry): chance_bp (basis points, 1–10000) and drop. The row chances of one draw must add up to exactly 10000 (100%); “no reward” can be a row.
  • Reward (drop): currency (key, min, max), item (key, min, max) or nothing. The quantity is always chosen from between min and max with a uniform distribution; quantities are between 1 and 1,000,000. A table has at most 50 rows and 10 guaranteed rewards.
  • Guaranteed reward: Given independently of the draw; it cannot be nothing.
  • Source (source): Writes, in a typed way, why the reward was given: content_entry, tournament, leaderboard_season, quest, npc_reward, rule or manual.

Calls your game makes

All reward endpoints are called with a server key (or a key of the admin type); a client key and a player session are rejected. The player ID is carried in the body by the trusted server component:

Authorization: Bearer crx_live_<prefix>_<secret>
Method and pathWhat it does
POST /v1/reward-tablesSaves a reward table as a new version
GET /v1/reward-tables/{key}Reads the current version
POST /v1/reward-tables/{key}/grantGrants a reward from the table, idempotently
GET /v1/login-rewards/{player_id}Reads today’s login reward status
POST /v1/login-rewards/{player_id}/claimGrants the daily login reward
POST /v1/questsSaves a quest definition
GET /v1/quests, GET /v1/quests/{key}Lists / reads quests
POST /v1/quests/{key}/claimGrants the quest period’s reward
GET /v1/npc-rewards/{key}/eligibilityReads NPC reward eligibility (does not write)
POST /v1/npc-rewards/{key}/claimGrants the NPC reward
GET /v1/vip/{player_id}Reads the VIP tier
POST /v1/vip/{player_id}/cards/{item_instance_id}/useUses a VIP card

Note

In the TypeScript SDK only the reward table has a typed client (CrxClient.createRewardTable, getRewardTable, grantRewardTable). The login reward, quest, NPC and VIP endpoints are called with raw REST. See TypeScript SDK.

Saving a reward table

POST /v1/reward-tables
Authorization: Bearer crx_live_<prefix>_<secret>
Content-Type: application/json

{
  "key": "welcome_pack",
  "name": "Welcome pack",
  "rolls": 1,
  "entries": [
    { "chance_bp": 7000, "drop": { "type": "currency", "key": "gold", "min": 100, "max": 300 } },
    { "chance_bp": 2500, "drop": { "type": "item", "key": "health_potion", "min": 1, "max": 3 } },
    { "chance_bp": 500,  "drop": { "type": "nothing" } }
  ],
  "guaranteed": [ { "type": "currency", "key": "gold", "min": 50, "max": 50 } ]
}

The response is 201 with { id, version, key, name, rolls, entries, guaranteed, created_at_ms }; the draw seed is not present in any response. If the chances do not add up to 10000, the table is empty or the limits are exceeded, the registration is rejected with 422 and a plain-text message. The validate key is reserved and cannot be used as a table key.

Granting a reward from a table

POST /v1/reward-tables/welcome_pack/grant
Authorization: Bearer crx_live_<prefix>_<secret>
Idempotency-Key: welcome-player-1234
Content-Type: application/json

{ "player_id": "0192f0f2-5b1a-7c3e-9d11-4a5b6c7d8e9f", "source": { "type": "manual", "operator_id": "game-server" } }
{
  "grant_id": "…", "tx_id": "…", "event_id": "…",
  "table_key": "welcome_pack", "table_version": 1,
  "rewards": [ { "drop": "currency", "key": "gold", "quantity": 250 }, { "drop": "item", "key": "health_potion", "quantity": 2 } ],
  "replayed": false
}

Idempotency-Key is required. The same key and the same body do not grant the reward a second time; the first result is returned with replayed: true. The same key with a different body is 422. The source variants:

typeAdditional fields
manualoperator_id
content_entrygroup, entry
questquest_key
npc_rewardnpc_key
tournamenttournament_id, placement
leaderboard_seasonleaderboard_key, season
rulerule_key, event_id

Money and items are granted in one transaction: if the inventory is full or the currency supply cap would be exceeded, the whole grant is rolled back and 422 is returned. An NPC reward cannot be granted for a character that is not bound to a player.

Daily login reward

GET /v1/login-rewards/{player_id} returns today’s status: period_date, claimed, streak_day, current_streak, config_version. POST /v1/login-rewards/{player_id}/claim grants the reward and returns { period_date, streak_day, rewards, event_id, tx_id, replayed }; a second call on the same day returns the same receipt with replayed: true and grants no second reward.

The day is computed according to the environment’s time zone (timezone, Environments and domains). The reward schedule (the number of days, each day’s money reward) and the missed-day policy (reset_on_miss: the streak returns to 1; freeze: the streak is preserved) are a versioned configuration.

Note

There is no public endpoint or panel screen yet that writes the login reward configuration (the schedule and the streak policy); for now the configuration is set up by the Open Play operator.

Quests

POST /v1/quests saves a quest definition as a new version:

{
  "key": "kill_10_wolves",
  "name": "Hunt ten wolves",
  "objectives": [ { "key": "wolves", "event_type": "event_emitted", "count": 10, "filter_cel": "event.event_key == \"wolf_killed\"" } ],
  "rewards": [ { "type": "currency", "key": "gold", "amount": 500 }, { "type": "table", "key": "welcome_pack" } ],
  "recurrence": "daily"
}
  • objectives[].event_type: the event type that advances the quest (character events, score_submitted, login, item and money events and so on); count is how many times it is required; filter_cel is an optional condition.
  • rewards[].type: currency (key, amount) or table (a reward table key).
  • recurrence: none, daily, weekly. window and prerequisite_cel (compiled at registration) are optional.
  • The body of POST /v1/quests/{key}/claim is { "player_id": "…", "period_key": "…" } (period_key at most 160 characters). The response is { status: "claimed" | "already_claimed", reward_count, event_id } — claiming the same period a second time returns already_claimed and the reward is not repeated.
  • GET /v1/quests lists with search, limit, offset (it returns an in-page total).

Note

There is no public REST endpoint yet that reads quest progress (which quest a player is at and how far). Progress is computed on the server from events; for this reason your game server itself must know the period_key value.

NPC rewards

GET /v1/npc-rewards/{key}/eligibility?character_id=…&period_key=… reports eligibility without writing: { eligible, reason } (reason: cooldown, daily_limit, condition). POST /v1/npc-rewards/{key}/claim (Idempotency-Key required, body { "character_id", "period_key" }) either grants the reward or returns rejected: { status: "claimed" | "rejected", reward_count, claim_count, tx_id, reason }. There is no public endpoint yet that registers the NPC rule (cooldown, daily limit, CEL condition, reward).

VIP

The VIP tier is derived from cumulative points. GET /v1/vip/{player_id} returns { player_id, cumulative_points, tier, config_version }. POST /v1/vip/{player_id}/cards/{item_instance_id}/use (Idempotency-Key required) consumes a vip_card item and adds points; if the card does not meet the condition of the next tier up (current tier + 1), 422 is returned. A client key and a server key can use the same VIP surface.

Versioning and replay safety

  • Table grant, NPC claim and VIP card use: Idempotency-Key is required.
  • Login reward: a second claim on the same day returns the same receipt; no header is needed.
  • Quest claim: the period key is the natural replay safety (already_claimed).
  • Table and quest registrations are a new version each time; old versions do not change.
  • General rules: Idempotency and concurrency.

Events and webhooks

Every reward granted from a table or as a fixed amount of money produces the reward_granted event and can be delivered as a webhook. The envelope contains event_id, environment_id, player_id, type and data (source_kind, table_key, table_version and what was granted); there is no seed. Registration and verification: Webhooks.

Limits

  • At most 50 rows, 10 guaranteed rewards and 10 draws (rolls) in a table; quantities 1–1,000,000.
  • The chance_bp total is exactly 10000.
  • Quest period_key and claim fields are at most 160 characters.

Common errors

SymptomCauseFix
403A call with a client key or a player sessionMake the call with a server key, from your server
422 (table registration)The chances do not complete to 10000 or a limit was exceededFix the rows; complete them with a nothing row
422 inventory fullThere is no empty slot for the item to be grantedHave the player make room; nothing was written, so it can be retried
422 supply capThe currency’s max_supply limitRaise the cap in the panel
422 idempotency mismatchThe same key, a different bodyUse a new key
404 table not foundkey is wrong or the table has not been saved yetVerify with GET /v1/reward-tables/{key}

Working example

The flow of granting money from the welcome table and then buying from the store is on the Economy page. In TypeScript, the call crx.grantRewardTable("welcome_pack", { playerId, source, idempotencyKey }) goes to the same endpoint; if idempotencyKey is not given, the SDK generates one per call, so on a network retry you must supply the same key yourself.