# Limits & security

## Limits

| Limit | Value |
|---|---|
| `spendCoins` amount per call | `1..10000` |
| `earnCoins` amount per call | `1..100` |
| `earnCoins` per user per hour | 2000 coins |
| `saveProgress` payload size | 100 KB encoded |
| SDK messages per minute | 60 |
| Reason format | `^[a-z0-9_]+$`, ≤ 64 chars |
| Bridge response timeout | 30 s |

All limits are enforced on the server with the same values shown above — the
client can't raise them, even by calling the backend directly.

## Why your game can't cheat

- **Coins are server-authoritative.** Your game can only *request* a spend or earn;
  the server validates it and applies the change. The balance never lives in the game.
- **Identity comes from the authenticated session.** The game never sends a user id.
  The coin RPCs derive the player from the signed-in session and reject any request
  that targets a different account (`forbidden`), so a player can't grant or drain
  anyone's coins — including their own outside of gameplay.
- **Spends are atomic.** Rapid or concurrent calls can't double-spend.
- **Saves are per-player.** Row-level security ties every save to the signed-in
  player; no one can read or write another player's save.
- **Inert outside the app.** With no app session there's nothing to spend — see
  [App-only & offline](./guides/app-only-and-offline.md).

## Platform note

Coins and progress sync are live in the native app (Android/iOS) and on the web
for games served from the Gamma Games origin. A game loaded from a third-party
origin on the web can't be wired to the host securely, so the SDK reports
`isInApp === false` there and your offline fallback runs — see
[App-only & offline](./guides/app-only-and-offline.md).
