# Quests (/docs/concepts/quests)



A quest is one thing you ask your community to do. Domino checks that they did it, counts it once, and hands out what they earned. For example:

- "Answer three questions about our launch"
- "Post about us on X"
- "Upload a selfie at our booth"
- "Invite a friend"

> [!TIP] Editing a quest is safe mid-campaign
> Members who already sent something keep the rules they saw. Your changes apply to what comes next.

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

Each quest can show instructions, a list of steps with links, an image, a video, and quiz questions. It can also hint which account or wallet to link. Opening a link in a step doesn't complete the quest on its own: Domino still checks the activity.

Your developer sets the defaults in code. In Console you can replace the steps, the image and the account hints together, and reset them to the defaults later with **Reset to type** or **Reset to code**.

> [!DEV]
> Steps are ordered, their links use HTTPS, and an image has alt text. A presentation edit in Console creates a new release; accepted attempts keep their original rules. See [Add participant presentation](/docs/build/first-quest#add-participant-presentation) for the authoring shape.

## How Domino checks a submission [#how-domino-checks-a-submission]

When a member sends their answer, link, or photo, Domino checks it, and can hand unclear ones to your team for a decision.

```mermaid
flowchart LR
    Checking --> Review["Your team reviews (if needed)"]
    Checking --> Accepted["Accepted: it counts"]
    Checking --> Rejected["Not accepted"]
    Review --> Accepted
    Review --> Rejected
```

If a check fails for a technical reason, that's not counted against the member, and the submission can still go through.

Domino keeps a member's answer while they fill it in, so a lost connection or a second tab doesn't send it twice. If the quest changed since they opened it, Domino asks them to look again before sending, and keeps what they wrote. An unsent answer expires 7 days after its last change. You can discard one from the member's page in Console.

A member can have one unfinished try per quest at a time. Until it's decided, including while it waits for your team, they can't send another.

> [!DEV]
> Each member has at most one open submission per quest: their text, URL, quiz answers or photo, and the release they saw. Submitting it creates one attempt, and submitting again returns the same attempt. A submission against an outdated release is refused and the draft kept.
>
> While a member's own attempt is undecided, another submission or a retry of a failed attempt is refused with `attempt-pending`. Staff confirmations and attempts Domino starts itself, from trackers or automatic quests, are not limited this way.

### When your team reviews [#when-your-team-reviews]

A quest can send unclear submissions to your team. If human review is off for the quest, an evaluator's "send to review" becomes a rejection instead, and Console marks it "Would have gone to review; human review is off". Turn review on if unsure results should reach a person.

When Domino rejects a submission, the member sees the reason written for them. Your developer can also tag each kind of rejection with a code, so you can tell them apart in Console without the member ever seeing it.

> [!DEV]
> Turn review off with `review: () => ({ enabled: false })`. An evaluator that returns `decision.review({ reason })` on such a quest is rejected with that reason and marked `requestedReview: true`; a plain `decision.reject({ reason })` leaves it `false`.
>
> Give each cause a code with `decision.reject({ reason, code: "wrong-answers" })`. Domino stores it as the attempt's `code`. Operators see it beside the reason in Console's attempt detail, the management API's attempt reads, and `domino attempts list|get`. Members never receive it. Keep a code when you reword its reason. See [reject with a code](/docs/reference/quest-rules#reject-with-a-code) for the format and the codes Domino reserves.

## Stop people guessing [#stop-people-guessing]

Add a **retry cooldown** to make members wait before trying again after a wrong answer, so nobody solves a quiz by trying every option. New quiz quests in Console start with a 5-minute cooldown.

> [!DEV]
> Set `retryCooldownMs` in code, from 1 second to 7 days. While it runs, a new attempt is refused with `retry-cooldown` and a `retryAt` your app can show as a countdown. See [Set a retry cooldown](/docs/reference/quest-rules#set-a-retry-cooldown).

### Retry cooldown [#retry-cooldown]

Without a cooldown, a member can try again as soon as an attempt is rejected. Set one in Console under **Retry cooldown** for quests made there; other quests, and quizzes defined in code, start with none. Only quests members submit themselves can have one.

| Starts a cooldown                                                                                 | Does not                                                                                                                 |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Domino's check rejects the attempt, including a "send to review" on a quest with human review off | An acceptance, which ends any cooldown                                                                                   |
| A reviewer rejects the attempt, also while Domino is waiting to check it again                    | A rejection Domino makes itself: an exhausted budget, a revoked completion, or a check that waited too long or too often |
|                                                                                                   | A technical failure; the attempt can still be retried                                                                    |

The cooldown applies only to the member whose attempt was rejected. It always uses the quest's current length, so shortening or removing it helps members who are already waiting.

> [!DEV]
> Until the cooldown ends, a new attempt, whether the member's own submission or a test member's action in Console, is refused with `409` and code `retry-cooldown`, and `retryAt` says when the member can submit again. Quest progress and the quest experience carry `retryAt` while it runs; the quest's `availability` stays `available`. It runs on the member's clock: advancing a test member's clock past it ends it.

### Submission limit [#submission-limit]

Every member can submit 120 attempts an hour in a project, across all its quests, counted separately in test and live. It is a ceiling for scripts, far above what a member needs to finish every quest in a starter. Past it, their submissions are refused until the hour moves on, and the answer they were submitting stays saved. Test members you act as in Console are not counted; a test member signed in to your staged app through the staging toolbar counts like any member.

> [!DEV]
> Past the limit, a member's submit, a submit of their saved submission, and a retry of a failed attempt answer `429` with code `rate-limited` and a `Retry-After` header in seconds. Every request counts, a repeat with the same `actionId` included, and the limit is checked before the request is read. Submitting again through the SDK after a rejection sends two of them: it replays the rejected submission to read its result, then submits the new draft. The SDK keeps the saved draft, so `resume()` continues it later.

## How often someone can complete it [#how-often-someone-can-complete-it]

| Choose                             | When you want                                                                                                                                                |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Once**                           | Each member to finish it one time, like a sign-up bonus.                                                                                                     |
| **Per occurrence**                 | Credit for each separate thing, like every order or every Discord message.                                                                                   |
| **Per occurrence, across members** | Credit once for each thing, whoever claims it first, like a single-use code or a post that pays once. See [keys shared by members](#keys-shared-by-members). |
| **Streak**                         | A milestone after a number of days in a row. See [calendar-day streaks](#calendar-day-streaks).                                                              |
| **Counter**                        | A reward after several separate actions, like three posts in a week. See [distinct-action counters](#distinct-action-counters).                              |
| **Budgets**                        | A cap on completions or points, per member, per giver, or for everyone, per day, week, or month.                                                             |

Your team can also take back a completion made by mistake, accept an attempt that was rejected by mistake, or delete the evidence a member submitted. See [revoke a completion](#revoke-a-completion), [accept a rejected attempt anyway](#accept-a-rejected-attempt-anyway) and [delete an attempt's evidence](#delete-an-attempts-evidence). Domino deletes evidence on its own 90 days after the decision unless you choose otherwise; see [how long evidence is kept](#how-long-evidence-is-kept).

### Completion cooldown [#completion-cooldown]

A quest members can complete more than once can make them wait between completions, for example so a second story counts only 90 minutes after the first was accepted. The wait starts each time a completion is accepted. A rejection doesn't start it, and a completion your team revokes doesn't count. If a retry cooldown is also running, the member waits for whichever ends later.

The cooldown applies only to that member, and always uses the quest's current length. Set it in Console under **Completion cooldown** for quests made there. Only quests members submit themselves can have one.

> [!DEV]
> Set `completionCooldownMs` in code, from 1 second to 7 days; publishing one on a quest that completes once is refused. Until it ends, a new attempt, whether the member's own submission or a test member's action in Console, is refused with `409` and code `completion-cooldown`, and `retryAt` says when the member can submit again. When both cooldowns run, the refusal names the later one. It runs on the member's clock: advancing a test member's clock past it ends it. See [Set a completion cooldown](/docs/reference/quest-rules#set-a-completion-cooldown).

### When a quest opens again [#when-a-quest-opens-again]

Your app can show members a countdown to when they can try again: after a cooldown, when a used-up budget resets, or when a once-a-day or once-a-week counter opens its next window. There is no countdown when the member can try now, or when nothing will reopen the quest, such as a lifetime budget that's used up or a quest whose dates end first.

> [!DEV]
> Quest progress and the quest experience carry `nextAvailableAt` (Unix milliseconds), the latest of a retry cooldown, a completion cooldown, an exhausted budget's next reset, and a counter's next window. It is absent when the member can submit now and when nothing will reopen the quest.

### Keys shared by members [#keys-shared-by-members]

A per-occurrence quest normally lets every member complete each occurrence once. A quest can instead give each occurrence to the first member who completes it: a code your backend issued that only one person may redeem, or a post that pays only one claimant. When someone else already holds the occurrence, a member's attempt is rejected and says another member already completed the quest with it. Two members who try at the same moment can't both be paid.

Console's attempt detail shows that rejection, and the quest's **Completion** reads **Once per key, across members**. To give the occurrence to someone else, [revoke](#revoke-a-completion) the holder's completion and free its occurrence, then accept the other member's attempt anyway. [Member imports](/docs/use/imports) follow the same rule: a row is refused when another member, or an earlier row, already used one of its occurrences.

> [!DEV]
> Use `keyed(fn, { across: "members" })`, or `({ keyed }) => keyed(fn, { across: "members" })` in the builder form. The release's completion is `{ kind: "keyed", across: "members" }`, and quest progress and the quest experience carry it. An evaluator's `completionKey` takes precedence over `fn` and is checked the same way. The losing attempt is `rejected` with `code: "key-used"` and `codeSource: "relay"`, which members see. Relay claims the key with the completion, its budgets and its rewards in one transaction. An import refuses such a row with `key-used`. Only completions recorded while the quest shares its keys hold them: switching an existing per-member quest doesn't count its earlier completions, so start a new quest and import them onto it.

## Who can take part [#who-can-take-part]

Prerequisites decide who can try a quest. A member can need to have completed another quest, hold a minimum number of points, or belong to a [group](/docs/concepts/groups), such as your VIPs.

* **Completed another quest.** For example "Welcome" before "Daily check-in", or a check-in today before today's bonus.
* **Holds enough points.** This is checked against the member's current balance, so spending points can make them miss it again.
* **Belongs to a group.** Your team adds members in Console **Groups**, or a quest's reward adds them. The same group can also limit leaderboards and referral rules.

> [!IMPORTANT] Completion is not a count
> "Completed another quest" means at least one completion. It is not a count of completions or a points threshold. Use a points prerequisite for a live point threshold.

A quest needs every prerequisite it lists. For a rule they can't express, such as "any two of these five quests", your developer can check it in the quest's own code.

> [!DEV]
> Check such a rule in `evaluate` with `ctx.member.balance(ledger)` and `ctx.member.completions()`. `ctx.member.groups()`, `ctx.member.enrolled(campaign)` and `ctx.member.referrals(program)` read the member's groups, campaign enrollment and referral progress the same way. See [the member's state in evaluators](/docs/reference/verification#evaluator-readers-null-errors-and-pinning).

Prerequisites control who can take part. [Collections](/docs/concepts/collections) only control the order quests are shown in: listing a quest second doesn't require the first one.

## Quest types and settings [#quest-types-and-settings]

A quest type is a reusable kind of quest, such as a quiz or a photo upload, with settings you fill in. In Console you can create quests from the types your app supports. Each quest has its own progress, even when it shares a type with another quest.

* **Your app supports the interaction.** The app must handle the quest's interaction, such as a photo, quiz, or claim.
* **Console can customize a code quest.** You can change the settings a code-authored quest exposes. Your values win over the code's defaults until you reset them; the rules themselves stay in code.
* **Secret settings stay in Domino.** A code quest can have a secret setting, such as a shared scan code. You set it in Console, and the quest reads it, but no deploy output, quest listing or Console screen shows it again ([change a setting](/docs/use/quests#secret-settings)).
* **Keys keep a quest's identity.** Changing a title or renaming a source file keeps the same quest and its history, as long as its key stays the same.
* **Duplicates are separate.** Duplicating a quest starts a new completion history.

## Pause and archive [#pause-and-archive]

A quest is active, paused, or archived. Console shows its state as **Active**, **Paused** or **Archived** under **Participation**. Where you pause it depends on where the quest comes from:

* **A quest made in Console.** Choose **Pause** or **Resume** under **Participation**, then review and publish.
* **A quest synced from code.** Choose **Pause quest** under **Quest availability** in its **Activity & details**, with a reason. This pause applies to the selected environment only, and works in live too. **Participation** doesn't offer a pause for these quests; it shows **Paused** only while a resource setting is empty.

| State    | New tries | What else                                                                                                  |
| -------- | --------- | ---------------------------------------------------------------------------------------------------------- |
| Active   | Allowed   |                                                                                                            |
| Paused   | None      | Use it for a short stop. A code-authored quest is also paused while one of its resource settings is empty. |
| Archived | None      | Hidden from collections, and your app no longer needs to support it.                                       |

* **History stays.** Completions, attempts, points, and rewards remain. Attempts already under way keep their rules. Content qualification and engagement settlement for an archived quest wait until it is unarchived.
* **Members keep what they earned.** Members who completed an archived quest still meet requirements on it.
* **Deleting the code doesn't archive it.** Deploying its code again keeps an archived quest archived. Archive and unarchive in Console or with `domino quests`.
* **Live waits for promotion.** Archive state is part of a quest's definition, so you can't archive in live directly; pause a live quest instead. See [Archive a quest](/docs/build/publishing#archive-a-quest).

> [!DEV]
> Lifecycle commands use the same verbs across resources: `archive`, `unarchive`, `pause` and `resume`. For example, `management.quests.pause(id, { revision, reason })` pauses participation and `management.trackers.archive(key, { reason })` retires a tracker; the CLI commands are `domino quests pause` and `domino trackers archive`. Archive preserves history; pause preserves archive state.
>
> Archiving publishes the quest's current rules again with the new state, so its release history shows the change. Read release history with `domino releases list [--quest KEY]` and one release with `domino releases get RELEASE`, or `management.releases.list` and `get`; both need `read` and `publish`.

## Revoke a completion [#revoke-a-completion]

Someone on your team with `review` permission can take back a completion that was accepted by mistake or through fraud, with a reason. The completion then stops counting: it disappears from progress, counts and profiles, no longer unlocks other quests, and no longer qualifies a referral. The attempt stays accepted, and its history shows the revoke and the reason.

Everything the revoke changes happens at once, or not at all:

| What the completion granted | After the revoke                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Points                      | Each award is reversed in its original period, up to the member's total minus held points. The result reports any remainder as `unreversed` for each point ledger. Relay records no member debt for it, so points already spent or reserved stay with the member and in `earned` leaderboard scores.                                                                                                                                                                      |
| Tiered reward               | Voided, after cancelling a handover in progress. If another completion grants the same reward, it is lowered to the best tier that one grants instead. A reward already collected stays collected with a persistent recovery flag, visible after reload. After recovering the item or correcting a mistaken collection, [correct collection](/docs/concepts/rewards#correct-a-tiered-reward) reopens only a tier still earned; without another grant it voids the reward. |
| Discord role                | Removes the completion’s justification and schedules membership reconciliation. The role stays while another accepted completion across any project/environment grants it. Relay removes only its own assignments; preexisting or explicitly preserved assignments stay. The revoke response retains original grant history; current state is in the delivery’s `reconciliation`.                                                                                         |
| Announcement                | A message not yet posted is cancelled. A message already posted stays in the chat or channel.                                                                                                                                                                                                                                                                                                                                                                             |
| Group membership            | The member leaves the group unless an operator added them too or another unrevoked completion's reward did; an archived group keeps them. The result's `groups` reports `stillMember` for each group. See [group membership](/docs/concepts/rewards#group-membership).                                                                                                                                                                                                    |
| Referral qualification      | Moves to another accepted completion of the program's quest before points are reversed, preserving that completion's percentage bonus contributions. Otherwise the member is unqualified and the inviter's reward is reversed.                                                                                                                                                                                                                                            |
| Streak milestone            | Revoked. The recorded days stay.                                                                                                                                                                                                                                                                                                                                                                                                                                          |

You choose whether the member can complete the quest again for the same occurrence (its completion key):

* **Kept occurrence (default).** The revoked completion still holds it, and the budget it used stays spent. Later attempts for it are rejected. A manual streak still records later days and pays nothing; a tracker-triggered `once()` or streak quest skips its observations, so it records no days at all.
* **Freed occurrence.** A new attempt can complete it and earn its rewards again. The quest's budgets get back what the completion used, so the occurrence can be completed again within its caps. An automatic quest without a tracker or content trigger queues a fresh attempt immediately if its prerequisites still hold. A tracker-triggered quest needs a new matching observation; replaying the original observation does not pay again. A manual keyed quest needs a new action ID and can reuse its freed completion key. A streak pays its milestone again on the next qualifying day, because its days stay.

On a quest whose keys are [shared by members](#keys-shared-by-members), the choice applies to every member: a kept occurrence keeps the key from everyone, and a freed one lets any member complete the quest with it.

> [!WARNING] Freeing an occurrence can pay twice
> A freed occurrence pays its points and rewards again when it is completed again, and a freed streak pays its milestone again on the next qualifying day. Points this revoke could not reverse, because the member had spent them, stay with the member as well. Keep the occurrence for fraud.

Completions it already unlocked stay. Freeing the occurrence returns to each budget what the completion was charged, in the budget period it was charged to; a budget priced in points gets back the charge less the points the revoke couldn't reverse, because those stay paid out. Completions recorded before Domino kept their charges give no budget back. In Console, revoke from the member's **Completions** or from the accepted attempt.

Console keeps an unreversed point remainder on the revoked completion in the member and attempt drawers. A person with `points` permission can open the member's existing point adjustment from the warning and record a separate correction with its own reason. That adjustment is an operator decision; revocation itself never creates debt or deducts the remainder later.

> [!DEV]
> Code uses `management.completions.revoke(completion, { reason, freeOccurrence })`, or the older `revoke({ completion, reason, freeOccurrence })`; see the [management client](/docs/reference/management#management-client). Revoking corrects member state, so an API key with `review` permission can revoke in live. A revoke that frees the occurrence reports `budgets`: each budget ID with the amount `released`.
>
> Revocation requires the completion's recorded reward history. Relay records it with every completion; only an older completion whose original record could not be read lacks it. Then the command returns `409 completion-grant-history-missing` before changing member state. If the completion granted a shared tiered reward, every other accepted completion of the member must also have reward history; otherwise Relay cannot prove which tier remains earned. Contact Domino support to restore the reported completion's history before retrying.

To revoke every completion of a member who farms, [ban the member](/docs/use/member-bans) and take back their completions: each one is revoked this way, keeping its occurrence.

### Restore a revoked completion [#restore-a-revoked-completion]

A revoke made by mistake can be undone. Someone with `review` permission restores the completion with a reason, and the completion counts again. Restoring re-applies what the revoke recorded, not the quest's current rewards, so it never gives back more than the revoke took, even if the quest's rewards changed since.

Everything the restore changes happens at once, or not at all:

| What the revoke did               | After the restore                                                                                                                                                                                   |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reversed points                   | Awarded again, as new entries for the amount the revoke reversed. Points it could not reverse stayed with the member and aren't awarded again.                                                      |
| Voided or lowered a tiered reward | A voided reward is available again, and a lowered or flagged one earns the completion's tier again. A reward someone changed since the revoke (restored, voided again or corrected) stays as it is. |
| Revoked a badge                   | Awarded again, even if staff revoked the badge since. A group membership staff ended since stays ended.                                                                                             |
| Removed a group membership        | The member joins the group again. A membership the revoke kept is left as it is now, and an archived group's membership doesn't change.                                                             |
| Stopped a Discord role grant      | The grant counts for membership reconciliation again; one that never reached Discord is queued again.                                                                                               |
| Cancelled an announcement         | It stays cancelled: Domino doesn't post a late announcement.                                                                                                                                        |
| Re-evaluated a referral           | The member qualifies on this completion again.                                                                                                                                                      |
| Revoked a streak milestone        | Awarded again.                                                                                                                                                                                      |

Budgets stay as the revoke left them when it kept the occurrence. When it freed the occurrence, the restore charges each budget again what the revoke returned, against the budget's current limit (or the limit it was first charged under, if the quest removed the budget or changed its period or cost); if a budget has no room left, because other completions used it since, the restore is refused and nothing changes. The completion holds its occurrence again. If the revoke freed the occurrence and the member has completed it again since, the restore is refused, because two completions can't hold one occurrence.

The revoke stays in history: the attempt's history shows the revoke and the restore, each with its reason, and Audit history lists both. In Console, restore from a revoked completion in the member's **Completions** or on the accepted attempt.

> [!DEV]
> Code uses `management.completions.restore(completion, { reason })` (or `restore({ completion, reason })`), or `domino completions restore <completion> --reason <text>`, with `review` permission, in live too. The result lists what it re-applied: `points` (each new `entry`, the `reversal` it replaces and the `amount`), `badges`, `groups` (`added`), `entitlements` (`restored`, `raised` or `kept`), `deliveries` (`requeued` or `kept`), `referrals` (`qualified` or `kept`), `streakMilestone` and, when the revoke returned budget, `budgets` (each budget ID with the amount `charged`). A completion that isn't revoked returns `409 completion-not-revoked`; a freed occurrence the member completed again returns `409 occurrence-taken`, with the `completion` that holds it; a budget without room returns `409 budget-exhausted`, with the `budget`. `members.get(id).revokedCompletions` lists the member's 25 most recently revoked completions, each with its revoke's `reason` and recorded result (`revocation`). Restoring sends no webhook, as revoking doesn't.

## Accept a rejected attempt anyway [#accept-a-rejected-attempt-anyway]

When a reviewer or Domino's check rejects an attempt by mistake, someone on your team with `review` permission can accept it anyway, with a reason. It works for any member, in test and in live.

1. In Console, open the rejected attempt from **Quest activity**.
2. Choose **Accept anyway**, write why the rejection was a mistake, and confirm.

The attempt is accepted as if a reviewer had approved it: the member gets the completion and its rewards, budgets are spent, referrals can qualify, and the member's activity and your webhooks see a new acceptance. The rejection is not erased. The attempt's history shows the rejection and the reversal, each with its reason, and the rejection's retry cooldown ends.

Domino refuses, and leaves the attempt rejected, when:

| Refusal                                                                  | Why                                                                                                                                                                                                               |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The member has tried the quest again since                               | Only the member's latest attempt on a quest can be accepted anyway. Console offers **Accept anyway** only there.                                                                                                  |
| The quest is archived                                                    | Unarchive it first.                                                                                                                                                                                               |
| The member's completion cooldown is still running                        | The acceptance would complete the quest inside it.                                                                                                                                                                |
| A budget the completion would spend is used up                           | The acceptance would exceed it.                                                                                                                                                                                   |
| The member already has this completion, or your team revoked it          | A once-only quest the member already completed, or a completion revoked without freeing its occurrence, can't be completed again. To give back a revoked completion, [restore it](#restore-a-revoked-completion). |
| Another member holds the attempt's [shared key](#keys-shared-by-members) | The quest pays each key once across members. Revoke the other member's completion and free its occurrence first.                                                                                                  |

A counter or streak quest counts the accepted attempt as one action, as an approval does, and its own rules decide whether that action completes it: the refusals above apply only when the acceptance would create a completion. A quest that completes per piece of content can't be accepted this way. To take back an acceptance, [revoke the completion](#revoke-a-completion).

> [!DEV]
> Code uses `management.attempts.acceptAnyway(attempt, { reason })` (or `acceptAnyway({ attempt, reason })`), and the CLI `domino attempts accept-anyway ATTEMPT --reason TEXT`; both send `POST /attempts/<id>/accept-anyway` and answer the accepted attempt. Replaying the action ID returns the first answer. The attempt's events keep the `rejected` event and add a `staff-decision` with `decision: "accept-anyway"` and the reason. The acceptance publishes `completion.created`, which members' activity shows as `attempt.accepted`.
>
> Refusals are `409`: `attempt-superseded` (the error names the newer attempt; `management.attempts.get(id)` answers it as `supersededBy`), `quest-archived`, `completion-cooldown` with `retryAt`, `budget-exhausted`, `already-completed`, `completion-revoked` and `key-used` (another member holds the attempt's [shared key](#keys-shared-by-members)). An attempt in any other status than `rejected` is refused with `attempt-not-rejected`. A keyed quest's acceptance uses the completion key the check proposed with its review, otherwise a fresh one, so it can't tell that a rejected duplicate of an occurrence the member already completed is the same occurrence: check the evidence before accepting such a rejection anyway. Test members also keep `testMembers.acceptAnyway`, which doesn't check these limits unless you pass `rules: "live"`; see [test members](/docs/use/quests#try-quests-with-test-members).

## Delete an attempt's evidence [#delete-an-attempts-evidence]

When a member asks you to delete what they submitted, such as a selfie or an email address they typed in, someone on your team with `review` permission can delete it, with a reason. The attempt and what it earned stay. It works for any member, in test and in live.

1. In Console, open the attempt from **Quest activity**.
2. Choose **Delete evidence**, write why, and confirm.

| Deleted                                                            | Kept                                                               |
| ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| The photo, which can't be downloaded again                         | The attempt, its outcome and the reason it was given               |
| The text, link or quiz answers the member submitted                | The completion, its points and rewards                             |
| What the quest's checks read from them, such as an AI photo review | The attempt's history, with who deleted the evidence, when and why |

The attempt then shows who deleted its evidence and why, and Audit history lists the deletion. Other attempts that submitted the same photo lose it too, and are listed in the result. Duplicate-photo checks keep working, because Domino keeps a fingerprint of each photo it has seen, not the photo.

You can delete evidence once the attempt is decided: accepted, rejected, partly counted toward a counter, or failed. An attempt still waiting for its check or for review keeps its evidence until someone decides it. An attempt without evidence can't be checked again, so retrying it is refused. A rejected one can still be [accepted anyway](#accept-a-rejected-attempt-anyway). Deleting evidence can't be undone.

> [!DEV]
> Code uses `management.attempts.redactEvidence({ attempt, reason })`, and the CLI `domino attempts redact-evidence ATTEMPT --reason TEXT`; both send `POST /attempts/<id>/redact-evidence` and answer `{ attempt, attempts, redaction }`, where `attempts` lists the attempts redacted and `redaction` is `{ at, actor, reason }`. Replaying the action ID returns the first answer; redacting a redacted attempt again answers its first `redaction` with `attempts: []`. `management.attempts.get(id)` answers `redaction`, and the attempt's `evidence` is `null` with no `input`. Downloading the deleted photo returns `404`. Each redacted attempt records an `evidence-redacted` event, in the `reviews` audit category.
>
> Refusals are `409`: `attempt-not-decided` while the attempt, or another attempt with the same photo, waits for its check or review, and `evidence-redacted` from `attempts.retry` afterwards. The photo leaves storage right after the deletion; if storage is unavailable, the hourly cleanup retries it.

### How long evidence is kept [#how-long-evidence-is-kept]

Domino deletes the evidence of decided attempts 90 days after the decision, in test and in live, exactly as deleting one attempt's evidence does. You choose a different period for your project, from 1 day to 10 years, or keep evidence until someone deletes it:

1. In Console, open **Settings** → **General**.
2. Under **Evidence retention**, choose how many days after review evidence is deleted, or keep it, and save.

The period counts from the decision, so an attempt still waiting for its check or for review keeps its evidence until it is decided and the period has passed. Failed attempts, which have no decision time, keep theirs. A photo also submitted by an attempt that isn't due yet stays until that attempt is due too. Domino deletes due evidence every hour, a batch per project at a time, and the deletions appear in Audit history.

Nothing is deleted in a project's first 90 days. For projects that existed when Domino started deleting evidence, on 20 October 2026, those 90 days count from then, so their first deletions are on 18 January 2027; the project's settings show the date while it is ahead.

Anyone who can publish can make the period longer or keep evidence. Making it shorter, or switching from keep to a number of days, deletes evidence sooner, possibly a whole project's at once, so it takes an organization admin or a [service key](/docs/start/existing-app#5-call-domino-from-your-backend) holding `publish`. The project's access history records each change with the old and new period.

> [!DEV]
> Code reads the period with `management.attempts.evidenceRetention()` and changes it with `management.attempts.setEvidenceRetention({ evidenceRetention })`, where `evidenceRetention` is `{ days }` (a whole number from 1 to 3650) or `"keep"`. Both methods are experimental. The CLI has `domino attempts evidence-retention` and `domino attempts set-evidence-retention --days N` or `--keep`. Both send `/management/v1/evidence-retention` (`GET` needs `read`, `POST` needs `publish`) and answer `{ evidenceRetention, noticeEndsAt }`: no evidence is deleted automatically before `noticeEndsAt`, in Unix milliseconds. A period outside 1 to 3650 days is refused with `400` `invalid-request`. Shortening it without an organization admin's Console session or personal key, or a service key, is refused with `403` `permission-required`, from test as from live, since one period covers both. A change records an `evidence-retention-changed` event in the project's access history (`before`, `after` and the environment the change was made from). Each hourly batch of up to 100 attempts records one `evidence-redacted-before` event in the `reviews` audit category with no actor and no quest, and each attempt's `redaction` has a `null` actor and the reason `retention: <days> days`.

### Delete old evidence on a schedule [#delete-old-evidence-on-a-schedule]

When you promise members to keep what they submit for less time than your project's period, or for a different time per quest, such as selfies for 7 days after an event, a job deletes it for you: each pass deletes the evidence of one quest's attempts decided before a time you choose, exactly as deleting one attempt's evidence does. For different periods per kind of evidence, run one pass per quest. See [delete old evidence nightly](/docs/build/schedules#delete-old-evidence-nightly) for a scheduled job that does it.

In test, anyone with `review` permission can run a pass. In live, a pass deletes a whole quest's evidence at once and can't be undone, so it takes an organization admin or a [service key](/docs/start/existing-app#5-call-domino-from-your-backend) with `review`, which only an admin can create. Reviewers who aren't admins can still delete one attempt's evidence in live.

A pass covers attempts that were accepted, rejected or partly counted toward a counter before the time you give. It skips attempts still waiting for their check or review, and failed attempts, which have no decision time. A photo also submitted by an attempt the pass doesn't cover, such as one waiting for review, one decided later, or one on another quest, stays with all its attempts. The time can't be in the future.

> [!DEV]
> Code uses `management.attempts.redactEvidenceBefore({ quest, decidedBefore, reason })`, where `quest` is a key or ID and `decidedBefore` is in Unix milliseconds, and the CLI `domino attempts redact-evidence-before --quest QUEST --decided-before MS --reason TEXT`, which repeats until nothing is left and prints the total. The method sends `POST /attempts/redact-evidence` (`review` permission; in live, an organization admin's own Console session or personal key, or a service key, otherwise `403` `permission-required`) and answers `{ quest, decidedBefore, attempts, more, redaction }`: each call deletes up to 100 attempts' evidence, oldest decision first, and `more` stays `true` while others remain, so repeat until it is `false`. `attempts` also lists the quest's other attempts that submitted the same photo. Replaying the action ID returns the first answer. Each call records one `evidence-redacted-before` event in the `reviews` audit category, naming the quest, `decidedBefore`, the number of attempts (`redacted`), their IDs and the reason. A `decidedBefore` in the future is refused with `400` `invalid-request`; an unknown quest with `404`.

## Calendar-day streaks [#calendar-day-streaks]

A streak quest rewards days of activity in a row, such as posting on X five days running or checking in every day for a week. Domino records the day of each accepted try and pays the milestone when the run reaches its goal.

Each day comes from a time Domino trusts, the same time budgets use:

| How the quest starts                    | Day counted                                                       |
| --------------------------------------- | ----------------------------------------------------------------- |
| A member submits, or your team confirms | The day Domino received the submission or confirmation.           |
| From a tracker                          | When the activity happened, such as when an X post was published. |

* **A submitted streak counts submission days.** A member who submits on seven days in a row completes a seven-day streak, whatever day the evidence shows. A submission that waits for review still counts the day it was submitted. To count the days the activity happened, start the quest from a tracker.
* **Days follow the project calendar.** Every streak in a project and environment counts days of the [project calendar](/docs/concepts/calendar), not each member's device timezone. Days end at midnight in the project timezone.
* **One action per day counts.** Several actions on one day fill a single day. Today stays open, so a run survives until a whole day passes without activity.
* **Each quest has one milestone.** It pays once per member. A broken run can restart, but it does not pay the milestone again. Use separate quests for 7-day and 30-day milestones. Changing the goal, or switching between submitted and tracker activity, also needs a new quest.
* **Late evidence counts at its activity time.** A delayed tracker record can repair a missing day. There are no implicit grace days.
* **Progress is not completion.** An accepted action below the goal records progress without a completion.

A timezone change takes effect at the next midnight, keeps a running streak going, and never rewrites recorded days. See [when the timezone changes](/docs/concepts/calendar#when-the-timezone-changes), and [streak progress](/docs/use/progress#consecutive-day-streaks) for what members see.

> [!DEV]
> `streak({ days: 7 })` replaces the completion rule; the evaluator accepts or rejects each attempt. A manual quest counts the day Domino admitted the submission or staff confirmation; a tracker quest counts the record's `occurredAt`. A [content quest](#content-backed-quests) completes once per post, and an `automatic()` quest with only prerequisites has no activity to count, so neither can be a streak; publication rejects them.

## Distinct-action counters [#distinct-action-counters]

A counter completes a quest after several separate actions, such as three different posts in a week. Each action is checked on its own, and Domino counts the ones it accepts. Each action counts once, and an accepted action below the goal shows as progress, with no completion or reward yet.

| Window                                 | What counts                                                                   | Default achievement limit  |
| -------------------------------------- | ----------------------------------------------------------------------------- | -------------------------- |
| Omitted                                | Distinct actions over the member's lifetime                                   | One lifetime achievement   |
| `calendar("day" \| "week" \| "month")` | Actions whose trusted occurrence time belongs to that project-calendar bucket | One achievement per bucket |
| `rolling({ days: 7 })`                 | Actions in the trailing elapsed duration when qualification settles           | One lifetime achievement   |

Quests members submit and tracker-triggered quests support counters; content quests and prerequisite-only automatic quests do not. Your team can exclude or restore an action that hasn't been settled yet, with a reason. A counter's goal, window and repeat rules can't change after publication: publish a new quest for different rules.

> [!DEV]
> Use `progress({ distinct, goal: count(n) })`. Relay records each accepted action's distinct key and settles the goal together with completion and rewards. `distinct` reads the same trusted context as `keyed`, including the pinned tracker observation and project calendar, and cannot mutate the counter. An accepted action below the goal leaves its attempt `progressed`.
>
> Set `repeat: "repeat"` to award another achievement for each separate batch of `goal` actions. An action never belongs to two achievements. Calendar counters deduplicate within their bucket; lifetime and rolling counters deduplicate for the lifetime of the quest. Manual actions use submission admission time; tracker actions use the record's occurrence time. Rolling days mean 24 elapsed hours each. Calendar days follow daylight-saving changes and the project calendar.
>
> Calendar evidence can arrive until seven days after its bucket ends. Set `calendar("day", { latenessMs })` to choose zero through 90 days. Rolling evidence older than its trailing window is retained but cannot count; future activity cannot count. Contributions and achievements have no automatic retention expiry. A later submission cannot reuse an expired distinct key.
>
> A budget-denied achievement is final and retains its actions and budget ID. It does not create a completion, grant rewards or qualify a referral, and it is not moved to a later budget period. Counter history exposes that result. Restored actions are considered on the next accepted action. Settled evidence stays immutable even if its completion is revoked.
>
> Revocation does not recycle counter evidence or reopen a settled counter slot. The completion command's `freeOccurrence` option releases its completion key, but it cannot override the counter's distinct-action and once-per-window rules. Repeat counters can still earn later achievements from new disjoint actions; a calendar counter can earn in its next bucket.

See [counter progress](/docs/use/progress#distinct-action-counters) for member and operator reads, and [qualified referrals](/docs/concepts/referrals#qualification-with-counters) for composition.

## Quests that run on their own [#quests-that-run-on-their-own]

Not every quest waits for a member to submit something. Domino can start one itself:

* **From a tracker.** A new post on X, a Discord message or reaction, or a record your server sends starts a check for the member behind it. See [tracker-triggered quests](#tracker-triggered-quests).
* **From creator content.** Each post a [content quest](#content-backed-quests) collects is checked and rewarded at most once.
* **When a member qualifies.** A quest with only prerequisites is checked as soon as the member meets them, such as reaching 100 points. See [outcomes for prerequisite-only automatic quests](#outcomes-for-prerequisite-only-automatic-quests).

### Content-backed quests [#content-backed-quests]

A quest can read a [tracker's dataset](/docs/concepts/trackers) while it checks, or be started by it. A content quest turns each collected item that belongs to a member into its own try, checked again when its body changes and rewarded at most once. See [UGC programs](/docs/concepts/ugc) for admission, performance gates, and engagement points.

> [!DEV]
> Declare one with `content({ tracker, ...policy })`. Content quests omit `completion`; X and custom sources with declared content bodies and metrics use the same stages.

### Tracker-triggered quests [#tracker-triggered-quests]

A tracker-triggered quest checks each new record a [tracker](/docs/concepts/trackers#tracker-triggers) collects, for the member it belongs to: by default the author, or for example the person an X post replies to. When a record is skipped, Domino records why, and your team can retry it.

> [!DEV]
> `automatic({ tracker })` starts one attempt for each record observation it selects: its firing mode `on` picks new records, every new observation, or changed data, and an optional `where` filter checks the record's data. The attempt belongs to the member behind one of the record's accounts, its `beneficiary` role: the author by default, the account an X post replies to, quotes or reposts with `"inReplyTo"`, `"quoted"` or `"reposted"`, the author of the message a Discord reaction is on with `"messageAuthor"`, or a named account a custom record carries. Its evaluator reads the observation as `ctx.tracker`. Use `once()` for a one-time threshold, `keyed(...)` for one completion per occurrence, with a budget to cap it, or `streak(...)` for consecutive [calendar days](#calendar-day-streaks) of activity. A skipped observation records its code and reason.

## Checks that wait [#checks-that-wait]

Some checks can't be decided right away, such as a post that needs more likes. Domino then checks again later, up to 24 hours from the first wait and 100 times, and can show the member how far they are, such as three of five. Showing progress never completes the quest, spends a budget or grants rewards on its own: only a later acceptance can.

If the wait runs out, the attempt is rejected. Pausing or archiving the quest stops new tries but lets waiting ones finish under their original rules. Someone on your team with `review` permission can reject a waiting attempt to stop it. While an attempt waits, the member can't send another for the same quest.

> [!DEV]
> The evaluator returns `decision.defer` or `decision.progress` (an absolute amount). Each scheduled check uses fresh reads and a fresh evaluation clock, while retries of that check replay its captured reads. The attempt keeps its original release, member, submission, and activity time. Progress becomes visible only when the decision commits. A reached deadline rejects with `defer-expired`; another deferral after the count limit rejects with `defer-limit-exceeded`. Human review ends the timer loop.
>
> A new manual submission for the same member and quest returns `409` with code `attempt-pending` while the attempt waits for its next evaluation, runs it, or awaits the resulting human review. Reusing the original action ID with the same request still returns that attempt. Manual completion keys are computed during evaluation, so this guard also applies to keyed quests.
>
> See [defer evaluation or report progress](/docs/reference/quest-rules#defer-evaluation-or-report-progress) for the limits and capture rules, and [show evaluator progress](/docs/use/progress#show-evaluator-progress) for member fields.

## Attempts and completion [#attempts-and-completion]

This section is the precise model behind the pages above, for your developer.

An attempt is one try under a particular release of the quest. Completion is a successful attempt.

| Term         | Meaning                                                                                                           |
| ------------ | ----------------------------------------------------------------------------------------------------------------- |
| Attempt      | One check of a submission, tracker record, prerequisite change or staff confirmation, under a particular release. |
| Completion   | The successful result of an attempt.                                                                              |
| Prerequisite | A typed completion, balance, or group condition that controls eligibility.                                        |
| Quest type   | Reusable behavior and configurable settings shared by quest instances.                                            |

| State                    | Meaning                                      |
| ------------------------ | -------------------------------------------- |
| Checking evidence        | Domino is verifying the submission.          |
| Waiting for human review | The submission waits for a human reviewer.   |
| Rejected                 | The evidence did not qualify.                |
| Accepted                 | The attempt succeeded; this is a completion. |

An accepted attempt keeps its original release and promised rewards. New publication does not rewrite an attempt already in progress. See [progress and review](/docs/use/progress) for recovery and review behavior.

> [!DEV] Completion rules
> | Rule           | Allows                                                                                                                                   |
> | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
> | `once()`       | One successful completion per member.                                                                                               |
> | `keyed(...)`   | One successful completion per stable occurrence key, such as an order or Discord message. Retrying the same occurrence must keep the same key. |
> | `keyed(..., { across: "members" })` | One successful completion per key across all members, such as a single-use code or a post that pays once. Another member's attempt with a used key is rejected with `key-used`. See [keys shared by members](#keys-shared-by-members). |
> | `streak(...)`  | One milestone per member after a number of consecutive calendar days. See [calendar-day streaks](#calendar-day-streaks). |
> | `progress(...)` | One achievement per batch of distinct accepted actions. See [distinct-action counters](#distinct-action-counters). |
> | Named budgets  | Limits on successful completions or awarded points across quests, per member, per tracker record author ([what one giver gives](/docs/build/trackers#cap-what-each-giver-gives)) or across the project, for all time or per day, week (starting Monday) or month of the [project calendar](/docs/concepts/calendar). |

> [!DEV] Triggers and prerequisites
> | Trigger   | Starts with                                               |
> | --------- | --------------------------------------------------------- |
> | Manual    | A member submission or staff confirmation.           |
> | Automatic | A tracker record, including a collected post for a [content quest](#content-backed-quests), or a change to prerequisite eligibility. |
> | Tracker   | A record observation a tracker trigger selects by firing mode and optional data filter. It runs for the member behind the record account it pays. |
>
> - `completed(quest)` requires a past completion, including one completion of a repeatable quest.
> - `completed(quest, { scope: "period", period })` requires a completion in the current project-calendar day, week, or month.
> - `balanceAtLeast(balance, minimum)` observes the member's current point balance.
> - `inGroup(group)` observes current membership in a [group](/docs/concepts/groups). `defineGroup(...)` names the group by its key; publishing creates it, empty, if the environment lacks it, and operators add members in Console **Groups** or with `management.groups`.

```ts
import {
  balanceAtLeast,
  completed,
  defineGroup,
  defineLedger,
  defineQuest,
  inGroup,
  manual,
  once,
  settings,
} from "@domino-sdk/relay/authoring";

const community = defineLedger({ key: "community", name: "XP" });
const vips = defineGroup({ key: "vip", title: "VIP" });
const welcome = defineQuest({
  key: "welcome",
  title: "Welcome",
  settings: settings.object({}),
  trigger: manual(),
  completion: once(),
  rewards: [],
});
const dailyCheckIn = defineQuest({
  key: "daily-check-in",
  title: "Daily check-in",
  settings: settings.object({}),
  trigger: manual(),
  completion: once(),
  rewards: [],
});

requires: [
  completed(welcome),
  completed(dailyCheckIn, { scope: "period", period: "day" }),
  balanceAtLeast(community, 100),
  inGroup(vips),
]
```

### Trigger and completion rules [#trigger-and-completion-rules]

The trigger decides which completion rules a quest can use. TypeScript checks authored quests, and publication validates the same rules for every client.

| Trigger                         | Completion                                               | Prerequisites |
| ------------------------------- | -------------------------------------------------------- | ------------- |
| Manual or tracker activity      | Once, keyed, streak or counter                           | Optional      |
| Prerequisite-only `automatic()` | Once                                                     | At least one  |
| Content stage                   | Implied once per content item; omit `completion` in code | Optional      |

Only content stages can declare performance evaluation. Reusable prerequisite-only quest types may leave the prerequisite list for Console instances; each instance must supply it before publication. Tracker beneficiary roles follow the source: choosing an account the source does not name fails validation.

### How the check runs [#how-the-check-runs]

The code that checks a submission is the quest's evaluator. Its runtime rules (repeated runs, the frozen clock and randomness, what a missing account or a provider outage returns, and which accounts are pinned) are in the authoring reference:

* See [evaluation time and randomness](/docs/reference/quest-rules#evaluation-time-and-randomness).
* See [evaluator readers: null, errors and pinning](/docs/reference/verification#evaluator-readers-null-errors-and-pinning).
* The [testing kit](/docs/build/testing) runs an evaluator locally under the same rules.

A missing or broken Discord connection is a setup problem, not evidence that a member failed the quest. Such an attempt ends as failed straight away with a clear reason. Fix the connection, then retry it.

> [!DEV]
> An uncaught `integration-required` capability error ends the attempt as `failed` immediately with a reason and code. Publication does not wait for the connection: a quest that needs Discord publishes, a deployment reports it in its `warnings`, and its attempts fail this way until Discord is connected. Connecting or repairing Discord admits the failed attempts of automatic quests without a tracker again. See [recover a configuration failure](/docs/build/integrations#recover-a-configuration-failure).

### Declared integrations [#declared-integrations]

A quest declares provider modules with `integrations: [discord, x]`. The declaration controls which provider readers its evaluator receives: `ctx.discord` and `ctx.x` exist only for declared integrations. The SDK types and the hosted evaluator enforce the same rule. A quest using Discord role rewards or resource selectors also declares `discord`. Wallet, tracker, member, image and review readers remain available; an image sourced from X still needs the X declaration.

A quest can also name a module by its provider, such as `{ provider: "future-chat" }`, for a module Domino adds before your SDK knows it. Such a quest builds, and publishing it refuses a module Domino does not have with `unknown-integration-module`.

> [!DEV]
> A quest's `integrations` lists only the modules this SDK knows (`discord`, `x`). `integrationModules` is its open sibling and lists every declared module, so read it to see modules a newer Relay adds. In the same way, `rewardEntries` beside a release's or a catch-up preview's `rewards` lists every reward as `{ type, data }`, where `type` is the reward's kind and `data` the rest of it. A reward kind your SDK does not know appears only in `rewardEntries`.

Use `discord.grantRole(roleId)` for a declared role reward. Evaluators read provider state; durable reward delivery happens after completion. See [integrations](/docs/build/integrations) and the [v1 upgrade steps](/docs/reference/upgrade-to-v1#declared-integrations).

### Announcements [#announcements]

An announcement is a message Domino's bot posts in one of the project's connected Telegram chats or Discord channels when a completion pays, such as "@ana just received +10 XP by getting a ⚡ from @sam". It isn't a reward: it is posted after the completion's rewards are saved, and a post that fails never undoes them. Each post is retried, shows on the member's page in Console with its status, and can be retried there once it fails for good. Posts in one chat or channel wait in line, paced to what Telegram and Discord allow.

The message comes from a template with a few placeholders: the member who earned, whoever triggered the completion (such as the admin who tipped), the points paid and the quest's title. People are mentioned through the account they linked on that platform. A Test environment posts only in its own connected chats and channels. See [Announce tips in the group](/docs/build/telegram#announce-tips-in-the-group) and [Announce in a channel](/docs/build/integrations#announce-in-a-channel).

> [!DEV]
> Declare them in `announcements`, beside `rewards`, with `telegram.postMessage()` and `discord.postMessage()`; a quest posts at most three. In SDK 1.x they are a quest field of their own because the reward union is closed. Each post is a reward delivery with `action: "post-message"`.

### Shared SDK runtime [#shared-sdk-runtime]

Quests import the installed SDK instead of each carrying their own copy, so a published quest is a few kilobytes and always runs with the SDK version it was built against. Each published bundle also declares the evaluator protocol it expects. See [shared SDK runtime](/docs/reference/authoring#shared-sdk-runtime) and [evaluator protocol](/docs/reference/verification#evaluator-protocol).

### Activity and webhooks [#activity-and-webhooks]

Your app can show members what just happened, such as a review decision, an accepted attempt or a reward change, including completions of quests that ran on their own.

> [!DEV]
> Subscribe with `relay.me.activity.observe()` or React's `useActivity({ onEvent })` instead of inferring a new completion by comparing snapshots. React's `<RelayProvider>` follows the feed itself: each event reads again only the hooks it changes, and the whole quest experience is read every 30 seconds for changes no event announces. An accepted attempt and its reward deliveries remain separate events; acceptance does not mean an integration reward has arrived.
>
> Events are scoped to the member's project and environment and contain public outcome fields. Review notes and provider diagnostics remain operator information. The feed starts with events created after its migration; existing progress and rewards remain the source for earlier state. See the [participant API reference](/docs/reference/participant-api#participant-activity) for cursor polling and recovery.
>
> Operator backends can subscribe to `completion.created` and `review.pending` [webhook events](/docs/reference/webhooks). A completion event identifies the committed completion, the attempt, and its completion key in `key`, with `source.kind` set to `quest`. A leaderboard prize sends the same event with `source.kind` set to `leaderboard`. Pending review identifies the attempt that needs a decision. Events are delivered independently, with retries and no ordering guarantee.

## Resource lifecycle [#resource-lifecycle]

The other things your quests use, such as trackers, leaderboards and point ledgers, can be archived too. Archive one when new activity should stop but its history should stay. Archive state belongs to one project and environment, and live changes it only through [promotion](/docs/concepts/environments).

| Resource         | While archived                                                                         | What remains                                                                                    |
| ---------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Tracker          | Scheduled collection, Discord collection and custom ingestion stop.                    | Records, observations and captured evaluator reads. Restoring retains a separate pause setting. |
| Referral program | New qualifications and bonus accrual stop.                                             | Relationships, previously earned rewards and correction history.                                |
| Leaderboard      | Members cannot list or read it.                                                        | Operators can inspect standings and history.                                                    |
| Engagement rule  | New settlements stop, including results computed before archive but not yet committed. | Accounts and accounting watermarks. Restoring can settle growth since the retained watermark.   |
| Collection slot  | The slot leaves member experiences.                                                    | Its selected quests and ordering. Those quests remain available through other paths.            |
| Quest type       | New quest instances cannot use it.                                                     | Existing quests and their pinned type versions.                                                 |
| Image            | A new evaluator read cannot capture the image by name.                                 | Stored image bytes and evidence already captured by an attempt.                                 |
| Point ledger     | New definitions cannot choose it.                                                      | Existing published rewards, balances and accounting entries.                                    |

Removing a declaration from source does not archive it, and deploying it again does not restore it. Unarchive it in **Settings → Resources**. A quest has its own [archive controls](/docs/build/publishing#archive-and-restore-resources). Archiving a reusable quest type does not archive its existing quest instances. Changes to archive state invalidate an earlier publication preview; review again before publishing.

> [!DEV]
> Unarchive through the management API too. Live refuses archive and restore with `promotion-required`. Tracker definitions and point ledger names and descriptions are current operator configuration, not immutable release histories. Captured evaluator facts, quest releases and point accounting entries retain their existing historical guarantees. Redeployment preserves lifecycle state independently of configuration changes.

## Outcomes for prerequisite-only automatic quests [#outcomes-for-prerequisite-only-automatic-quests]

A quest with only prerequisites checks a member again whenever something changes that could qualify them: a completion, joining a group, enrolling in a campaign, a publication, or a freed occurrence. Any change to their points, such as a quest or referral award, a leaderboard prize, spending, a refund or an operator adjustment, also queues a check of their current balance.

Each check is recorded as admitted or skipped:

| Outcome                    | What happens next                                                                                                                                                                                                                                                   |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `admitted`                 | The linked attempt evaluates the quest. Admission does not guarantee a reward. An attempt with status `rejected` and code `budget-exhausted`, or `failed` with code `integration-required`, can run again after another member event or reviewed operator recovery. |
| `prerequisites-incomplete` | A later prerequisite change checks the current rules again.                                                                                                                                                                                                         |
| `quest-paused`             | Resuming keeps the skipped event unchanged. An operator can preview and confirm recovery.                                                                                                                                                                           |
| `already-completed`        | No new attempt is created for the completed occurrence.                                                                                                                                                                                                             |
| Another skip code          | Show its reason. Current windows, campaign enrollment and budgets can also block admission.                                                                                                                                                                         |

Your team can recover up to 50 paused or budget-blocked outcomes at once, after a preview of the new checks, rewards and budget capacity. See [Recover automatic quest outcomes](/docs/use/quests#recover-automatic-quest-outcomes).

> [!DEV]
> Duplicate delivery preserves an outcome; later transitions can clear `prerequisites-incomplete`. Several transitions while a once-quest attempt is pending reuse the same attempt. If an admitted attempt loses a race for budget while claiming its completion, a later transition checks current rules and creates one new attempt for the uncompleted occurrence. Duplicate or concurrent transitions reuse that retry. An attempt that failed with `integration-required` retries the same way, and Domino admits it again on its own once the integration becomes ready. Evaluator, staff and revoked-occurrence rejections do not retry. Freeing a revoked occurrence also permits one new attempt if the current rules allow it.
>
> Members read these decisions with `relay.quests.outcomes()` alongside tracker outcomes. Prerequisite outcomes have an `automatic` ID and null `tracker`, `record`, and `observedAt`; they are never member-claimable. Tracker activity still checks completion prerequisites at the record's occurrence time.
>
> Recovery selects at most 50 paused outcomes, or admitted outcomes whose attempt has status `rejected` and code `budget-exhausted`, or status `failed` and code `integration-required`. The preview shows new evaluations, existing attempts, declared rewards, each budget's remaining capacity and its fixed or dynamic cost. Confirmation checks the preview against current rules before admitting. Admission reserves no budget: rewards and budget consumption are decided when evaluations settle.

## Where next [#where-next]

- [Your first quest](/docs/build/first-quest) — Start here to author and publish a quest.
- [Test quests locally](/docs/build/testing) — Unit-test an evaluator before you publish it.
- [Compose a campaign](/docs/composition) — Connect quests with points, referrals, and a leaderboard.
- [Manage quests](/docs/use/quests) — Run quests from Console.
- [UGC programs](/docs/concepts/ugc) — Quests that qualify and reward creator content.
- [Authoring](/docs/reference/authoring) — Configuration details.
