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

# Punch Cards

> Reward customers with digital stamp cards that grant rewards as punches accumulate.

Punch Cards are a campaign type that tracks progress toward a fixed number of punches. Each qualifying event can add one punch to the customer's active card. Effects attached to a specific punch number are granted when that punch is reached.

## How punch cards work

A punch card campaign combines three ideas: **cards** (progress records), **punches** (steps on a card), and **per-punch rewards** (effects labeled by punch number).

```mermaid theme={null}
flowchart LR
  E[Customer event] --> S{Campaign matches?}
  S -- No --> Skip[Ignored]
  S -- Yes --> C{Active card?}
  C -- No --> N[Create card\npunches = 0]
  C -- Yes --> P[Use active card]
  N --> P
  P --> F{fulfilled?}
  F -- No --> Preview[Preview rewards\nno punch added]
  F -- Yes --> Inc[punches + 1]
  Inc --> R{Effects for\npunch label?}
  R -- Yes --> Reward[Grant rewards]
  R -- No --> Done[Done]
  Inc --> Full{punches >= max?}
  Full -- Yes --> Fin[status = FINISHED]
  Full -- No --> Active[status = ACTIVE]
```

### Cards and punches

Each customer can have an **active** punch card per campaign. On the first qualifying event (when they are allowed to participate), Masivo creates a card with:

* `punches`: `0`
* `max_punches`: from campaign `attributes.punches_per_card` (default `10` if missing when the card is created)
* `status`: `ACTIVE`

Only **fulfilled** events increment `punches`. Unfulfilled events can still match the campaign and preview effects, but they do not change the card.

When `punches` reaches `max_punches`, the card becomes `FINISHED` and punches are capped at `max_punches` (they do not go past the limit).

### How many cards a customer can have

Participation is controlled by campaign attributes:

| Setting                  | Behavior                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------ |
| `unlimited_cards: true`  | Customer can always start a new card after the previous one finishes                             |
| `unlimited_cards: false` | Customer may hold at most `cards_per_customer` cards for that campaign (including finished ones) |

If there is already an `ACTIVE` card, the customer continues on that card. A new card is created only when there is no active card and the customer is still under the card limit (or unlimited cards is enabled).

### Per-punch rewards

Rewards are not granted on every punch by default. Effects are selected when their `label` matches the current punch number as a string (`"1"`, `"2"`, …).

Optional `tier_id` on an effect restricts it to that tier; effects without `tier_id` apply to all tiers.

Example: a 5-punch card that rewards on punch 3 and punch 5:

```json theme={null}
{
  "rules": [
    {
      "conditions": [],
      "effects": [
        { "type": "POINTS", "amount": 50, "label": "3" },
        { "type": "POINTS", "amount": 200, "label": "5" }
      ]
    }
  ]
}
```

Campaign rules still go through the normal rules engine for matching (event type / conditions). The punch processor then filters effects by punch `label` and tier.

## Punch card lifecycle

```mermaid theme={null}
stateDiagram-v2
  [*] --> Active: Card created
  Active --> Active: Fulfilled qualifying event\n(punches < max)
  Active --> Finished: punches reaches max_punches
  Finished --> Active: New card created\n(if allowed)
```

| State      | Description                                        |
| ---------- | -------------------------------------------------- |
| `ACTIVE`   | Customer can continue earning punches on this card |
| `FINISHED` | Card completed; no further punches on this record  |

There is no loss window or scheduled reset. Completed cards stay `FINISHED` until a new card is created under the participation rules above.

## Campaign configuration

| Field                | Type                    | Description                                                     |
| -------------------- | ----------------------- | --------------------------------------------------------------- |
| `trigger`            | `string`                | Event type that can earn punches (e.g. `"PURCHASE"`)            |
| `privacy`            | `"PRIVATE" \| "PUBLIC"` | Controls visibility in public-facing APIs                       |
| `punches_per_card`   | `number` (≥ 1)          | Punch capacity stored as `max_punches` on each new card         |
| `unlimited_cards`    | `boolean`               | When `true`, customers can open new cards without a count limit |
| `cards_per_customer` | `number` (≥ 1)          | Max cards per customer when `unlimited_cards` is `false`        |

## Consent

Punch card processing requires punch-card campaign consent. If `consent.purposes.punchcard_campaigns` is explicitly `false`, matching campaigns are skipped. Missing consent is treated as allowed.

## Storefront API

List a customer's cards:

`GET /api/storefront/v1/customers/{id}/punch-cards`

| Query           | Description                              |
| --------------- | ---------------------------------------- |
| `status`        | `ACTIVE` (default), `FINISHED`, or `ALL` |
| `punch_card_id` | Optional filter for a single card        |

Progress is driven by [Emit event](/api-reference/behavior/events/post): send the campaign trigger event with `fulfilled: true` for the customer.

## Comparison with Score Streaks

|                         | Punch Cards                    | Score Streaks                            |
| ----------------------- | ------------------------------ | ---------------------------------------- |
| Progress unit           | Punches (capped per card)      | Score (grows in a cycle)                 |
| Completion              | Card finishes at `max_punches` | Milestones (can repeat in a cycle)       |
| Time pressure           | None                           | Loss window + optional reset             |
| Event history on record | None                           | JSON history on the streak               |
| Cron required           | No                             | Yes (expire lost streaks)                |
| Dedicated concept       | This page                      | [Score Streaks](/concepts/score-streaks) |
