# Inventory API

> List what a player owns, give them items and consume them from your backend with the server API.

The inventory is what a player owns: the quantity of each item of your catalog. An item is either `consumable` (used up, like a potion) or `durable` (kept, like a sword, often limited to one). This page lists an inventory, grants items for free and consumes them. To sell an item for coins, use [Purchases](/docs/api/purchases). The examples on this page continue from the [setup](/docs/api#setup-for-the-examples).

## List the inventory

Returns the items a player owns. Use it to show an inventory screen, or to check that a player owns an item before you let them use it.

```endpoint
GET /players/{player}/inventory
```

**Authentication:** secret key. **Idempotency:** not needed.

**Path parameters**

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

<!-- tabs:start -->
```bash tab="curl"
curl "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/inventory" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY"
```
```javascript tab="Node.js"
const items = await gamecoin.inventory.list(player);
for (const item of items) console.log(item.sku, item.quantity);
```
```python tab="Python"
for item in gamecoin.inventory.list(player):
    print(item.sku, item.quantity)
```
```php tab="PHP"
foreach ($gamecoin->inventory->list($player) as $item) {
    echo $item->sku, ' ', $item->quantity, "\n";
}
```
```go tab="Go"
owned, err := client.Inventory.List(ctx, player)
check(err)
for _, item := range owned {
	fmt.Println(item.SKU, item.Quantity)
}
```
```csharp tab="C#"
foreach (var item in await gamecoin.Inventory.ListAsync(player))
{
    Console.WriteLine($"{item.Sku} {item.Quantity}");
}
```
<!-- tabs:end -->

**Response** `200 OK`: the owned items.

| Field | Type | Description |
|---|---|---|
| `items` | array of [InventoryItem](/docs/api/objects#inventoryitem) | The items with a quantity above 0. Empty when the player owns nothing. |

```json
{
  "items": [{ "sku": "potion", "quantity": 3, "updatedAt": "2026-10-05T22:14:37.251Z" }]
}
```

**Errors**

| Status | Code | When | What to do |
|---|---|---|---|
| 400 | `VALIDATION_FAILED` | The player reference is malformed (`PLAYER_REF_INVALID`) | Encode the `ext:` reference exactly once. |
| 404 | `NOT_FOUND` | The player does not exist | An `ext:` player exists after its first write; before that, treat the inventory as empty. |

**Notes**

- The list holds **owned items only**: an item the player has used up (quantity 0) is not listed. The SDKs return the array of items directly (`inventory.list`), without the `items` wrapper.
- `sku` is the key of the item in your catalog: see [Get the catalog](/docs/api/catalog#get-the-catalog) for its name, type, price and limit.

## Grant an item

Gives items to a player for free: a reward, a gift, a compensation. Nothing is debited. It is for your backend only: the client API can never give an item.

```endpoint
POST /players/{player}/inventory/grant
```

**Authentication:** secret key. **Idempotency:** required (`Idempotency-Key` header).

**Path parameters**

| 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 |
|---|---|---|---|
| `sku` | string | yes | The sku of an active item of the catalog: 1 to 48 characters. |
| `quantity` | integer | no | A whole number from 1 to 1,000,000,000,000. Default 1. |

<!-- tabs:start -->
```bash tab="curl"
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/inventory/grant" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: grant-potions-user-42" \
  -H "Content-Type: application/json" \
  -d '{"sku": "potion", "quantity": 3}'
```
```javascript tab="Node.js"
const item = await gamecoin.inventory.grant(player, { sku: "potion", quantity: 3 }, { idempotencyKey: "grant-potions-user-42" });
console.log(item.sku, item.quantity, item.replayed);
```
```python tab="Python"
item = gamecoin.inventory.grant(player, sku="potion", quantity=3, idempotency_key="grant-potions-user-42")
print(item.sku, item.quantity, item.replayed)
```
```php tab="PHP"
$item = $gamecoin->inventory->grant($player, ['sku' => 'potion', 'quantity' => 3], ['idempotency_key' => 'grant-potions-user-42']);
echo $item->sku, ' ', $item->quantity, ' ', var_export($item->replayed, true), "\n";
```
```go tab="Go"
item, err := client.Inventory.Grant(ctx, player, gamecoin.InventoryParams{SKU: "potion", Quantity: 3},
	gamecoin.WithIdempotencyKey("grant-potions-user-42"))
check(err)
fmt.Println(item.SKU, item.Quantity, item.Replayed)
```
```csharp tab="C#"
var item = await gamecoin.Inventory.GrantAsync(
    player,
    new InventoryGrantRequest { Sku = "potion", Quantity = 3 },
    new RequestOptions { IdempotencyKey = "grant-potions-user-42" });
Console.WriteLine($"{item.Sku} {item.Quantity} {item.Replayed}");
```
<!-- tabs:end -->

**Response** `200 OK`: the item with its new quantity.

| Field | Type | Description |
|---|---|---|
| `item` | [InventoryItem](/docs/api/objects#inventoryitem) | The item and the quantity the player owns **after** the grant. |

```json
{ "item": { "sku": "potion", "quantity": 3, "updatedAt": "2026-10-05T22:14:37.251Z" } }
```

**Errors**

| Status | Code | When | What to do |
|---|---|---|---|
| 400 | `IDEMPOTENCY_KEY_REQUIRED` | The `Idempotency-Key` header is missing or empty | Send one: see [Idempotency](/docs/api#idempotency). |
| 400 | `VALIDATION_FAILED` | An unknown field; `sku` empty or longer than 48 characters; `quantity` not an integer between 1 and 10¹²; a key that is not 1 to 100 printable ASCII characters | Read `details.fieldErrors`. |
| 403 | `PLAYER_BLOCKED` | The player is blocked | Nothing can be written for this player until the player is unblocked. |
| 404 | `NOT_FOUND` | The item is not in the catalog (or is archived), or a GameCoin id does not exist | Check the sku against [Get the catalog](/docs/api/catalog#get-the-catalog). |
| 409 | `LIMIT_REACHED` | The grant would take the player over the item's `maxOwned`: `details.maxOwned` and `details.owned` | Nothing was granted. Grant less, or nothing: the player already has the item. |
| 409 | `IDEMPOTENCY_CONFLICT` | The key was already used with a different request | Use a new key for a new grant. |

```json
{
  "error": {
    "code": "LIMIT_REACHED",
    "message": "Item ownership limit exceeded",
    "details": { "maxOwned": 1, "owned": 1 }
  }
}
```

**Notes**

- **Replay.** The same key with the same request returns the original response again, with the header `Idempotent-Replayed: true`, and changes nothing.
- A grant adds to what the player owns; it never replaces it. Granting 3 potions to a player who owns 2 leaves them with 5.
- A limit is all or nothing: a grant that would pass `maxOwned` is refused entirely, not cut down to fit. For an item with `maxOwned: 1`, grant it once.
- The SDKs return the inventory item with a `replayed` flag (`Replayed` in Go and C#) that is `true` when the answer is the replay of an earlier request with the same key.
- The first write on an unknown `ext:` player creates it.

## Consume an item

Removes units of a **consumable** item: the player drinks a potion, you take a ticket. Use it when your game confirms the use of an item, so that the inventory stays the source of truth.

```endpoint
POST /players/{player}/inventory/consume
```

**Authentication:** secret key. **Idempotency:** required (`Idempotency-Key` header).

**Path parameters**

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

**Body**

| Field | Type | Required | Rules |
|---|---|---|---|
| `sku` | string | yes | The sku of an active item of the catalog: 1 to 48 characters. |
| `quantity` | integer | no | A whole number from 1 to 1,000,000,000,000. Default 1. |

<!-- tabs:start -->
```bash tab="curl"
curl -X POST "https://gamecoin.apilow.com/api/v1/players/ext%3Auser-42/inventory/consume" \
  -H "Authorization: Bearer $GAMECOIN_SECRET_KEY" \
  -H "Idempotency-Key: use-potion-user-42-1" \
  -H "Content-Type: application/json" \
  -d '{"sku": "potion"}'
```
```javascript tab="Node.js"
const item = await gamecoin.inventory.consume(player, { sku: "potion" }, { idempotencyKey: "use-potion-user-42-1" });
console.log(item.sku, item.quantity, item.replayed);
```
```python tab="Python"
item = gamecoin.inventory.consume(player, sku="potion", idempotency_key="use-potion-user-42-1")
print(item.sku, item.quantity, item.replayed)
```
```php tab="PHP"
$item = $gamecoin->inventory->consume($player, ['sku' => 'potion'], ['idempotency_key' => 'use-potion-user-42-1']);
echo $item->sku, ' ', $item->quantity, ' ', var_export($item->replayed, true), "\n";
```
```go tab="Go"
item, err := client.Inventory.Consume(ctx, player, gamecoin.InventoryParams{SKU: "potion"},
	gamecoin.WithIdempotencyKey("use-potion-user-42-1"))
check(err)
fmt.Println(item.SKU, item.Quantity, item.Replayed)
```
```csharp tab="C#"
var item = await gamecoin.Inventory.ConsumeAsync(
    player,
    new InventoryConsumeRequest { Sku = "potion" },
    new RequestOptions { IdempotencyKey = "use-potion-user-42-1" });
Console.WriteLine($"{item.Sku} {item.Quantity} {item.Replayed}");
```
<!-- tabs:end -->

**Response** `200 OK`: the item with its remaining quantity.

| Field | Type | Description |
|---|---|---|
| `item` | [InventoryItem](/docs/api/objects#inventoryitem) | The item and the quantity the player owns **after** the consumption. It is returned even when the quantity reaches 0. |

```json
{ "item": { "sku": "potion", "quantity": 2, "updatedAt": "2026-10-05T22:14:37.830Z" } }
```

**Errors**

| Status | Code | When | What to do |
|---|---|---|---|
| 400 | `IDEMPOTENCY_KEY_REQUIRED` | The `Idempotency-Key` header is missing or empty | Send one: see [Idempotency](/docs/api#idempotency). |
| 400 | `VALIDATION_FAILED` | An unknown field; `sku` empty or longer than 48 characters; `quantity` not an integer between 1 and 10¹²; a key that is not 1 to 100 printable ASCII characters | Read `details.fieldErrors`. |
| 403 | `PLAYER_BLOCKED` | The player is blocked | Nothing can be written for this player until the player is unblocked. |
| 404 | `NOT_FOUND` | The item is not in the catalog, or a GameCoin id does not exist | Check the sku against [Get the catalog](/docs/api/catalog#get-the-catalog). |
| 409 | `INSUFFICIENT_FUNDS` | The player owns fewer units than `quantity`: `details.required` and `details.available` | Nothing was consumed. Refuse the action in your game. |
| 409 | `CONFLICT` | The item is `durable`: a durable item cannot be consumed | Only consume items of type `consumable`. |
| 409 | `IDEMPOTENCY_CONFLICT` | The key was already used with a different request | Use a new key for a new consumption. |

```json
{
  "error": {
    "code": "INSUFFICIENT_FUNDS",
    "message": "Not enough items",
    "details": { "required": 100, "available": 3 }
  }
}
```

**Notes**

- **Replay.** The same key with the same request returns the original response again, with the header `Idempotent-Replayed: true`, and changes nothing.
- Not owning the item at all is the same as owning 0: it answers `INSUFFICIENT_FUNDS` with `available: 0`. The code is the one of the wallet, here applied to a quantity of items.
- A consumption is all or nothing: asking for more than the player owns consumes nothing.
- An item that reaches 0 stays out of [List the inventory](#list-the-inventory), but its last quantity is in the response so that your game can show it.
- The browser can consume too, with the player's own token: [Consume an item](/docs/api/client#consume-an-item) in the client API.
