# Rewards (/docs/concepts/rewards)



A reward is what a member gets for taking part. Most rewards come from completing a quest; members can also buy some with their points. For example:

* **50 XP** for answering a quiz.
* **An "OG" badge** for joining in the first week.
* **A Discord role** that opens a members-only channel.
* **A tote bag** collected at your booth, or bought for 500 points.

A quest's rewards go to the member who completes it. A [referral program](/docs/concepts/referrals) can reward both the inviter and the friend they invited.

```mermaid
flowchart LR
    C["Qualifying result"] --> P["Points award"]
    C --> E["Tier entitlement"]
    C --> R["Role delivery request"]
    O["Offer redemption"] --> S["Points spend"]
    O --> E
    E --> H["Staff handover"]
    E --> A["Automatic fulfillment"]
    R --> G["Role granted, with retries"]
```

## Reward kinds [#reward-kinds]

| Reward           | What the member gets                                                                           | Granted by                               |
| ---------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------- |
| Badge            | An achievement they hold once, however many quests award it.                                   | Quests and reviewers                     |
| Group membership | A place in a [group](/docs/concepts/groups), kept while the completion stands.                 | Quests only                              |
| Points           | Points in one of your [point ledgers](/docs/concepts/points).                                  | Quests, referrals and leaderboard prizes |
| Tier reward      | A prize, such as merch or a ticket, with levels it can improve through before they collect it. | Quests, referrals and leaderboard prizes |
| Discord role     | A role in your Discord server, granted by Domino's bot.                                        | Quests only                              |
| Offer            | A tier reward bought with points, from stock you set.                                          | The member, by redeeming it              |

Offers sell tier rewards. Referral programs and leaderboard prizes pay points and tier rewards. Badges, group memberships and Discord roles come only from quests (and reviewers, for badges): to give one for a referral or a leaderboard place, reward it from a quest instead. See [referrals](/docs/concepts/referrals) before choosing which one owns an award.

### Match tiered rewards in your app [#match-tiered-rewards-in-your-app]

Reads name a tiered reward by its stored ID, not the key you gave `defineTieredReward`. An entitlement's `reward.id` is `reward_…` even when the source says `key: "tote"`.

1. Read the project's tiered rewards with `relay.tieredRewards.list()`, or `useTieredRewards()` in React, whose `data` holds the list. Each entry has `id` and `key`.
2. Find your reward by key, then compare IDs: `rewards.find((r) => r.key === "tote")?.id`.
3. Show the reward's `title` from the read itself, never its ID.

Quest views already carry the key: a tier reward has `reward: { id, key, title }` and the `tier` it grants.

### Retire a tiered reward [#retire-a-tiered-reward]

When a tiered reward is done, such as merch from a finished campaign, archive it so nothing new hands it out. In test, open **Settings → Resources**, choose **Archive tiered reward** on it and give a reason, then promote to live.

* **Nothing new can grant it.** A deploy that would make a quest, offer or leaderboard prize grant it fails until you unarchive it or remove it from that code.
* **What already grants it keeps working.** A quest, offer or prize that granted it before you archived it keeps granting it, a new version of it included. To stop those grants too, archive them.
* **Members keep what they earned.** Rewards they earned can still be handed over, and their apps still show them.
* **Console stops listing it.** **Rewards** leaves it out of its rewards, but still shows the ones members earned.
* **Its key stays taken.** A new tiered reward with the same key must have the same definition. Give a replacement its own key.

**Unarchive tiered reward** brings it back. Badge and group membership rewards aren't listed here: archive the badge or group instead.

> [!DEV]
> `domino tiered-rewards list` lists them with their `archivedAt`, and `domino tiered-rewards archive|unarchive KEY --reason "…"` archives and restores one in test, which needs `publish` permission. `management.resources.list()` returns them in `tieredRewards`, and `management.tieredRewards.archive(key, { reason })` and `unarchive` archive and restore one. The commands and methods are experimental and can change in a minor release. A deploy or promotion that would newly grant an archived one fails with 422 `tiered-reward-archived`, naming it in `reward`.

## Completion and delivery [#completion-and-delivery]

Completing a quest and receiving its reward are two separate moments. Points arrive with the completion. A Discord role or a tote bag can take longer.

