# Points (/docs/concepts/points)



Points are how your members keep score. They earn points for what they do, spend them on rewards, and climb leaderboards with them. For example:

* **XP** for every quest a member completes.
* **Raffle tickets** a member spends to enter a prize draw.
* **Event credits** your own system hands out at the door.

Each kind of points lives in its own **point ledger**. A project can have several, and every member has a balance in each one. Quests, referrals and your own trusted systems can all add points.

```mermaid
flowchart LR
    Q["Quest rewards"] --> L["Point ledger"]
    R["Referral rewards"] --> L
    B["Trusted backend awards"] --> L
    L --> Bal["Member balance"]
    L --> Board["Leaderboard standings"]
```

## What members see [#what-members-see]

A signed-in member sees their own balance in each ledger and the history of how they earned and spent it, newest first. Each history entry names the quest that paid it when there is one. Members never see the notes an operator wrote when changing their points.

Points only ever come from you: from a quest, a referral, a leaderboard prize, an engagement reward, or a change your team or your server makes. A member can't give themselves points.

## More than one kind of points [#more-than-one-kind-of-points]

Make a separate ledger when the points mean different things, such as XP that ranks members and tickets that members spend. Several leaderboards can read one ledger with different time windows, so you rarely need a ledger per board.

* **Renaming is safe.** A ledger keeps its identity when you rename it. Balances and history don't change, and two ledgers both named "XP" stay separate.
* **Archive to retire.** Archive a ledger when new quests and rewards should stop using it. Nothing is deleted: rewards you already published can still pay it, and members and operators can still read its balances and history. Console leaves an archived ledger out of new reward pickers.
* **Restore any time.** See [Restore an archived ledger](#restore-an-archived-ledger).

> [!DEV]
> Publication rejects a new quest, leaderboard, referral, engagement rule or priced reward that references an archived ledger. Ledger IDs are stable; names and descriptions only help people recognise a ledger. One points reward pays a whole number from 0 to 2,147,483,647, whether a quest, an evaluator decision, a leaderboard prize or a referral pays it. An offer price takes the same range but starts at 1.

For Console setup, see [Choose a point ledger](/docs/build/leaderboards#choose-a-point-ledger).

### Define a ledger in code [#define-a-ledger-in-code]

In shared authored configuration, use a stable ledger key. Domino resolves it to the environment's ID during publication. Define the ledger in `ledgers.ts`:

```ts
import { defineLedger } from "@domino-sdk/relay/authoring";

export const community = defineLedger({
  key: "community",
  name: "XP",
  description: "Community participation",
});
```

Publish that export from `relay.json`:

```json
{
  "pointLedgers": [
    { "entry": "ledgers.ts", "exportName": "community" }
  ]
}
```

Merge this property into the existing manifest. A quest can import the handle and award `points(community, 10)`.

> [!DEV]
> Authoring helpers take typed handles, while member and management reads return opaque string IDs. Unknown references fail validation. See [resource IDs and authoring keys](/docs/reference/authoring#resource-ids-and-authoring-keys).

### Match ledgers in your app [#match-ledgers-in-your-app]

Reads return ledger IDs, not keys. A referral reward, point account, or history entry has `balance: "ledger_…"` even when your code declared `"community"`. Comparing that value with a key never matches, and showing it puts an ID on screen.

1. Read the project's ledgers with `relay.pointLedgers.list()`, or `usePointLedgers()` in React, whose `data` holds the list once `status` is `ready`. Each entry has `id`, `key`, and `name`. Signed-out visitors can read it too.
2. Find your ledger by key, then compare IDs: `ledgers.find((l) => l.key === "community")?.id`.
3. Show the ledger's `name`, never its ID. Ledgers created in Console without a key have `key: null`.

Quest views already name their ledger: each points reward carries `amount` and `ledger: { id, key, name }`, and its `label` reads like "10 XP".

## Entries, balances, and available points [#entries-balances-and-available-points]

Every award, purchase, correction and reversal is recorded as an entry. A member's balance adds them up.

Engagement points are dated when the growth was measured. An [engagement rule](/docs/build/trackers#settle-engagement-points-daily) pays once a day, but each of its entries takes effect when the content sample that measured the growth was observed, so the points land in the leaderboard window and budget period of the measurement, not of the payment.

| Term             | Meaning                                                                                                   |
| ---------------- | --------------------------------------------------------------------------------------------------------- |
| Current balance  | The sum of the member's entries.                                                                          |
| Reservation      | Points held for a reward that's on its way. They aren't spent yet, but the member can't spend them twice. |
| Available points | What the member can spend after reservations.                                                             |

* **Balances never go negative.** Deductions cannot make a balance negative or consume reserved points.
* **Only trusted server operations change points.** A member's own session cannot grant points.
* **A retried award pays once.** Retrying an external award requires the same action ID and payload, so a lost response does not produce a second award. See [Supply points from a trusted backend](/docs/build/leaderboards#supply-points-from-a-trusted-backend).

> [!DEV] Entry timestamps
> New entries record `receivedAt` from the database clock when they are inserted. It is independent of `effectiveAt`, which controls the scoring window and can reflect backdating or a test member's advanced clock. Retrying an entry returns its original receipt time. Existing entries retain their recorded timestamps.

### Record points as an operator [#record-points-as-an-operator]

Your team can award, take away or correct a member's points. Each change needs a reason, and **Audit history** records it with who made it.

| Change                | CLI                                                                                                     | Management API `kind` |
| --------------------- | ------------------------------------------------------------------------------------------------------- | --------------------- |
| Award                 | `domino points award MEMBER LEDGER --amount 50 --reason "…"`                                            | `award`               |
| Record spending       | `domino points spend MEMBER LEDGER --amount 20 --reason "…"`                                            | `spend`               |
| Correct by an amount  | `domino points adjust MEMBER LEDGER --amount -5 --reason "…"`                                           | `adjust`              |
| Set an absolute total | `domino points set MEMBER LEDGER --total 120 --expected-total 100 --classification earned --reason "…"` | `set`                 |
| Reverse an entry      | `domino points reverse MEMBER LEDGER --entry ENTRY --amount 50 --reason "…"`                            | `reverse`             |

> [!DEV]
> Operators change points from a trusted server with `management.points.record`, or with `domino points` from the CLI, and need `points` permission. `LEDGER` is the point ledger's ID; `domino point-ledgers list` lists them. `domino points get MEMBER LEDGER` reads the member's total and held points, and `domino points list MEMBER LEDGER` their entries with each one's reason and actor. A CLI change is saved before it is sent: after an uncertain answer, run the error's `operation.resume` command, which repeats the same change without applying it twice. See [CLI automation](/docs/reference/automation#resource-commands).

### Show members their points [#show-members-their-points]

A signed-in member reads their own points from the browser. No server route or management key is needed.

```ts
import { createBrowserRelayClient } from "@domino-sdk/relay/browser"

const relay = createBrowserRelayClient({ baseUrl: "/relay" })
const { items } = await relay.me.balances()
const points = items.find((b) => b.ledger.key === "community")
// points.balance: the current balance. points.available: what they can spend now.

const page = await relay.me.pointHistory({ ledger: "community" })
// page.items: most recently recorded first, each { amount, kind, at, quest };
// pass page.nextCursor for more.
```

In React, `useBalances()` and `usePointHistory("community")` hold the same answers in `data`, and both read again whenever one of the member's quests completes.

* **Own points only.** Neither read takes a member: each answers for the session's member, in the project and environment the session belongs to.
* **Every ledger.** `balances()` lists every ledger in the project, including archived ledgers, with zero where the member has no entries.
* **No operator notes.** History entries leave out the reason and actor an operator recorded. Operators see them in Console, with `domino points list`, and through `management.points.history(member, ledger)`.
* **Recorded order.** History lists entries in the order Domino recorded them. `at` is when an entry took effect: a backdated award has an earlier `at`, and a reversal keeps the `at` of the entry it reverses.
* **Where points came from.** `quest` names the quest that paid an entry, through a completion or an engagement settlement. `kind` is `award`, `spend`, `adjust` or `reverse`, and Domino may add kinds. Referral payouts appear as adjustments (`adjust`, with no quest). An opening balance a [member import](/docs/use/imports) recorded is an adjustment with `imported: true`.

## Completion and standings [#completion-and-standings]

> [!IMPORTANT] A balance does not prove completion
> A point balance does not prove which quests a member completed. To require a quest before another, use a completion prerequisite (`completed(...)`), not a points threshold.

A [leaderboard](/docs/concepts/leaderboards) ranks members by a ledger. You choose whether spending lowers a member's rank:

| Board           | Spending         | Adjustments and reversals | Imported opening balances |
| --------------- | ---------------- | ------------------------- | ------------------------- |
| Current balance | Lowers the score | Included                  | Included                  |
| Earned points   | Excluded         | Relevant ones included    | Excluded                  |

A score that comes from outside Domino, such as a game's high score, gets its own ledger, updated with absolute values. That ledger is never spent or priced by rewards. See [external scores](/docs/concepts/leaderboards#external-scores).

> [!DEV] Notify your backend
> Positive point journal awards can notify an operator backend through the `points.awarded` [webhook event](/docs/reference/webhooks). Its `source` names what paid the award: a quest, a leaderboard prize, an engagement rule, or an operator's manual award, which has no source ID. Manual adjustments and imported opening balances (entry kind `adjust`) do not emit that event.

## Restore an archived ledger [#restore-an-archived-ledger]

Choose **Unarchive ledger** beside it in **Rewards → Point ledgers**, or **Unarchive point ledger** in **Settings → Resources**, and give a reason. The same ledger becomes available to new rewards again, with its balances and history unchanged.

> [!DEV]
> From the CLI, use `domino point-ledgers unarchive <key-or-id> --reason <text>` in test. The ledger's ID, balances and accounting history remain unchanged. Ledger names and descriptions are current operator configuration rather than versioned release history. See [resource lifecycle](/docs/concepts/quests#resource-lifecycle).
