{
  "info": {
    "name": "TesserApp Business API v1.0.0",
    "description": "Issue cards from your own systems, read what happened at the till, and get told about it as it happens. No transaction can be written over this API — see the reference for why. Additive changes only inside /v1.\n\nGENERATED from https://docs.tesserapp.eu/openapi.json — do not edit by hand; it is regenerated and drift-checked in CI.\n\nSet the `api_key` collection variable to a key created in the dashboard (Settings → API & integrations). Bearer auth is already wired at the collection level, so every request inherits it.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.tesserapp.eu/api/public/v1",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Me",
      "item": [
        {
          "name": "Who am I, and what can this key do",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "me"
              ]
            },
            "description": "Shop, add-on status, entitlements, location counts — and the CALLING KEY'S OWN scopes and location restriction. An integrator's first call, and the fastest way to resolve a 403. The Business API is an ADD-ON, not a subscription tier: read `entitlements.api_access` and `entitlements.source` rather than looking for a tier name."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Events",
      "item": [
        {
          "name": "The webhook event catalogue",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "events"
              ]
            },
            "description": "Every subscribable event with one payload example each, generated from the builders."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Locations",
      "item": [
        {
          "name": "List locations",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/locations?limit=&cursor=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "locations"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size. Clamped to the maximum (100 by default, PUBLIC_API_MAX_PAGE_SIZE) rather than rejected."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque cursor from a previous response's `next_cursor`. Absent `next_cursor` (null) means the end."
                }
              ]
            },
            "description": "Cursor-paginated. A key restricted to certain locations sees only those; `limit` above the maximum is clamped, not rejected, and the response states the effective limit."
          },
          "response": []
        },
        {
          "name": "Get one location",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/locations/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "locations",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Returns 404 — never 403 — for a location outside the key's restriction: a 403 would confirm that another site exists."
          },
          "response": []
        },
        {
          "name": "Update a location",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Via Roma 1\",\n  \"address\": \"Via Roma 1\",\n  \"city\": \"Milano\",\n  \"postal_code\": \"20121\",\n  \"country_code\": \"IT\",\n  \"lat\": 45.4642,\n  \"lon\": 9.19,\n  \"require_staff_pin\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/locations/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "locations",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The shop's own record of a site: its name, its address and whether staff must enter a PIN there. A location cannot be CREATED over the API — the platform is pay-then-provision, so a new site is bought in the dashboard — and `active` is not a field here: enable and disable are explicit actions below, because \"disabled by the merchant\" and \"blocked for non-renewal\" are different states and one boolean cannot hold both."
          },
          "response": []
        },
        {
          "name": "Disable a location",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/locations/:id/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "locations",
                ":id",
                "disable"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The location's cards stop working immediately. Billing is deliberately NOT affected — renewal levers stay owner-initiated in the dashboard. Creating a location is not an API write: the platform is pay-then-provision."
          },
          "response": []
        },
        {
          "name": "Enable a location",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/locations/:id/enable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "locations",
                ":id",
                "enable"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The inverse of disable: the site starts accepting cards again immediately. Enabling a location that is already enabled is not an error — this is the endpoint to call when reconciling your own store list against ours, without first reading each one."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Programs",
      "item": [
        {
          "name": "List programs",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs?limit=&cursor=&type=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size. Clamped to the maximum (100 by default, PUBLIC_API_MAX_PAGE_SIZE) rather than rejected."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque cursor from a previous response's `next_cursor`. Absent `next_cursor` (null) means the end."
                },
                {
                  "key": "type",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Every card template the shop owns — published or not, of every type. Cursor-paginated, newest first, and filterable by `type`. Programs are SHOP-WIDE and carry no location, so a location-restricted key sees the same list an unrestricted one does; that is the model, not a leak. This is the call that gets you the `program_id` every card write needs."
          },
          "response": []
        },
        {
          "name": "Create a program",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Coffee Card\",\n  \"name_localizations\": {\n    \"it\": \"Tessera Caffè\"\n  },\n  \"description\": \"Ten coffees, the eleventh on us.\",\n  \"description_localizations\": {\n    \"it\": \"Dieci caffè, l’undicesimo lo offriamo noi.\"\n  },\n  \"category\": \"bar\",\n  \"brand_color_hex\": \"#6F4E37\",\n  \"logo_url\": \"https://cdn.tesserapp.eu/shops/caffe-docsfixture/logo.png\",\n  \"total_stamps\": 10,\n  \"repeat_after_reset\": true,\n  \"prize_mode\": \"climb\",\n  \"visible_in_catalog\": true,\n  \"location_scope\": \"all_locations\",\n  \"border_enabled\": false,\n  \"type\": \"standard\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs"
              ]
            },
            "description": "Creates a card template. It is created UNPUBLISHED — publish it as a second call once you are happy with it. Image uploads are not part of v1: supply a logo or access image by URL at create time instead. Several fields are conditionally required by `type` and the example below cannot show every branch at once: a stamp program (`standard`, `private`) also needs `total_stamps` and `icon_key`, `discount` needs `discount_percent`, `access` needs `access_image_url`, and a gift card (type `prepaid`) needs `initial_balance_cents`."
          },
          "response": []
        },
        {
          "name": "Get one program",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The full template: everything the list returns, plus the prize ladder resolved in order and `has_installed_cards` — the edit lock. Once a single card minted from this program is installed on a device, the template is frozen and an update returns `program_has_installed_cards`, so check that flag before offering an edit screen of your own."
          },
          "response": []
        },
        {
          "name": "Update a program",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Coffee Card\",\n  \"name_localizations\": {\n    \"it\": \"Tessera Caffè\"\n  },\n  \"description\": \"Ten coffees, the eleventh on us.\",\n  \"description_localizations\": {\n    \"it\": \"Dieci caffè, l’undicesimo lo offriamo noi.\"\n  },\n  \"category\": \"bar\",\n  \"brand_color_hex\": \"#6F4E37\",\n  \"logo_url\": \"https://cdn.tesserapp.eu/shops/caffe-docsfixture/logo.png\",\n  \"total_stamps\": 10,\n  \"repeat_after_reset\": true,\n  \"prize_mode\": \"climb\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "A partial update — send only the fields you are changing. `type` is immutable after create. If any card from this program is installed on a device the template is locked and this returns 409 `program_has_installed_cards`: publish a new program rather than editing a card people are already carrying. Sending `prizes` REPLACES the whole ladder; omit it to leave the ladder alone, and send `[]` to clear it."
          },
          "response": []
        },
        {
          "name": "Publish a program",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/publish",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "publish"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Makes the template live: from here on it can issue cards and, for catalogue types, it appears in the consumer app. A program is created unpublished, so this is the second half of every create — a program that is never published can still be read over the API but nobody can hold one."
          },
          "response": []
        },
        {
          "name": "Unpublish a program",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/unpublish",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "unpublish"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Existing cards keep working. There is no program delete over the API — it is destructive and dashboard-only."
          },
          "response": []
        },
        {
          "name": "Suspend a program",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/suspend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "suspend"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "A temporary stop, distinct from unpublish: existing cards stop earning and redeeming until it is lifted, rather than the template merely leaving the catalogue. Use it when a promotion is paused mid-flight; use unpublish when it is over."
          },
          "response": []
        },
        {
          "name": "Unsuspend a program",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/unsuspend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "unsuspend"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Lifts a suspension and resumes earning and redeeming on every card of the program. Nothing is replayed: activity that would have happened while suspended did not happen."
          },
          "response": []
        },
        {
          "name": "List a program's prizes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/prizes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "prizes"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The prize ladder in `ordinal` order. A ladder is at most three thresholds with at most three interchangeable options each, and the holder redeems ONE option per threshold — which is why several rows can share a `threshold`. Not paginated: the whole ladder is always one response."
          },
          "response": []
        },
        {
          "name": "Add a prize",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"threshold\": 10,\n  \"name\": \"Free coffee\",\n  \"name_localizations\": {\n    \"it\": \"Tessera Caffè\"\n  },\n  \"description\": \"One espresso or filter coffee, redeemed at the till.\",\n  \"description_localizations\": {\n    \"it\": \"Dieci caffè, l’undicesimo lo offriamo noi.\"\n  },\n  \"ordinal\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs/:id/prizes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "prizes"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Adds one rung to the ladder, or one more option at a threshold that already exists. `threshold` is the stamp count that unlocks it and must be reachable — a threshold above the program's `total_stamps` is refused. Caps: three distinct thresholds per program, three options per threshold."
          },
          "response": []
        },
        {
          "name": "Update a prize",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"threshold\": 10,\n  \"name\": \"Free coffee\",\n  \"name_localizations\": {\n    \"it\": \"Tessera Caffè\"\n  },\n  \"description\": \"One espresso or filter coffee, redeemed at the till.\",\n  \"description_localizations\": {\n    \"it\": \"Dieci caffè, l’undicesimo lo offriamo noi.\"\n  },\n  \"ordinal\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs/:id/prizes/:prizeId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "prizes",
                ":prizeId"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                },
                {
                  "key": "prizeId",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Renames a prize or moves it. Editing the ladder of a program whose cards are already installed is subject to the same edit lock as the program itself — a holder must not watch the reward they were working towards change underneath them."
          },
          "response": []
        },
        {
          "name": "Delete a prize",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/prizes/:prizeId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "prizes",
                ":prizeId"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                },
                {
                  "key": "prizeId",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Removes one option from the ladder. Prizes already redeemed are unaffected: a redemption is a transaction, and deleting the prize it named does not rewrite history."
          },
          "response": []
        },
        {
          "name": "A program's statistics",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "stats"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The numbers behind the dashboard's program page: cards, stamps, redemptions, completion rate, and the per-day / per-location / per-hour breakdowns. EVERY aggregate is nullable and that is the contract — each sub-query runs independently and returns null if it fails, so one broken aggregation degrades one widget instead of failing the call. Treat null as \"unavailable\", never as zero. `timezone` is the shop's, and every bucket is anchored to it."
          },
          "response": []
        },
        {
          "name": "A program's activity timeline",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/timeline",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "timeline"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Cards issued and stamps awarded per bucket over a range, grouped by day, week or month. `buckets` is the label axis and the series are index-aligned with it, zero-filled, so it can be charted without reindexing. `stamps` is null — not an array of zeros — for faced programs (discount, access, gift card (type `prepaid`) and points), which have no stamps at all. Very long ranges at day granularity are refused rather than truncated."
          },
          "response": []
        },
        {
          "name": "The public enrollment deep link and its QR",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/enrollment-qr",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "enrollment-qr"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Returns the deep link string and an inline SVG, so embedding it in your own portal — or printing a poster — is one call."
          },
          "response": []
        },
        {
          "name": "Issue a card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"holder_name\": \"Ada Lovelace\",\n  \"holder_email\": \"ada@docsfixture.invalid\",\n  \"external_ref\": \"EMP-00417\",\n  \"expires_at\": \"2026-10-04T10:15:00.000Z\",\n  \"send_email\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs/:id/cards",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "cards"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The primary write. Set `external_ref` to YOUR id for the person — it comes back on every read and in every webhook, and it is the field to join on."
          },
          "response": []
        },
        {
          "name": "List a program's webhooks",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "webhooks"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Signing secrets and custom header values are masked on read."
          },
          "response": []
        },
        {
          "name": "Register a webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://hooks.example.com/tesserapp\",\n  \"events\": [\n    \"stamp.assigned\"\n  ],\n  \"bearer_token\": \"a-token-your-receiver-checks\",\n  \"active\": true,\n  \"description\": \"CRM sync — production receiver\",\n  \"program_ids\": [\n    \"prg_docsfixture0001\"\n  ],\n  \"location_ids\": [\n    \"loc_docsfixture0001\"\n  ],\n  \"include_shop_wide_events\": true,\n  \"http_method\": \"POST\",\n  \"custom_headers\": {\n    \"X-Partner-Token\": \"a-value-only-your-receiver-knows\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs/:id/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "webhooks"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The response carries the signing secret ONCE. `url` must be https and must resolve to a public address. An empty program or location filter means ALL — see the webhook guide."
          },
          "response": []
        },
        {
          "name": "Update a webhook",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://hooks.example.com/tesserapp\",\n  \"events\": [\n    \"stamp.assigned\"\n  ],\n  \"bearer_token\": \"a-token-your-receiver-checks\",\n  \"active\": true,\n  \"description\": \"CRM sync — production receiver\",\n  \"rotate_secret\": false,\n  \"program_ids\": [\n    \"prg_docsfixture0001\"\n  ],\n  \"location_ids\": [\n    \"loc_docsfixture0001\"\n  ],\n  \"include_shop_wide_events\": true,\n  \"http_method\": \"POST\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/programs/:id/webhooks/:webhookId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "webhooks",
                ":webhookId"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                },
                {
                  "key": "webhookId",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Setting `active` back to true is how an auto-disabled webhook is re-enabled; it also resets the consecutive-failure counter. `rotate_secret` returns a new secret once — signing switches immediately, so update your receiver in the same change."
          },
          "response": []
        },
        {
          "name": "Delete a webhook",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/webhooks/:webhookId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "webhooks",
                ":webhookId"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                },
                {
                  "key": "webhookId",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "A soft delete: it stops firing and stops being listed, and its delivery history survives."
          },
          "response": []
        },
        {
          "name": "Send a test ping",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/webhooks/:webhookId/test",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "webhooks",
                ":webhookId",
                "test"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                },
                {
                  "key": "webhookId",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Posts a `test.ping` payload signed exactly like a real delivery, bypassing subscription matching. A genuine test of your verification code. The ping is ENQUEUED, not sent inline: the response is the id of the delivery row, and its outcome shows up in the delivery log."
          },
          "response": []
        },
        {
          "name": "The delivery log for one webhook",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/programs/:id/webhooks/:webhookId/deliveries?limit=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "programs",
                ":id",
                "webhooks",
                ":webhookId",
                "deliveries"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                },
                {
                  "key": "webhookId",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Every attempt with its status, duration and error, and the headers sent with secrets masked. Retained for the configured window, which is a data-minimisation control."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Cards",
      "item": [
        {
          "name": "List and search cards",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/cards?limit=&cursor=&external_ref=&holder_email=&program_id=&status=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size. Clamped to the maximum (100 by default, PUBLIC_API_MAX_PAGE_SIZE) rather than rejected."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque cursor from a previous response's `next_cursor`. Absent `next_cursor` (null) means the end."
                },
                {
                  "key": "external_ref",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "holder_email",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "program_id",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Filterable by `external_ref`, `holder_email`, `program_id` and `status`. Cursor-paginated. `status` is the DERIVED status, in precedence order `archived` > `disabled` > `expired` > `locked`/`invited` > `active`, so every card appears under exactly one of them and a card that is both disabled and past its expiry reads as `disabled`. An unrecognised `status` is a `400`, not an empty page. `holder_email` is NOT an identity: one address can hold several cards of the same shop, so it answers with a page — join on `external_ref`. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Get one card",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/cards/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "One holder's card, with the program it was minted from, the locations it works at (derived from the PROGRAM's scope — a card carries no location of its own) and what its barcode contains. `installed_at` is the field to check before offering an edit: once it is set, the pass is on a device and the holder fields are frozen. `barcode` is what to print on a badge: `scan_url` is the form a phone's own camera app can open, `legacy_payload` the original `tlp:` string. Do NOT print `install_qr_svg` from the issue response for this — that is the single-use install token, not the card. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Update a card",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json",
                "disabled": false,
                "description": ""
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"holder_name\": \"Ada Lovelace\",\n  \"holder_email\": \"ada@docsfixture.invalid\",\n  \"expires_at\": \"2026-10-04T10:15:00.000Z\",\n  \"external_ref\": \"EMP-00417\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/cards/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Holder fields and `external_ref`. A card installed on a device is locked: its holder fields and expiry are frozen and a write returns 409 `card_locked` — reissue it (which unbinds the device) and the fields open again. An ARCHIVED card is a different 409, `conflict`, and reissue is NOT the way round that one: archiving is permanent, and it KEEPS the holder as the audit record of whose card it was, so the holder fields can never be edited again — issue a new card instead. `external_ref` is the exception to both: it is yours, not the holder's, so it stays writable, and it is applied BEFORE the holder fields are refused — a PATCH that mixes the two can therefore 409 with `external_ref` already changed. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Delete a card (the GDPR / offboarding path)",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/cards/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "The same service call the dashboard's delete makes, so every scrub that path performs — including the single-holder webhook payload scrub — applies here too. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "One card's statistics",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/cards/:id/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id",
                "stats"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Per-card activity for a support screen: \"how many stamps has this person actually earned, and when did they last come in\". `kpis` is a map whose keys depend on the card type (stamp, gift card (type `prepaid`), points or faced) — read the keys your card type has and ignore the rest, so a new card type cannot break your parser. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Reissue a card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/cards/:id/reissue",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id",
                "reissue"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Returns a fresh single-use install link with its expiry, and the same link as an inline QR. The card keeps its identity, its stamps and its balance — this is the \"new phone\" path, not a replacement card — and the previous install link stops working. An ARCHIVED card cannot be reissued and returns 409 `conflict`: archiving unbound the card from its holder’s device permanently, so there is nothing left to install. A DISABLED card can be reissued, and the link works — the card simply stays refused at the till until it is enabled again. A card whose PROGRAM is deleted, inactive or suspended cannot be reissued: 409 `program_inactive` — reactivate the program first. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Archive a card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/cards/:id/archive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id",
                "archive"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Unbinds the device and keeps the holder for audit. NOT erasure — that is DELETE. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Disable a card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/cards/:id/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id",
                "disable"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Switches the card OFF, reversibly. Every scan is refused with `card_suspended`, and the card shows as disabled in the holder's wallet — but it keeps its stamps, its expiry and its device binding, so enabling puts it back exactly as it was. This is the opposite of archive: archive is permanent and unbinds the card from the holder’s device. An archived card cannot be disabled or enabled (409). Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        },
        {
          "name": "Enable a previously disabled card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Makes a retry safe: a repeat with the same key and body replays the stored response; the same key with a different body is a 409. Keys are remembered for 24h (IDEMPOTENCY_DEDUPE_WINDOW_HOURS). A request without the header is processed normally."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/cards/:id/enable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cards",
                ":id",
                "enable"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "Switches a disabled card back on. The card returns to exactly the state it was in — same stamps, same expiry, still installed on the holder’s device. Enabling a card that is not disabled is a 409, and an ARCHIVED card can never be enabled because archiving already unbound it from its holder’s device. A card whose PROGRAM is deleted, inactive or suspended cannot be enabled: 409 `program_inactive` — reactivate the program first. Disabling is always allowed. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. A card is not attributed to a location anywhere in TesserApp — it belongs to a program, and programs belong to the whole shop — so there is no correct subset of cards to serve such a key."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Transactions",
      "item": [
        {
          "name": "List transactions",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/transactions?limit=&cursor=&location_id=&program_id=&card_id=&type=&from=&to=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "transactions"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Page size. Clamped to the maximum (100 by default, PUBLIC_API_MAX_PAGE_SIZE) rather than rejected."
                },
                {
                  "key": "cursor",
                  "value": "",
                  "disabled": true,
                  "description": "Opaque cursor from a previous response's `next_cursor`. Absent `next_cursor` (null) means the end."
                },
                {
                  "key": "location_id",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "program_id",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "card_id",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "type",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "from",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "to",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "The ledger: every stamp, redemption, reversal and expiry, newest first. Filterable by location, program, card, type and date range, and cursor-paginated — the cursor is a keyset, so a row inserted while you are paging cannot make you skip one. This is the one read a location-restricted key can genuinely narrow, because every transaction carries the site it happened at; asking for a location outside the restriction returns an EMPTY PAGE rather than an error, so a sync loop does not break on it. There is NO endpoint that writes a transaction, and no `transactions:write` scope: a transaction is the record that a real card was presented at a real place."
          },
          "response": []
        },
        {
          "name": "Get one transaction",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/transactions/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "transactions",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "An opaque id. The API never promises an id format."
                }
              ]
            },
            "description": "One ledger entry, by the id a webhook or a list page gave you. The natural second call after a `transaction.created` delivery when your receiver keeps only the id. A location-restricted key gets 404 for a transaction at another site — never 403, which would confirm it exists."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Analytics",
      "item": [
        {
          "name": "Stamps over time",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/stamps-timeline?from=&to=&group=&program_ids=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "stamps-timeline"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "to",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "group",
                  "value": "",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "program_ids",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Transaction-derived: recomputed for a location-restricted key."
          },
          "response": []
        },
        {
          "name": "Activity by hour and weekday",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/activity-heatmap?weeks=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "activity-heatmap"
              ],
              "query": [
                {
                  "key": "weeks",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Transaction-derived: recomputed for a location-restricted key."
          },
          "response": []
        },
        {
          "name": "Busiest programs",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/top-programs?limit=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "top-programs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "Transaction-derived: recomputed for a location-restricted key."
          },
          "response": []
        },
        {
          "name": "Cards by type",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/card-type-distribution",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "card-type-distribution"
              ]
            },
            "description": "How the shop’s cards split across the card types it runs. Card-derived. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. This series is not derived from transactions, which are the only site-attributed rows in the system, so it cannot be narrowed to a location."
          },
          "response": []
        },
        {
          "name": "Apple / Google / app split",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/wallet-split",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "wallet-split"
              ]
            },
            "description": "Where the shop’s installed passes live — Apple Wallet, Google Wallet or the app. Card-derived. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. This series is not derived from transactions, which are the only site-attributed rows in the system, so it cannot be narrowed to a location."
          },
          "response": []
        },
        {
          "name": "Growth over time",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/growth-timeline?months=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "growth-timeline"
              ],
              "query": [
                {
                  "key": "months",
                  "value": "",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "New cards and activity over time, as one series. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. This series is not derived from transactions, which are the only site-attributed rows in the system, so it cannot be narrowed to a location."
          },
          "response": []
        },
        {
          "name": "Headline numbers",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/analytics/summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "summary"
              ]
            },
            "description": "The headline numbers, mixing card counts with till activity. Requires an UNRESTRICTED key: a key limited to specific locations is refused with `403 location_scope_unsupported`, and shop-wide data is never silently served to it instead. This series is not derived from transactions, which are the only site-attributed rows in the system, so it cannot be narrowed to a location."
          },
          "response": []
        }
      ]
    }
  ]
}