* **Discord roles arrive on their own.** Domino records the role request with the completion, then keeps trying to grant it. It always goes to the Discord account and server the member had when they earned it. If you [revoke the completion](/docs/concepts/quests#revoke-a-completion), Domino removes the role, unless something else still earns it. Domino only removes roles it granted itself; roles members already had, or that you chose to keep, stay.
* **Physical rewards wait for pickup.** Earning a tote bag and handing it over are separate steps. A collection pass lets staff look the member up and start the pickup; confirming they took it is a separate action.
* **Digital rewards can be collected at once.** With automatic fulfillment, Domino marks the reward collected the moment it's granted, with no staff step.
* **Buying with points is all or nothing.** Domino holds one unit of stock and the member's points before granting the reward. A failed purchase spends nothing and takes no stock.
* **Promised rewards stay fixed.** Publishing new quest rules does not replace the reward an existing attempt was promised. Keep reward identity stable when updating source.

> [!IMPORTANT] Completed does not mean delivered
> A completed quest does not mean the role is already in place. Show delivery separately from completion in your app.

> [!DEV] Role delivery status
> The original delivery outcome is history. Its optional `reconciliation` reports current membership: `pending`, `granted`, `removed`, `preserved` or `failed`, with ownership `unclaimed`, `relay` or `external`. A revoked grant can still report `granted` when another accepted completion justifies the role. Operators can preserve an independently owned assignment and retry a failed reconciliation in [Console or the management API](/docs/build/integrations#recover-or-preserve-a-role). How Domino reconciles roles and orders point refunds is in [reward delivery internals](/docs/reference/authoring#reward-delivery-internals).

> [!NOTE] Two kinds of collection
> A reward collection pass is unrelated to a [collection](/docs/concepts/collections) of quests.

See [Discord role rewards](/docs/reference/authoring#discord-role-rewards) for authoring and the [participant API](/docs/reference/participant-api) for progress and delivery reads.

> [!DEV] Notify your backend
> Operator backends can subscribe to [webhook events](/docs/reference/webhooks) for completion, terminal external delivery failure, and confirmed entitlement collection. These notifications represent separate business changes and may arrive out of order.

## Offers [#offers]

An offer lets members spend points on a reward, like a shop shelf. You set the price, how many there are, and how many one member can buy. Members see whether an offer is available, paused, sold out, or already at their limit.

* **Each purchase grants one reward.** Rewards that need staff wait for pickup; automatic ones are collected straight away.
* **Pause without editing.** You can pause an offer at any time without changing it.
* **Retire an offer by archiving it.** In test, choose **Archive offer** on its card in **Rewards** or in **Settings → Resources**, then promote. Members stop seeing it and can't buy it. Its past purchases stay as they are and can still be canceled. Console labels it Archived, and **Unarchive offer** brings it back. Deleting an offer from your code doesn't archive it.
* **Sell only to some members.** An offer can require what quests can: a completed quest, a minimum balance, or membership of a group. Members who don't qualify still see the offer, along with what they're missing, and can't buy it until they meet every requirement. Console shows an offer's requirements on its card. The loyalty starter works this way: its members-only offer unlocks once a quest's group membership reward adds the member to its members' group.
* **Cancel with a refund.** Canceling a fulfilled purchase refunds the points the member paid, returns one unit to stock, and voids the reward. Canceled purchases stay visible in Console.

> [!DEV]
> Source declares the offer's stock and per-member limit. Redemption creates one durable record and one entitlement. From the CLI, `domino offers list` reads offers and `domino redemptions history [--member MEMBER] [--cursor CURSOR]` reads redemptions newest first, a page at a time, with their revisions, and `domino redemptions cancel REDEMPTION --expected-revision N --reason "…"` cancels one at the revision you read. Canceled redemptions remain visible in management reads too. An offer's `requires` takes `completed`, `balanceAtLeast` and `inGroup`; redemption refuses a member who misses one with 409 `prerequisites-incomplete` and `missing`, spending nothing, and member offer reads list what they lack in `missing`. `domino offers archive OFFER --reason "…"` and `unarchive` archive and restore one in test; management reads show `archivedAt`, and redeeming an archived offer fails with 409 `offer-paused`. In test, `domino test-members redeem MEMBER OFFER` (`management.testMembers.redeem`) redeems as a [test member](/docs/use/quests#try-quests-with-test-members), with the same price, stock, limit and refusals, so you can rehearse an offer without a member session. See [offers](/docs/reference/authoring#offers) for authoring.

### Keep an uncertain redemption pending [#keep-an-uncertain-redemption-pending]

An offer redemption spends points and consumes stock once for its action ID. If the answer is
lost, a new ID represents another purchase. Keep the original ID and payload until the result
is known. `relay.action()` and `useRedeemOffer()` retain them and offer a replay-safe retry;
the helper is kept in memory, so recovery across reloads needs a persisted key. See
[Retry after an uncertain answer](/docs/reference/participant-api#retry-after-an-uncertain-answer).

## Correct a tiered reward [#correct-a-tiered-reward]

A tier reward moves from `available` to `handing-over` when staff start a handover, and to `collected` when they confirm it. Your team can undo each step, void the reward, or restore a voided one, with a reason. Each correction acts on the version the operator saw, so a reward that changed in the meantime is refused until they reload it.

| Correction         | From           | To                    | Permission | Effect                                                                                                       |
| ------------------ | -------------- | --------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| Cancel handover    | `handing-over` | `available`           | `handover` | Releases the handover. A pickup size it took goes back into stock.                                           |
| Void reward        | `available`    | `void`                | `review`   | The member can no longer collect it. Settles any referral review on it.                                      |
| Restore reward     | `void`         | `available`           | `review`   | The member can collect it again at the tier they still earn. Brings back a referral review the void settled. |
| Correct collection | `collected`    | `available` or `void` | `review`   | Returns a pickup size to stock. A recovery flag with no remaining grant makes it void.                       |

**Needs recovery.** If you revoke a completion after the member already collected its reward, Console shows **Needs recovery** in Rewards and explains why in the reward and member details. The item still counts as collected until you resolve it. Get the item back, or fix a mistaken collection, then choose **Correct collection** with a reason. The correction either voids the reward or makes the tier the member still earned available, and records it in history.

> [!DEV] Recovery record
> Revoking a completion can leave a collected entitlement with a `recovery` record: the revoked completion, reason, flag time and `remainingTier`. The flag stays after a reload. If `remainingTier` is null, **Correct collection** voids the reward; otherwise it makes the remaining tier available. The correction clears the flag and records its resolution in history. A later legitimate grant can update the remaining earned tier but does not silently clear the recovery flag.

A voided reward stays in the member's rewards with status `void` and the time it was voided. If a quest grants the member the same reward again, it becomes `available` as a fresh grant at that quest's tier. A voided referral reward stays void until you restore it.

**Restore reward** undoes a void your team made. It can't undo a void that something else made, so the reward can't drift from the cause:

* **A revoked completion voided it.** Restore the completion instead.
* **A completion revoked after the void earned it.** Restoring gives back the tier another accepted completion still earns. If none does, restore the completion instead.
* **Canceling an offer redemption voided it.** The redemption's price went back to the member, so the reward stays void.
* **A referral reward voided before restore existed.** Its referral review wasn't kept, so it stays void.

In Console, open the member and find the reward under **Earned rewards**. A project with in-person pickup also lists every earned reward in the **Earned rewards** tab on **Rewards**. **Audit history** lists each correction under **Rewards** with its reason and who made it. These are corrections to member state, so they work in live too.

> [!DEV]
> A script uses `handover.cancel`, `entitlements.void`, `entitlements.restore` and `entitlements.correctCollection` in the [management client](/docs/reference/management#management-client), or `domino entitlements restore <id> --expected-revision <n> --reason <text>` from the CLI. An API key with the permission can make them in live. A restore that is refused answers 409 with code `entitlement-status`; a refusal for a revoked completion also names the `completion`.

## Inventory corrections and retirement [#inventory-corrections-and-retirement]

Your team can rename a stock item, correct how many are left, archive it and unarchive it. Use Console **Rewards** → **Pickup & stock** → **Inventory items**, including stock owned by offers, and choose **Rename item**, **Correct stock**, **Archive item** or **Unarchive item**. A project with no rewards handed over in person has no tabs, and lists **Inventory items** on **Rewards** itself.

* **Stock already promised stays.** A correction cannot remove units already reserved or redeemed. Pickup labels already assigned stay the same when you rename the item.
* **Archive only unused items.** You can't archive an item while an offer or pickup uses it, including a paused or archived offer, so an item behind an offer stays as it is; archive the offer instead. For pickups, move the pickup to another item first. Existing handovers and their history stay available.
* **Live takes stock corrections only.** Creating, renaming, archiving and restoring an item, and pickup setup, are definition changes, so they reach live by [promotion from test](/docs/concepts/environments).

> [!DEV]
> Archiving refuses with `inventory-in-use` while any offer or pickup variant references the item; the refusal detail names the blocking offers and pickups. Pickup configuration refuses an archived item with `inventory-archived`; unarchive it before using it again. Live refuses definition changes with `promotion-required`. Management scripts use `inventory.rename`, `inventory.adjustStock`, `inventory.archive` and `inventory.unarchive`, with the item's current `expectedRevision` and a reason. Read current fences with `inventory.items({ cursor, limit })`. Its `{ items, nextCursor }` page includes archived and offer-backed items; pass `nextCursor` back as `cursor` until null. The corresponding CLI commands are `domino stock-items rename`, `adjust-stock`, `archive` and `unarchive`.

## Participant reward reads [#participant-reward-reads]

Your app can show members what they earned, whether it's waiting for pickup, collected, or voided, and whether a Discord role has arrived.

> [!DEV]
> Members read their earned tiers with `relay.rewards.entitlements()` or `useEntitlements()`. The response includes collection state and pickup allocation when assigned. `useExperience().data.entitlements` includes the same entitlements with the quest snapshot. Available, handing-over, collected and voided states remain distinct; collection recovery stays visible until an organizer resolves it.
>
> `relay.rewards.deliveries()` and `useDeliveries()` combine point awards, current tier entitlements and integration delivery states in an `items` list. Point deliveries show the awarded and reversed amounts. Tier deliveries describe the current earned reward, not a separate fulfillment for each tier improvement. Integration deliveries use current reconciliation state when available, so a role removed after revocation does not appear granted because its original delivery succeeded. Unknown kinds and states remain readable. These member-only reads do not grant or fulfill rewards.

## Badges [#badges]

A badge marks an achievement, like "Early supporter" or "Attended the meetup". It has a name, a description and an image. A member holds each badge once, even when several quests award it. Badges have no built-in levels: if you want bronze, silver and gold, your app builds them from badge keys, points or ledgers.

| Action                               | Result                                                                                                            |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| Complete a quest with a badge reward | The member gets the badge with the quest's other rewards.                                                         |
| Revoke that completion               | The badge goes, unless another accepted completion or a manual award still earns it.                              |
| Manually award a badge               | A reviewer gives the badge, with a reason that's kept in the audit history.                                       |
| Manually revoke a badge              | A reviewer takes the badge away, with a reason. A later legitimate completion may award it again.                 |
| Archive a badge                      | It disappears from the catalog and new reward choices. Members keep it, and published quest rewards keep working. |

Manage the catalog in Console **Rewards**. **Archive badge** and **Unarchive badge** sit beside each badge and ask for a reason. In a Console quest's editor, choose **Add badge**. Reviewers award and revoke badges in the member drawer. Live badges come from a promotion from test; awards and revokes work in live. A member you [erased](/docs/use/member-erasure) keeps their badges, and neither works for them.

> [!DEV]
> Define badges with `defineBadge` and attach them to quest rewards with `badge`. Each catalog definition has a stable key. Definition changes require `publish` permission; manual awards and revokes require `review` permission. Live refuses definition changes from Console and the API with `promotion-required`. Read the active catalog with `relay.badges.catalog()`, which answers `{ items }`, and the signed-in member's awards with `relay.me.badges()`. `relay.profiles.badges(profileId)` applies the same project and member visibility rules as other profile reads. Member reads return `items`, each containing `badge` and `awardedAt`; the badge has its stored `id` and authoring `key`. Match keys for application rules and show names to members. Offers, referral programs and leaderboard prizes cannot carry a badge: `defineOffer`, `defineReferral` and `defineLeaderboard` throw `Badge rewards can only be granted by quests or manually`, and a deployment refuses one with 422 `validation-failed` and the same message. See [authoring](/docs/reference/authoring#badges) and [participant reads](/docs/reference/participant-api#badges).

## Group membership [#group-membership]

A group membership reward adds the member to a [group](/docs/concepts/groups) when they complete the quest. It's the way to run an approved program: members apply through a quest your team reviews, approval puts them in the "Approved creators" group, and the program's quests are open only to that group. Approving the application is the one step that lets someone in.

```ts
import { defineGroup, groupMembership } from "@domino-sdk/relay/authoring";

export const creators = defineGroup({ key: "approved-creators", title: "Approved creators" });

// In the application quest's definition:
const rewards = [groupMembership(creators)];
```

| Action                                  | Result                                                                                                                                                                                                                                              |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Publish a quest with the reward         | Creates the group, with no members and the title your code gives it, if the environment doesn't have it: deploying creates it in test, approving the promotion in live. An archived group is refused, with 422 `validation-failed` in a deployment. |
| Deliver the completion's rewards        | Add the member, recording the quest and completion as the reason in **Audit history**, and check the prerequisite-only automatic quests they can now enter. Someone already in the group stays as they were.                                        |
| Revoke that completion                  | Remove the member unless an operator's add or another unrevoked completion also keeps them in the group. An archived group keeps them.                                                                                                              |
| Remove the member in Console **Groups** | Removes them whatever added them, as for any member.                                                                                                                                                                                                |
| Archive the group                       | Refused with 409 `group-in-use` while a quest that is not archived rewards it.                                                                                                                                                                      |

> [!DEV]
> The program's quests require `inGroup` of the group. Participant quest reads show the reward as a `tier` reward whose `label` is the group's title and whose `reward.group` is `{ key, title }`, with the title the code's handle gives. It never creates a tiered-reward entitlement. Offers, referral programs and leaderboard prizes cannot carry it: `defineOffer`, `defineReferral` and `defineLeaderboard` throw `Group membership rewards can only be granted by quests`, and a deployment refuses one with 422 `validation-failed` and the same message. See [authoring](/docs/reference/authoring#group-membership-rewards).
