# Codes API

> Let a player redeem a gift code for free coins and items, with the server API or the client API, and read the creator code of an order.

A **gift code** is a code that your studio creates in the dashboard (« Codes », in French for now) and hands out: at a convention, in a contest, to a partner. A player who types it receives **free coins** (the `bonus` bucket) and items, once. A **creator code** is different: the buyer types it at checkout, and it is covered in [Checkouts](/docs/api/checkouts) and in the [guide](/docs/guides/codes). The examples on this page continue from the [setup](/docs/api#setup-for-the-examples).

## Redeem a gift code

Credits the free coins and items of a gift code to a player. A code works **once per player** and up to its own limit of uses. The coins are **always free**: a code never gives paid coins, and it cannot be passed from one player to another.

```endpoint
POST /players/{player}/codes/redeem
```

**Authentication:** secret key. **Idempotency:** required (`Idempotency-Key` header). Limit: 10 attempts per 15 minutes per player.

```endpoint
POST /client/me/codes/redeem
```

**Authentication:** publishable key and player token. **Idempotency:** required. **CORS:** open. Limit: 10 attempts per 15 minutes per player and 30 per IP address. This is the only client route that adds coins, and only free ones, from a code that your studio issued.

**Path parameters** (server route)

| Parameter | Type | Description |
|---|---|---|
| `player` | string | A GameCoin player id, or `ext:<your id>` URL-encoded once. An unknown `ext:` player is created. |

**Body**

| Field | Type | Required | Rules |
|---|---|---|---|
| `code` | string | yes | The code as the player types it: 1 to 64 characters. Upper or lower case, spaces and dashes are ignored, so `summer-k7m2q-xh4np` and `SUMMERK7M2QXH4NP` are the same code. |

```bash
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/codes/redeem" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: redeem-user-42-summer" \
  -H "Content-Type: application/json" \
  -d '{"code": "SUMMER-K7M2Q-XH4NP"}'
```

From a browser, with the publishable key and the token of the player:

```javascript
const response = await fetch("https://gamecoin.apilow.com/api/v1/client/me/codes/redeem", {
  method: "POST",
  headers: {
    "X-GameCoin-Key": publishableKey,
    Authorization: `Bearer ${playerToken}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ code: typedByThePlayer }),
});
if (response.status === 400) showMessage("This code is not valid."); // unknown, expired, used up, disabled or already used
```

**Response** `200 OK`.

| Field | Type | Description |
|---|---|---|
| `grants` | array | One line per currency credited: `currency`, `amount` (always added to the `bonus` bucket) and the `balance` afterwards ([Balance](/docs/api/objects#balance)). |
| `items` | array | The items added to the inventory: `sku` and `quantity`. |
| `skipped` | array | The items the player could not receive, because they already own the most they can of a durable item. The rest of the code is still given. |

```json
{
  "grants": [{ "currency": "gems", "amount": 100, "balance": { "currency": "gems", "paid": 0, "bonus": 100, "total": 100 } }],
  "items": [{ "sku": "potion", "quantity": 2 }],
  "skipped": []
}
```

Send the same request again with the same key and you get the **same answer** with `Idempotent-Replayed: true`, and nothing is credited twice. The ledger entry has the type `gift_code`, and its `metadata.code` is the code.

**Errors**

| Status | Code | When | What to do |
|---|---|---|---|
| 400 | `VALIDATION_FAILED` | `details.fieldErrors.code` is `["CODE_INVALID"]`: the code is unknown, malformed, expired, used up, disabled, already used by this player, or not a gift code | Tell the player the code is not valid. Nothing says which of these it is. |
| 400 | `VALIDATION_FAILED` | An unknown field, or `code` missing or too long | Read `details.fieldErrors`. |
| 400 | `IDEMPOTENCY_KEY_REQUIRED` | The `Idempotency-Key` header is missing or empty | Send one. |
| 401 | `UNAUTHENTICATED` | Client route: the player token is missing or expired | Ask for a new token. |
| 403 | `PLAYER_BLOCKED` | The player is blocked | Nothing can be redeemed for this player until they are unblocked. |
| 404 | `NOT_FOUND` | A GameCoin player id that does not exist | Check the id. |
| 409 | `LIMIT_REACHED` | The player already owns the most they can of every item of the code, and the code gives nothing else. The code is **not** used up | Tell the player they already have this reward. |
| 409 | `CONFLICT` | The currencies and items of the code were archived since it was created. The code is not used up | Contact the studio of the game. |
| 409 | `IDEMPOTENCY_CONFLICT` | The key was already used with a different code | Use a new key for a new redemption. |
| 429 | `RATE_LIMITED` | Too many attempts, valid or not: `Retry-After` gives the seconds to wait | Slow down. |

```json
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Invalid or expired code",
    "details": { "fieldErrors": { "code": ["CODE_INVALID"] } }
  }
}
```

**Notes**

- **Every refusal looks the same.** An answer that told "expired" from "unknown" would let anyone find real codes by trying. Codes have ten random characters from an alphabet without look-alikes (no `0`, `O`, `1`, `I` or `L`), and the attempts are limited, so guessing a code is not realistic. Do not try to tell the cases apart in your game: show one friendly message.
- The attempts are counted whether the code is right or wrong. A player who types 10 wrong codes must wait for the next quarter of an hour; the server route has no limit per IP address, because the address would be your backend's.
- A code redeemed through the server API for an unknown `ext:` player creates that player, like the other `POST` routes.
- A creator code is not a gift code: typing one here gives the same `CODE_INVALID`.
- The server API and the client API do the same thing. Use the client route when the player types the code in a game without a backend; use the server route when your backend checks the player first.
