# Security model

> Who can do what in GameCoin, why a browser can never add coins to a balance, and what the service refuses by design.

## The rule

**A client never credits.** Code that runs in a browser or on a phone can be read and changed by the player. So GameCoin never lets it add to a balance, give an item, correct a balance or refund an order. The browser can only make a player's own balance go **down**, or pay. One exception, by design: a player can **redeem a gift code** your studio created. The value comes from the code, not from the client: it gives free coins only, works once per player, has a limited number of uses, and every refusal looks the same so codes cannot be guessed (see [Codes API](/docs/api/codes)).

That is what makes a game without a backend safe for real money. It also tells you where to put your own rules: whatever decides that a player *earns* something must run on your server.

## What each holder can do

| Holder | Can | Cannot |
|---|---|---|
| **Secret key** (`gc_sk_…`) | The whole server API for its game and environment: declare players, grant and spend, give and consume items, create checkouts, issue tokens, read and refund orders | Reach another game or the other environment |
| **Publishable key** (`gc_pk_…`) | Read the catalog. Create an anonymous player, if the game allows it. | Anything on a player, without a token |
| **Publishable key + player token** (`gc_pt_…`) | As that one player: read their wallet and inventory, **spend**, **buy an item** with a currency, **consume** an item, **pay for a pack**, read their own orders | Grant coins, give items, correct a balance, refund, act for another player |
| **Dashboard session** | What your role in the studio allows | |

Behind each row, GameCoin checks four things on every request:

1. The key says which game and which environment. Nothing in the request can change that.
2. A player token names one player. A token cannot read the order of another player: it answers `404 NOT_FOUND`.
3. Only a secret key reaches the server API, and only a publishable key reaches the client API. The wrong kind answers `403 FORBIDDEN`.
4. A body with a field GameCoin does not know is refused. There is no hidden field that gives more power.

## Why the client cannot credit

Think about a game where the browser could call `grant`. A player opens the developer tools, calls it with `amount: 1000000`, and owns a fortune. If the same game sells currency for money, that fortune is worth money.

With GameCoin, coins enter a balance in only two ways:

- a **paid order**, which GameCoin delivers after a payment;
- a call from **your server** with your secret key.

A player can change what their own browser does, but not what your server decides.

## Reward on your server

A game with a server rewards players like this:

1. The browser tells your server that something happened: "I finished level 3".
2. Your server checks that it is true, using what it knows.
3. Your server calls `grant` with an idempotency key built from the event.

The browser never says how much to credit. Your server does.

A game **without** a server cannot grant coins. It can still keep points of its own (a high score, experience, a progress bar) in the browser, and let players spend GameCoin currency on things. Do not use GameCoin currency as a prize that the browser decides to give out.

## What GameCoin will not do

GameCoin keeps its currencies closed. It has no call for any of these, and none will be added:

| It will not | In plain words |
|---|---|
| **Turn a currency back into money** | Gems can be bought. They cannot be cashed out. Coins and items are for use inside your game. |
| **Move a currency between players** | There is no transfer, gift or trade of balances. Only your server can give coins to a player. |
| **Share a currency between studios** | A currency belongs to one game of one studio. |
| **Take a bet or an entry stake** | No wagering and no stake tournaments, where players pay in to win a prize. |
| **Sell loot boxes for money** | A pack states what it contains. A pack is not a mystery box. |

These limits keep a GameCoin currency a game token and not money. Design your economy within them.

## How the API protects you

| Protection | How |
|---|---|
| Scope | The key picks game and environment; a resource of another game answers `404`, never `403`, so nothing about it is revealed |
| Strict bodies | An unknown field is a `400`; a body is at most 64 KiB |
| Safe retries | Every write that changes a wallet or an inventory needs an [idempotency key](/docs/concepts/idempotency) |
| No double spend | Operations on one wallet run one at a time |
| Return URLs | The `successUrl` and `cancelUrl` of a browser checkout must have the origin of the page that asks, so a script on another site cannot send your players somewhere else |
| CORS | Only the client API answers cross-origin requests, and only to the origins you allow for the game, so a secret key cannot be used from a web page by accident |
| Rate limits | Per key, per player and per IP: see [Rate limits](/docs/concepts/rate-limits) |
| Short tokens | A player token lasts one hour, since it cannot be revoked. Blocking the player stops it anyway. |
| Card data | Payment happens on a hosted page. No card number passes through your game or your server. |

## Checklist before you ship

- The secret key is only on your server, read from the environment, and not in your repository or your game bundle.
- Your server decides every grant, and every grant has an idempotency key made from the event.
- Your token endpoint sits behind your own login, so a player can only get a token for themselves.
- The browser code uses the publishable key only.
- Your game handles `INSUFFICIENT_FUNDS`, `LIMIT_REACHED` and `UNAUTHENTICATED` without crashing. See [Errors](/docs/concepts/errors).
- You tried the whole flow in the [test environment](/docs/guides/testing).

## Where next

- [Keys and environments](/docs/concepts/keys-and-environments)
- [A game with a backend](/docs/guides/game-with-backend): the flow that follows this model.
