Overview
Codes are authored on the dashboard with their reward (currency, items, bundles) and consumability rules. The server enforces single- and shared-redemption limits - the SDK is purely a thin transport.
Redeeming a code
exampleC#
1using KalmForge;23var result = await PromoCodeRedeemer.RedeemCode("WELCOME2026");4if (result.success) {5 Debug.Log($"Got {result.reward_data.amount} {result.reward_data.type}");6} else {7 Debug.LogWarning($"{result.ErrorCodeEnum}: {result.error}");8}Error codes
Strongly typed via RedeemResponse.ErrorCodeEnum:
| Name | Type | Description |
|---|---|---|
| INVALID_REQUEST | ErrorCode | Empty code or missing fields. |
| INVALID_FORMAT | ErrorCode | Server response could not be parsed. |
| MISSING_PROJECT_ID | ErrorCode | X-Project-Id header missing. |
| CODE_NOT_FOUND | ErrorCode | Code does not exist. |
| CODE_NOT_ISSUED | ErrorCode | Code is in a campaign but has not been issued yet. |
| CODE_ALREADY_REDEEMED | ErrorCode | This player already redeemed this code. |
| CODE_LIMIT_REACHED | ErrorCode | Shared code is out of remaining redemptions. |
| INTERNAL_ERROR | ErrorCode | Server-side failure. Safe to retry. |
| NETWORK_ERROR | ErrorCode | Connection failure. Safe to retry. |
Shared (non-consumable) codes
exampleC#
1if (!result.is_consumable && PromoCodeRedeemer.HasRemainingUses(result)) {2 Debug.Log($"{result.remaining_redemptions} of {result.max_redemptions} left");3}Note
max_redemptions = -1 means unlimited. HasRemainingUses returns true in that case.Issuing codes (partner integrations)
PromoCodeIssuer is intended for trusted server-side use (a creator portal, an affiliate webhook). Hard-code the partner API key on the server, never in the Unity client.
exampleC#
1var issuer = new PromoCodeIssuer(partnerApiKey);2var resp = await issuer.IssueCode(campaignId: "creator_alpha", partnerPromoCode: "LIVESTREAM");3if (resp.success) Debug.Log($"Issued {resp.reward_code} (request {resp.request_id})");API reference
PromoCodeRedeemer - static API
| Name | Type | Description |
|---|---|---|
| RedeemCode(code) | Task<RedeemResponse> | Redeem a single code for the current player. |
| HasRemainingUses(response) | bool (static) | Helper for shared (non-consumable) codes. |
RedeemResponse - fields
| Name | Type | Description |
|---|---|---|
| success | bool | True on a successful redemption. |
| campaign_id | string | Source campaign of this code. |
| reward_type | string | "coins" | "gems" | "item" | "bundle" | … |
| reward_data | RewardData | type / amount / item_id. |
| is_consumable | bool | True for one-shot personal codes. |
| redemption_count / max_redemptions / remaining_redemptions | int | -1 = unlimited / not provided. |
| error_code | string | Stable enum (see ErrorCodeEnum). |
| error | string | Human-readable detail. |
PromoCodeIssuer - instance API
| Name | Type | Description |
|---|---|---|
| new PromoCodeIssuer(partnerApiKey) | ctor | Server-side only. Reads ProjectId + endpoint from KalmForgeClient. |
| IssueCode(campaignId, partnerPromoCode?) | Task<IssueResponse> | Mint and return a single reward_code. |
REST endpoints
examplebash
1# Redeem (client)2POST /api/public/promo/redeem3Headers: X-Project-Id: PROJECT4{ "reward_code": "WELCOME2026", "player_id": "...", "device_id": "..." }56# Issue (partner backend)7POST /api/public/promo/issue8Headers:9 Authorization: Bearer kf_partner_xxx10 X-Project-Id: PROJECT11 X-Request-ID: <uuid> # idempotency key12{ "campaign_id": "creator_alpha", "partner_promo_code": "LIVESTREAM" }