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 do | Panel page |
|---|---|
| Set up a reward table, edit row chances and guaranteed rewards, see versions | Reward tables, Reward table profile |
| Define the currency to use in a reward | Currencies |
| Define the item to use in a reward | Content |
| Grant rewards with event-driven automation | Game 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) andguaranteed(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) anddrop. 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) ornothing. The quantity is always chosen from betweenminandmaxwith 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,ruleormanual.
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 path | What it does |
|---|---|
POST /v1/reward-tables | Saves a reward table as a new version |
GET /v1/reward-tables/{key} | Reads the current version |
POST /v1/reward-tables/{key}/grant | Grants a reward from the table, idempotently |
GET /v1/login-rewards/{player_id} | Reads today’s login reward status |
POST /v1/login-rewards/{player_id}/claim | Grants the daily login reward |
POST /v1/quests | Saves a quest definition |
GET /v1/quests, GET /v1/quests/{key} | Lists / reads quests |
POST /v1/quests/{key}/claim | Grants the quest period’s reward |
GET /v1/npc-rewards/{key}/eligibility | Reads NPC reward eligibility (does not write) |
POST /v1/npc-rewards/{key}/claim | Grants the NPC reward |
GET /v1/vip/{player_id} | Reads the VIP tier |
POST /v1/vip/{player_id}/cards/{item_instance_id}/use | Uses 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:
type | Additional fields |
|---|---|
manual | operator_id |
content_entry | group, entry |
quest | quest_key |
npc_reward | npc_key |
tournament | tournament_id, placement |
leaderboard_season | leaderboard_key, season |
rule | rule_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);countis how many times it is required;filter_celis an optional condition.rewards[].type:currency(key,amount) ortable(a reward table key).recurrence:none,daily,weekly.windowandprerequisite_cel(compiled at registration) are optional.- The body of
POST /v1/quests/{key}/claimis{ "player_id": "…", "period_key": "…" }(period_keyat most 160 characters). The response is{ status: "claimed" | "already_claimed", reward_count, event_id }— claiming the same period a second time returnsalready_claimedand the reward is not repeated. GET /v1/questslists withsearch,limit,offset(it returns an in-pagetotal).
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_keyvalue.
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-Keyis 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_bptotal is exactly 10000. - Quest
period_keyandclaimfields are at most 160 characters.
Common errors
| Symptom | Cause | Fix |
|---|---|---|
403 | A call with a client key or a player session | Make the call with a server key, from your server |
422 (table registration) | The chances do not complete to 10000 or a limit was exceeded | Fix the rows; complete them with a nothing row |
422 inventory full | There is no empty slot for the item to be granted | Have the player make room; nothing was written, so it can be retried |
422 supply cap | The currency’s max_supply limit | Raise the cap in the panel |
422 idempotency mismatch | The same key, a different body | Use a new key |
404 table not found | key is wrong or the table has not been saved yet | Verify 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.