# Scores and storage

> Leaderboards with no server of your own, and three kinds of storage. Cloud storage follows a player to any phone; device and secure storage stay on this one. From the Lia developer docs: https://docs.lialabs.org/reference/sdk-scores-and-storage

## Scores

Each app has one leaderboard. Only a player's best score counts.

### Lia.submitScore(score, options)

Records a score. If it beats the player's best, it becomes their best.

```js
const { best } = await Lia.submitScore(1280);
```

| Parameter | Type | |
| --- | --- | --- |
| `score` | number | A whole number, 0 to 1,000,000,000,000. Higher is better. |
| `options.idempotencyKey` | string | Optional (SDK 1.5). See [Idempotency keys](/reference/sdk-payments-and-ads#idempotency-keys). |

Resolves with `{ best }`: the player's best score now. Errors: `bad_score`, `bad_idempotency_key`, `not_open`.

> **Important** Scores come from your page, so a determined player could send any score. Fine for bragging rights; for prizes, decide the winners on your server.

### Lia.leaderboard()

The top 50, and where this player stands.

```js
const { top, me } = await Lia.leaderboard();
```

Resolves with:

```json
{
  "top": [
    { "rank": 1, "name": "amara", "score": 4210, "me": false },
    { "rank": 2, "name": "kofi", "score": 3990, "me": true }
  ],
  "me": { "rank": 2, "score": 3990 }
}
```

`name` is the Lia username. Ties go to whoever got there first. `me` is `null` until the player has a score.

## Storage

| | Where | Limits | For |
| --- | --- | --- | --- |
| `Lia.cloudStorage` | Lia's server, per player | 1024 keys, values up to 4096 characters | Progress and settings that follow the player to any phone |
| `Lia.deviceStorage` | This phone, for your app | 5 MB in all | Caches and drafts |
| `Lia.secureStorage` | This phone's secure store (the Keychain on iPhone) | 10 items, values up to 2048 characters | Tokens and secrets |

Each app only ever sees its own keys. Keys are 1 to 128 of letters, digits, `_` and `-`; values are text (store JSON with `JSON.stringify`). Bad keys and values reject with `bad_key`, `bad_keys` or `bad_value`.

### Lia.cloudStorage

On Lia's server, for this player in your app, on any phone. Test and live share it.

```js
await Lia.cloudStorage.setItem("progress", JSON.stringify({ level: 7, coins: 120 }));
const progress = JSON.parse((await Lia.cloudStorage.getItem("progress")) ?? "{}");
```

| Method | Resolves with |
| --- | --- |
| `Lia.cloudStorage.setItem(key, value, options)` | `true`. Fails with `storage_full` at 1024 keys. |
| `Lia.cloudStorage.getItem(key)` | The value, or `null`. |
| `Lia.cloudStorage.getItems(keys)` | An object of the keys that exist and their values. 1 to 100 keys. |
| `Lia.cloudStorage.removeItem(key, options)` | `true` |
| `Lia.cloudStorage.removeItems(keys, options)` | `true`. 1 to 100 keys. |
| `Lia.cloudStorage.getKeys()` | All of this player's keys, sorted. |

The writes take an optional `{ idempotencyKey }` last (SDK 1.5), so a late retry of an old write never overwrites a newer value. See [Idempotency keys](/reference/sdk-payments-and-ads#idempotency-keys).

Errors: `not_open` (call `init()` first), `storage_full`, `bad_item`, `bad_keys`, `bad_idempotency_key`.

### Lia.deviceStorage

On this phone only, for your app. Gone if the person removes Lia or moves to another phone.

| Method | Resolves with |
| --- | --- |
| `Lia.deviceStorage.setItem(key, value)` | `true`. Values up to 1,048,576 characters; everything your app keeps together (keys and values) up to 5 MB, or `storage_full`. |
| `Lia.deviceStorage.getItem(key)` | The value, or `null`. |
| `Lia.deviceStorage.removeItem(key)` | `true` |
| `Lia.deviceStorage.clear()` | `true`. Removes all of your app's keys on this phone. |

### Lia.secureStorage

In the phone's secure store (the Keychain on iPhone), for your app.

| Method | Resolves with |
| --- | --- |
| `Lia.secureStorage.setItem(key, value)` | `true`. Values up to 2048 characters; up to 10 items, or `storage_full`. |
| `Lia.secureStorage.getItem(key)` | The value, or `null`. |
| `Lia.secureStorage.removeItem(key)` | `true` |
| `Lia.secureStorage.clear()` | `true` |

Your page's own `localStorage` also works, but it's tied to Lia's web view and may be cleared; prefer these.
