> ## Documentation Index
> Fetch the complete documentation index at: https://docs.passlet.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Redemption & scanning

> Control when and how often passes can be redeemed, connect scanner devices, and work with scan history.

Redemption is where passes meet the real world: a barcode is scanned, Passlet decides **accept or deny** based on the template's redemption rule, and the decision is recorded.

## Redemption rules

Each template carries one **redemption rule** — the template-wide policy for when and how many times a pass can be scanned. Configure it in the template editor.

### When can the pass be scanned?

* **Always valid** — no time restriction.
* **During date windows** — one or more windows with open, fixed, issue-relative, or
  variable-driven bounds. Each window can cap its own redemptions, and a multiple-choice
  variable can select which windows apply per pass (e.g. which festival days a ticket covers).
* **After first scan** — activation flow: the pass becomes valid for N hours after its first
  scan (day tickets, time-boxed vouchers).

### How often can it be scanned?

* **Recurring limit** — e.g. once per day, in a timezone you pick.
* **Cooldown** — a minimum number of minutes between scans.
* **Lifetime total** — a maximum number of scans over the pass's life (punch cards).

With no explicit rule, a pass redeems **once per scan context** (and once ever if no context is set).

<Note>
  Validity windows are projected into the wallet's native fields (Apple `relevantDates` /
  `expirationDate`, Google `validTimeInterval`) so passes surface and expire correctly on the
  device — but enforcement is always server-side at scan time, on the server clock.
</Note>

## Scan outcomes

Every scan gets a **result** — `accepted`, `duplicate`, `unknown`, `ambiguous`, `denied`, or `reversal` — plus a machine-readable `reasonCode` explaining it (e.g. `already_scanned`, `out_of_window`, `expired`, `revoked`, `quota_exhausted`). Ambiguous scans return candidate passes for the operator to confirm.

Accepted scans can be **reversed** within a grace window — a reversal frees the redemption slot (useful for accidental scans at the gate).

## The Scanner app

The **Scanner app** page in the sidebar manages the mobile devices that scan passes:

<Steps>
  <Step title="Connect a device">
    Show the **setup QR** to the device running the Passlet Scanner app. The device enrolls
    against your workspace and appears in the **Connected devices** table.
  </Step>

  <Step title="Operate">
    The device scans wallet barcodes and gets an instant accept/deny verdict with the reason.
    Devices report name, platform, and last-seen time.
  </Step>

  <Step title="Revoke when needed">
    **Revoke device** stops a device authenticating immediately — it can be reconnected later
    with the setup QR. **Rotate** the setup QR if it leaked.
  </Step>
</Steps>

Scanner devices authenticate with scoped credentials (`scans:write`) — they never sign in to the admin console and can't see anything beyond scanning.

## Bring your own scanner

Everything the Scanner app does is available on the public API: record scans (single or bulk), list and filter scan history, stream scans in real time (SSE), reverse scans, and pull aggregated stats. See the [Scans endpoints](/api-reference/overview) and give your integration a token scoped to `scans:write` / `scans:read`.

## Where scans show up

* **Pass detail** — per-pass scan history and activity timeline.
* **Projects** — per-project scan panels and stats.
* **API** — `GET /v1/scans`, `GET /v1/scans/stats`, and `GET /v1/scans/stream`.
