Membership Club — Training Manual

The complete manual in one document: every persona, then every workflow by area. Filtered to one area. Generated 2026-08-31T04:15:30Z · content revision 2026-08-30T21:54:00Z.

Back to training

Contents

Part 1 — By persona

Part 2 — By area

Appendices

Customer (Guest)

Anyone on the public site who has not logged in yet.

You are the public. You have no account, no groups and no session — and a surprising amount of the platform is deliberately open to you: the event catalogue, an event's tier and price breakdown, the resale exchange window, the host application form, a comp-invite claim link and a contract signing link.

That is a design decision, not an oversight. Every gate in this platform is enforced at the moment of the action, never by hiding the page. A customer must be able to see what is on sale, and a prospective promoter must be able to apply, before either of them has an account.

The moment you need to own something — a ticket, house credit, a listing — you become a Member. Registering is the boundary.

Core workflows (4)

Takes part in (2)

What this persona cannot do

Without a session you cannot:

  • hold a reservation, check out or pay — the cart and checkout pages require the member group;
  • see anyone's tickets, orders, credit balance or wallet passes;
  • open any /admin/* surface or the door scanner.

Anonymous requests that need a session get an HTML redirect to /login?next=…, or a 401 envelope for JSON.

Member

A registered customer: buys, holds tickets, house credit and resale listings.

You are the platform's centre of gravity. Registering grants the member group automatically, and with it every customer-owned surface: the cart and checkout, your orders and tickets, your wallet passes, your house credit balance and statement, and the resale exchange as both a seller and a buyer.

Your hub is /my. Everything you own hangs off it — /my/tickets, /my/orders, /my/credit, /account/passes. Note the shape of those URLs: the member portal lives under /my/* and /account/*, and every one of those routes scopes its query to your user id. A member cannot read another member's order even by guessing the id — the lookup 404s rather than 403s, so ids cannot be probed.

Core workflows (13)

Takes part in (18)

  • Browse events as a guest — owned by Customer (Guest), your step 9. Shows the same pages with a session, so the difference is visible rather than described.
  • Create your member account — owned by Customer (Guest), your steps 3–6. The account you just made — everything after the redirect is theirs, including the verification the first purchase waits on.
  • Create and launch an event — owned by Host / Promoter, your step 15. The reason the whole thing exists — they are who finally sees it on sale.
  • Buy a ticket — owned by Customer (Guest), your steps 6–13. Everything from the cart onwards needs a session — the buying half is theirs.
  • Understand your event's tiers, prices and releases — owned by Host / Promoter, your step 11. The buyer the whole release schedule is designed for.
  • Refund a ticket or an order — owned by Admin, your steps 1–8. It is their money and their ticket — and where it lands depends on the policy, not on what they ask for.
  • Read the scanner screen — owned by Door Staff, your steps 9–10. Presents the credential — and can show you what the rotating QR looks like from their side.
  • Diagnose a red scan — owned by Door Staff, your step 9. Is the guest at the window, and usually holds the fix on their own phone.
  • Use your membership card at the bar (VIP) — owned by VIP Member, your steps 1–2. Holds the same card without Zone B — the contrast is the lesson.
  • Open or close the resale exchange — owned by Admin, your step 5. They are the seller and the buyer: the bounds you set are the prices they are allowed to type.
  • Process a payout run — owned by Admin, your steps 1–3. It is their money leaving the platform: they request it and they are the one the identity gate applies to.
  • Post a house credit adjustment — owned by Admin, your step 5. It is their balance that moves, and the correction lands on their statement in full view.
  • Claim your comp invite and name your plus-ones — owned by Customer (Guest), your steps 5–7. The same invite addressed to an account is claimed from the member portal instead of a token link.
  • Build a trigger rule — owned by Venue Manager, your step 9. Is the person whose purchase crosses the threshold that fires the rule.
  • Build a role from capabilities, and tune a membership tier — owned by Admin, your steps 18–19. Is the person a tier is for — and the only one who can confirm the benefit actually landed.
  • Grant and revoke roles — owned by Admin, your step 4. It is their account: the grant is what changes what they can buy and where they can walk.
  • Deactivate a user — owned by Admin, your step 3. It is their account: they are the one who suddenly cannot log in, and they are told nothing.
  • Run the feedback backlog as a build list — owned by Venue Manager, your step 1. Supplies the backlog, and is the only person besides an admin who may edit their own words.

What this persona cannot do

You cannot:

  • reach any /admin/* page, the door scanner, or the debug console;
  • refund your own order at will — self-cancellation is only offered when the event's refund policy sets a cancellation deadline;
  • see or edit another member's data, in any module;
  • issue yourself a comp, a door override or a role.

VIP Member

A member with a house tab and Zone B access. Implies everything a member can do.

You are a member with two extra things: a VIP tab (a credit limit you can spend against and settle later) and a wider door credential — your membership card opens Zone B, not just the general zone.

The group logic matters here. vip_member implies member: the platform's group check expands vip_member to include member, so you are never granted both and never lose member surfaces. Everything on the Member page is yours too — this page only covers the difference.

The tab is deliberately not a wallet. It is an obligation recorded on the double-entry ledger: spending against it raises what you owe, and a scheduled monthly settlement sweeps your prepaid balance first and then charges your card for the remainder. Nothing about it is a discount.

Core workflows (2)

Takes part in (7)

What this persona cannot do

VIP status does not grant staff powers. You cannot:

  • set or raise your own tab limit — only an admin can, and revoking VIP freezes the tab while the balance stays owed;
  • skip the door — a VIP card is scanned like every other credential;
  • reach any admin, scanner or ledger surface.

Host / Promoter

An outside promoter running an event at the venue. Scoped to that one event.

You are an outside party who wants to put on a night at the venue. You are not staff, and the platform is emphatic about that: your grant is a scoped one. A host grant carries an event_id, and the permission check only passes for that event. Two promoters with host grants cannot see each other's events at all.

Your journey starts before you have an account: the application form is public. Approval is what creates your user and your scoped grant — in that order, and never by hand.

Inside your event you are effectively read-only. You can see your event and its sales, you sign the contract, you invite guests against the comp buckets allocated to you. You do not price it, transition it, refund it or touch its money. That separation is the whole point of the contract: it is the record of what the venue agreed to do on your behalf.

Core workflows (11)

Takes part in (11)

What this persona cannot do

Even on your own event you cannot:

  • create the event yourself — it is converted from your approved application by staff;
  • change tiers, prices, capacity or the event's status;
  • issue refunds, read the ledger, or take a payout outside the contracted royalty;
  • see any other promoter's event, or any venue-wide report.

Door Staff

Works the door: scans credentials, searches the guest list, checks people in.

You are the last mile. Your job is two screens — the scanner and the guest list — and a single question per person: green or red.

The design intent is that you never have to decide. The scan endpoint returns a verdict and a reason, and the screen is deliberately loud and colour-coded so it reads at arm's length in the dark. A red scan is not an accusation; it is a reason code (already used, wrong event, outside the door window, listed for resale, revoked) and each one has a different answer.

You are one of the three employee groups. That is a pseudo-group — a gate meaning "door staff or venue manager or admin" — and it is what opens the scanner. It is not a grantable group: nobody is ever given "employee", they are given door_staff, venue_manager or admin.

Core workflows (5)

Takes part in (14)

  • Find your way around as staff — owned by Venue Manager, your steps 8–9. Learns the two screens their role actually opens, and what a 403 there means.
  • Monitor live sales on the night — owned by Venue Manager, your step 9. Supplies the other half of the picture — how many of the sold tickets actually walked in.
  • Get through the door — owned by Member, your steps 5–6. Scans the credential and reads the verdict out loud.
  • Use your membership card at the bar (VIP) — owned by VIP Member, your step 5. Scans the card at the bar and reads the credit summary off the verdict.
  • Run the door offline — owned by Venue Manager, your steps 5–7. Actually works the queue in offline mode and presses Sync now when the network returns.
  • Resell a ticket you can't use — owned by Member, your step 6. Shows the consequence of listing: the seller's own credential now scans red.
  • Buy a ticket on the resale exchange — owned by Member, your step 9. Confirms the new pass works and the seller's old one does not.
  • Allocate comps to a promoter — owned by Venue Manager, your step 9. Turns the names in the bucket into people in the room, and lives with your plus-one decisions.
  • Claim your comp invite and name your plus-ones — owned by Customer (Guest), your steps 8–9. Finds the guest by name on the night and checks them in with their plus-ones.
  • Invite a guest and track plus-ones — owned by Host / Promoter, your steps 11–12. Finds the name on the night and counts the plus-ones in.
  • Revoke a comp — owned by Venue Manager, your step 6. Meets the consequence at the door if the revocation lands after the guest arrives.
  • Grant and revoke roles — owned by Admin, your step 8. A door_staff grant is the thing that opens the scanner at all.
  • Map groups to door zones — owned by Admin, your steps 4–7. The map decides which reader turns green for them and for the guests they scan.
  • Deactivate a user — owned by Admin, your step 4. The revoked credential fails at their reader and they must not mistake it for a broken scanner.

What this persona cannot do

Your access is narrow on purpose. You cannot:

  • open any admin dashboard, ledger, payout or tax surface;
  • download the offline sync bundle or the push feed from a browser — those carry every pass secret, so they need a venue manager session or a provisioned reader's device token;
  • create or revoke passes, refund anything, or change a role;
  • allocate comps — you check in the guests a manager or promoter listed.

Venue Manager

Runs the floor and the calendar: intake review, live sales, the door, marketing.

You run the venue day to day. venue_manager implies door_staff, so everything on the Door Staff page is yours as well — plus the surfaces a shift lead actually needs: the host application queue, the live sales dashboards, the guest list allocations, the door overrides, the access-control readers view and the marketing tools.

What you will notice is where the platform stops you, and it is worth understanding why rather than filing a ticket about it. You are trusted with operations; you are not trusted with money and identity. Payouts, refunds, the ledger, the tax office, role management and the universal data suite are all admin-only. That boundary means a manager can be hired, trained and given the floor on day one without ever being able to move a cent or grant themselves a group.

Core workflows (17)

Takes part in (24)

What this persona cannot do

You are a 403 on all of these, by design:

  • the ledger, trial balance and adjustments;
  • refunds and the payout queue;
  • the tax office;
  • role grants and revocations;
  • the universal admin data suite and its audit log;
  • transitioning an event's status and editing tiers or prices.

You can read most of the dashboards those things feed — the boundary is on writing, and on the surfaces where money is decided.

Admin

Owns money, identity and configuration. Implies every other group.

Admin is not "manager plus a bit". The permission check special-cases it: admin implies everything, including every event-scoped host grant. There is no surface in this platform you cannot open.

What is genuinely yours alone is the set of decisions that are hard to reverse: money out (refunds, payouts, ledger adjustments), identity (who is in which group), the contract counter-signature that binds the venue, the tax filings that go to an agency in the post, and the universal data suite that can edit any row in the database.

Two habits the design assumes of you. First, the ledger is append-only — enforced by database triggers, not convention — so you correct a mistake by posting a compensating entry, never by editing history. Second, everything you do through the data suite and the debug console is audited with a full before/after row snapshot. Both exist so that "the admin fixed it by hand" is always a readable event rather than a mystery.

Core workflows (24)

Takes part in (43)

What this persona cannot do

There is no group above you, so the limits are structural rather than permission-based:

  • you cannot UPDATE or DELETE an append-only table — the ledger, the audit logs and the sealed contract documents refuse the write at the database level, even from the data suite;
  • you cannot un-send a mailed tax filing or un-seal a signed contract;
  • you cannot grant the pseudo-group employee — it is a gate, not a grantable group.

Door & Access

Wallet passes, rotating QR codes, the scanner, and getting people inside.

Add your ticket to your phone wallet

Turn a pass into an Apple or Google wallet card, and understand the QR that changes every 15 seconds.

Owned by Member · 8 steps · about 12 minutes

Why this exists

A ticket and a pass are two different rows on purpose. The ticket is the entitlement — it survives being sold to someone else. The pass is the door credential minted from it, and it must not survive that, because the person walking in has to be the person who currently owns the ticket.

The pass carries two credentials. The NFC payload is a compact signed blob: the zones and identity are signed with a venue key that can be rotated without invalidating what is already in people's wallets. The QR fallback is a rotating code derived from a per-pass secret and the current 15-second time step, with one step of tolerance either side. That short window is the entire anti-screenshot design: a code someone photographed and sent to a friend is worthless within half a minute, so the venue never has to argue about whether a picture of a ticket is a ticket.

Your phone renders that code itself. The wallet page fetches the pass secret once — from an endpoint only the owner can call, admins included — caches it in the browser, and computes codes locally, so the QR keeps rotating with no signal in a basement venue.

Corrected 2026-08-24 (end2end). This paragraph used to say that paying for a ticket does not mint a pass, that a freshly bought ticket can legitimately show an empty wallet, and that the remedy is to re-run the seed. All three are wrong now, and the last one is worse than wrong — re-seeding a live instance destroys the feedback overlay, which is the input to the whole plan pipeline.

Paying mints the pass. Settlement issues it inside its own SAVEPOINT, so a credential failure rolls back only the half-written pass rows and the sale still commits — because a customer charged with no order, no ticket and no ledger record is strictly worse than a paid ticket whose pass is a moment late. Issuance is idempotent per ticket and a failed attempt leaves no pass row at all, in particular no revoked one. The scheduler tick payments.backfill_ticket_passes is the recovery arm: it mints the missing pass on the next tick for any purchased ticket that has none. Comp tickets are deliberately excluded — the guest-list module owns comp credential policy and mints their passes itself.

Before you start

  • A member session holding at least one issued ticket that has a pass (the seeded demo accounts do).
  • An admin session only for the last step.

Practise with

PersonaEmailPasswordNote
membermember@club.test member123holds a seeded membership card
membernova@demo.club nova-pass-123holds event tickets on the demo event, with passes after a second seed run
adminadmin@club.test admin123owns the signing keys and the wallet push log

Steps 1–7 — Member

their manual →
  1. 1
    Start from your tickets and find the one you are travelling with.
    /my/tickets payments
    Expected result Your tickets grouped into upcoming and past, with event, tier and serial.
    Watch out for This page shows tickets, not credentials. It links to your wallet rather than showing a code — do not try to scan anything from here.
  2. 2
    Open your wallet. Every credential you hold lives here: event passes and, if you have one, your membership card.
    Expected result One card per pass, with its status, the zones it opens and a live rotating code.
    Watch out for A pass showing Suspended means the ticket is listed on the resale exchange. A pass showing VOID has been revoked and is never coming back.
  3. 3
    Look at the same list as JSON when you need to see exactly what you hold.
    Expected result Your passes with kind, status, serial and zones.
  4. 4
    Add the pass to Apple Wallet.
    /api/access/passes/{pass_id}/pkpass access
    Expected result A pass file, and a wallet registration recorded so the venue can push updates to it later.
    Watch out for A revoked pass returns 410 rather than a broken file. The wallet copy is not a snapshot — if the pass is later suspended or revoked, a push updates or voids the copy on your phone.
  5. 5
    Do the same for Google Wallet.
    /api/access/passes/{pass_id}/gpass access
    Expected result A signed token for the Google pass.
  6. 6
    Fetch the current QR payload and watch the seconds remaining tick down.
    /api/access/passes/{pass_id}/qr access
    Expected result A payload plus how long it is valid and the 15-second period it rotates on.
    Watch out for A pass suspended for resale returns a 409 here instead of a code. You cannot list a ticket for sale and also carry a working code for it.
  7. 7
    Understand how the offline code works: this endpoint hands the pass secret to the phone once, and the page computes every future code locally.
    /api/access/tickets/{ticket_id}/secret access
    Expected result The secret, the digit count, the algorithm and the 15-second period.
    Watch out for Owner only. An admin calling this for your ticket gets a 403 — the secret is the credential, and staff never need it. Treat a leaked secret exactly like a leaked password: it is worth a revocation.

Steps 8 — Admin

their manual →

Owns the signing keys behind every credential and the log of wallet pushes.

  1. 8
    As an admin, look at the wallet push log and the signing keys behind all of this.
    /admin/access access
    Expected result Readers, keys with their status, the revocation list and recent wallet traffic.
    Watch out for Rotating a signing key keeps existing payloads valid — retired keys still verify — so rotation is safe to do mid-season. Marking a key compromised is the one that starts failing credentials.

Get through the door

Turn a ticket into a wallet pass, then get scanned green on the night.

Owned by Member · 8 steps · about 10 minutes

Why this exists

A ticket is an entitlement in the database. A pass is the physical-world credential minted from it, and the two are deliberately separate rows: the ticket can survive changes of ownership that the pass must not.

Every credential is signed, and the QR code rotates — it is derived from a per-pass secret and the current time step, so a screenshot someone sent a friend is worthless within a minute. The NFC payload is signed with a key that can be rotated venue-wide. This is why a red scan is almost never "the phone is broken": it is a specific, logged reason.

The other half of the design is that revocation is terminal. When a ticket is resold or refunded, the old pass is revoked, its serial and payload hash are blacklisted and its secret is killed — and a new pass is minted for the new owner. The old credential does not merely stop working; it fails loudly with a reason the door can read out.

Before you start

  • A paid order with at least one issued ticket (see 'Buy a ticket').
  • For the door half: an employee session (door staff, venue manager or admin).

Practise with

PersonaEmailPasswordNote
membermember@club.test member123the ticket holder
door_staffdoor@club.test door123works the scanner
venue_managermanager@club.test manager123sees the scan logs and the readers

Steps 1–4 — Member

their manual →
  1. 1
    Find the ticket you bought.
    /my/tickets payments
    Expected result The ticket with its event and tier.
    Watch out for A ticket does not automatically have a pass. Issuing the door credential is the next step and it is yours to do.
  2. 2
    Open your wallet. This is where every credential you hold lives — event passes and, if you have one, your membership card.
    Expected result Your passes with their status and the zones each one opens.
  3. 3
    Add the pass to Apple Wallet (or use the Google variant next to it).
    /api/access/passes/{pass_id}/pkpass access
    Expected result A downloadable pass file; the platform records a wallet registration so it can push updates to it later.
    Watch out for The wallet copy is not a snapshot. If the pass is later suspended or revoked, a push updates or voids the copy in your phone.
  4. 4
    Look at the rotating QR fallback — the thing you actually hold up if the NFC reader is having a bad night.
    /api/access/passes/{pass_id}/qr access
    Expected result A QR payload that changes on a short time step.
    Watch out for It rotates. A screenshot taken earlier will scan red, and that is the point, not a bug.

Steps 5–6 — Door Staff

their manual →

Scans the credential and reads the verdict out loud.

  1. 5
    On the door, open the scanner, pick your reader, and scan the guest's phone.
    /scanner access
    Expected result A full-screen green or red verdict readable at arm's length.
    Watch out for The scanner is gated to the employee pseudo-group — door staff, venue manager or admin. It is the only staff surface most door staff ever need.
  2. 6
    Understand what the scan actually did: it validated the signature, checked the pass is active, checked the event window and zone, and checked nobody has already walked in on this credential.
    /api/access/scan POST access
    Expected result Green with the holder and zone, or red with a specific reason code.
    Watch out for This endpoint is authenticated by the READER's device token, not by your login. That is what lets a provisioned door device keep scanning when the network drops and it has no session.

Steps 7–8 — Venue Manager

their manual →

Investigates a red scan from the access dashboard.

  1. 7
    When a guest insists their ticket is fine, open the access dashboard rather than arguing at the door.
    /admin/access access
    Expected result Readers, signing keys, revocations and recent scans.
  2. 8
    Look up the actual scan and read the reason code: already used, wrong event, outside the door window, suspended because the ticket is listed for resale, or revoked.
    Expected result The scan history for that pass, with the verdict and reason on each row.
    Watch out for 'Listed for resale' is the one that surprises people. Listing a ticket suspends its pass on purpose — you cannot sell it and also walk in on it.

Scan guests at the door

Work the scanner and the guest list through a whole door shift, including offline.

Owned by Door Staff · 8 steps · about 20 minutes

Why this exists

This is the door shift from the staff side, and it is designed around one constraint: the person holding the phone should never have to make a judgement call, and should never be blocked by the network.

Everything a door needs resolves to a verdict plus a reason. Ticket holders scan; guest-list names are searched and checked in; a walk-in a manager vouches for gets an override that is recorded as an override rather than quietly issued as a normal entry. The audit trail is the product here — after the night, the difference between "we let 40 people in on the list" and "we let 40 people in and here is who authorised each one" is the whole point.

Offline is a first-class mode, not a fallback. A venue manager pulls a signed bundle of the night's valid credentials to the device up front; scans queue locally and are pushed back in a batch. The bundle and the push feed carry pass secrets, which is exactly why door staff cannot download them from a browser — it needs a manager session or the reader's own device token.

Before you start

  • An employee session (door staff, venue manager or admin) for the scanner.
  • An event with issued passes and, ideally, a guest list with entries.

Practise with

PersonaEmailPasswordNote
door_staffdoor@club.test door123the door shift persona
venue_managermanager@club.test manager123the only non-admin who can pull the offline bundle
adminadmin@club.test admin123provisions readers and rotates the signing key

Steps 1–4 — Door Staff

their manual →
  1. 1
    Open the scanner and select the reader you are working on before the first guest arrives.
    /scanner access
    Expected result The reader picker and a scan surface.
    Watch out for Selecting the right reader matters: the reader is what defines which zones you are admitting to.
  2. 2
    Scan credentials one after another and act on the colour. Green: in. Red: read the reason to the guest, do not improvise.
    /api/access/scan POST access
    Expected result Green with holder and zone, or red with a reason code.
    Watch out for The same credential scanned twice is red with 'already used'. That is a passback guard, not a system error — the second person holding that screenshot is the problem it exists to catch.
  3. 3
    For guests with no ticket, switch to the guest list and search by name.
    /scanner/guestlist guestlist
    Expected result Matching entries with their bucket, their plus-one allowance and whether they are already in.
    Watch out for Search by the name on the list, not the name on the ID — comps are listed by whoever the promoter invited.
  4. 4
    Check the guest in, with their plus-ones counted.
    /api/guestlist/door/{event_id}/entries/{entry_id}/checkin POST guestlist
    Expected result The entry flips to checked in and the plus-one count is decremented.
    Watch out for Plus-ones are an allowance, not a suggestion. Over-admitting here is what makes the promoter's allocation meaningless.

Steps 5–6 — Venue Manager

their manual →

Prepares the device for offline work and watches the door from the dashboard.

  1. 5
    Before the doors open, pull the offline bundle for the event onto the device.
    Expected result A signed bundle of the night's valid credentials.
    Watch out for Door staff cannot do this from a browser and should not be asked to: the bundle contains pass secrets, so it needs a manager session or a provisioned reader's device token.
  2. 6
    Watch the door from the access dashboard during the shift — live scans, reds, and the readers that are actually reporting in.
    /admin/access access
    Expected result Readers, recent scans and revocations.

Steps 7–8 — Admin

their manual →

Provisions the reader and owns the signing keys behind every credential.

  1. 7
    Provision a new reader when a device is added, and note the device token it issues.
    /api/access/readers POST access
    Expected result A reader row with a rotatable token.
    Watch out for The token is the device's identity. If a device is lost, rotate the token — do not delete the reader, or you lose its scan history.
  2. 8
    After the night, review the scan log: greens, reds by reason, and every override.
    Expected result The full scan ledger for the event.
    Watch out for This is the record you will be asked for when a capacity or a comp question comes up weeks later. It is append-only by design.

Read the scanner screen

Reader, zone, direction, NFC versus rotating QR, and what green and red actually tell you.

Owned by Door Staff · 11 steps · about 15 minutes

Why this exists

The scanner is designed so that the person holding the phone never has to make a judgement call. Every scan returns a colour and a reason, the screen is enormous and colour-coded so it reads at arm's length in the dark, and there is no 'override' button anywhere on it. If the screen is red, the answer is not to try again harder; it is to read the reason out loud.

Two credential formats arrive at that box and it matters that you can tell them apart. An NFC payload is a signed blob — a base64url JSON claim set with an HMAC after a dot. A rotating QR is the short string beginning CLB1: followed by the pass serial and an eight-digit code. The scanner sniffs the CLB1 prefix and labels the scan qr, otherwise nfc; you do not choose. The QR code exists because NFC readers have bad nights, and it rotates on a fifteen-second step with one step of tolerance either side. That is the whole anti-screenshot design: a code a guest forwarded to a friend earlier in the evening is dead within about forty-five seconds.

The other thing to internalise is that zones come from the pass row, never from the credential. The signing scheme is symmetric HMAC, so a payload's own zone claim is not a trust boundary — if a presented payload claims zones that differ from the stored ones, that is a re-signed credential and it scans red for wrong zone. Your reader's zone is compared against what the database says the pass opens, and nothing else.

Finally, the reader you pick is not cosmetic. It carries the zone, the direction (in or out), and the anti-passback window. A credential scans red for wrong zone when your reader's zone is not among the zones written on the pass, so standing at VIP Door B and scanning general-admission tickets produces a wall of wrong-zone reds that are entirely your own doing. It does not run the other way. A VIP tier's ticket carries the main door as well as the VIP room, and a member's card carries every zone their groups grant, so neither is refused at the main door.

Before you start

  • An employee session — door staff, venue manager or admin.
  • At least one issued pass to scan. The seed mints membership cards for member@club.test and vip@club.test.
  • The four seeded demo readers: Main A (zone A, in), VIP B (zone B, in), Exit A (zone A, out) and the bar POS.

Practise with

PersonaEmailPasswordNote
door_staffdoor@club.test door123door shift account: scanner + guest list, nothing else
membermember@club.test member123holds a seeded pass and membership card to practise scans against
venue_managermanager@club.test manager123shift lead: implies door_staff, plus intake, sales, guest list, marketing

Steps 1 — Venue Manager

their manual →

Chooses which readers exist for the night and reads the scan log afterwards.

  1. 1
    Before the shift, open the Access Console and check the readers your door will use are active and pointed at the right zones and directions.
    /admin/access access
    Expected result A Readers table with name, zone, direction, passback window, status, last seen and cache freshness.
    Watch out for A reader with status disabled will refuse every scan with reader_disabled, not with a normal red verdict. Fix it here before doors, not at the door.

Steps 2–8 — Door Staff

their manual →
  1. 2
    Open the scanner and pick your reader from the dropdown. Check the two badges underneath it: they show the zone and the direction you are now scanning as.
    /scanner access
    Expected result Badges reading something like zone A and in. The choice is remembered in the browser, so it survives a reload.
    Watch out for The remembered reader is the classic first mistake of a second shift. If you worked the VIP door last night and the main door tonight, the page will happily reload with VIP B still selected.
  2. 3
    Learn to recognise the two payload shapes. A long string with a dot near the end is an NFC payload. A short string starting CLB1: is a rotating QR. The box accepts either — paste it, or let the reader hardware type it, and press Enter.
    Expected result The scan fires on Enter or on the Scan button; the input clears itself immediately so the next guest can go.
    Watch out for The input clearing is intentional. If you need to know what you just scanned, read the Recent scans table below — do not try to scroll the input back.
  3. 4
    Scan a valid pass and read the green screen properly. It shows a tick and Welcome, then the holder name and their roles, the event, the tier and the zones the pass opens, the entry count, and the round-trip latency in milliseconds.
    /api/access/scan POST access
    Expected result A full-screen green panel and a new row at the top of Recent scans.
    Watch out for Green on a door reader means the whole gate chain passed: signature or TOTP, revocation blacklist, pass status, order paid, event window, zone, and anti-passback. It is not a partial verdict — there is nothing else for you to check. One green is narrower than that and it is not yours: a membership card presented at a BAR reader returns green as soon as the card authenticates (access/service.py:1403), deliberately skipping anti-passback and writing no check-in. It answers 'is this a member', not 'has this person come through a door', so a bar green never tells you anyone is inside the building.
  4. 5
    Now scan the same credential a second time on the same in reader. Watch it go red with Already Scanned.
    /api/access/scan POST access
    Expected result A red panel reading Already Scanned.
    Watch out for This is anti-passback, and it is the guard that matters most in practice. The seeded in readers use a window of zero, which means infinite — once a serial is checked in it cannot check in again until it checks OUT on an out reader. The second person holding that screenshot is exactly the problem this exists to catch.
  5. 6
    Switch to the Exit A reader (direction out) and scan the same pass to check the guest out. Then scan them back in on the in reader.
    /api/access/scan POST access
    Expected result Green on the way out, then green on the way back in with the entry count incremented.
    Watch out for Scanning a guest OUT who never came in is red with Not Checked In. That is not an accusation of anything — it usually means they entered on a different door before you started, or on a reader that was offline.
  6. 7
    Take a general-admission pass to the VIP B reader and scan it. Read the wrong-zone red.
    /api/access/scan POST access
    Expected result A red panel reading Wrong Zone.
    Watch out for Zone codes and role zones are written differently in the two halves of the system — the event side uses A, B, C and the role side uses zone_a, zone_b, zone_c — and the scanner normalises them before comparing, so A really does equal zone_a and a spelling mismatch is never the cause. What the red DOES tell you is narrower: your reader's zone is not among the zones that pass opens. Two things produce that and only one of them is the guest. Check your reader badges first — the wall of wrong-zone reds this manual warns about two steps up is the same verdict, caused by the reader you picked — and only then tell somebody they do not hold the zone. There is also a second, rarer zone red, Zone Closed For This Event: that one means the event dropped the zone after the pass was issued, and it is never the guest's doing.
  7. 8
    Understand the bar reader, because it behaves differently on purpose. Scanning a membership card on the bar POS reader authenticates the member and returns their prepaid balance and available tab — and deliberately does NOT check anybody in or consume an entry.
    Expected result Green with a credit line showing prepaid and tab amounts.
    Watch out for Do not use the bar reader on the door. It never records an entry, so a night scanned on the bar reader leaves you with no check-in count at all.

Steps 9–10 — Member

their manual →

Presents the credential — and can show you what the rotating QR looks like from their side.

  1. 9
    From the guest's side: open your wallet and show the pass you are presenting.
    Expected result Your passes with status and the zones each one opens.
  2. 10
    Show the rotating QR fallback and watch the seconds-remaining counter tick down.
    /api/access/passes/{pass_id}/qr access
    Expected result A payload plus seconds_remaining and a fifteen-second period.
    Watch out for If your pass is suspended because you listed it for resale this returns a 409 rather than a code, and if it has been revoked it returns a 410. Neither is a network problem.

Steps 11 — Venue Manager

their manual →

Chooses which readers exist for the night and reads the scan log afterwards.

  1. 11
    After the doors settle, pull the scan log filtered to your event to see the shape of the night: greens, reds by reason, and the latency percentiles.
    Expected result Log rows plus a stats block with count, green, red, p50, p95 and max milliseconds.
    Watch out for A cluster of reds all sharing one reason code is nearly always a configuration mistake — the wrong reader selected, or a reader pointed at the wrong zone — not a crowd of fraudsters.

Diagnose a red scan

Every rejection reason the door can produce, what it means, and who fixes it.

Owned by Door Staff · 15 steps · about 18 minutes

Why this exists

The reason codes are a closed set. There are thirteen of them and the scanner can never invent a fourteenth, which is what makes it possible to train a door on them exhaustively in one sitting. A red scan is never 'the system is being weird'; it is one of thirteen specific statements about that credential, recorded in the scan log with a timestamp and a reader.

They are produced in a fixed order, and the order is itself the diagnosis. The pipeline runs: parse the payload, then verify the signature or the time-based code, then check the revocation blacklist, then the pass status, then whether the order is actually paid, then whether the event is inside its door window, then the zone, then anti-passback. The first gate that fails is the one you are shown.

Two codes can also appear before their place in that order, and one of them means something different when they do. The pipeline splits on which kind of credential was scanned (access/service.py:1270), and each half has an early exit:

  • Already Scanned, on the QR path only, fires right after the time-code check — before paid, event and zone are looked at at all. That is the cross-reader replay guard, and it is the good kind of early: it stops the second door before anything else is considered.
  • Wrong Zone, on the signed-pass path, fires right after the signature, when the zones written into the payload do not match the zones on the issued pass. A pass is re-issued rather than edited when its zones change, so those two can only disagree if the payload was altered after issuance. This wrong-zone is not "right guest, wrong door" — it is a credential whose claims do not match the pass it names, and the guest at the barrier may not be the person it was issued to.

So the free inference holds for the ordinary wrong-zone red — signature valid, pass live, order paid, event open — but only for the one raised at the zone gate. If the guest scanned a signed pass rather than a live QR code, a wrong-zone red may have skipped every one of those checks. When a wrong-zone red comes off a saved or forwarded pass rather than the wallet's live screen, treat it as a credential question and call a manager, not as a door mix-up.

The design intent behind showing a reason at all, rather than a generic refusal, is that most reds have a legitimate answer at the door. Listed for sale means the guest has their ticket on the exchange and needs to delist it. Code expired means their screenshot is stale and they should open the live wallet page. Ticket not paid usually means a refund. Only a handful are genuinely 'this person is not getting in', and you should be able to tell those apart in a second.

What you must not do is escalate by improvising. There is no override on the scanner. If a guest needs to come in and the pass says otherwise, the sanctioned route is a manager and the guest-list walk-in override, which is recorded as an override with a named authoriser and a reason.

Before you start

  • An employee session and a red scan in front of you.
  • A manager or admin reachable for the escalations at the end.

Practise with

PersonaEmailPasswordNote
door_staffdoor@club.test door123door shift account: scanner + guest list, nothing else
venue_managermanager@club.test manager123shift lead: implies door_staff, plus intake, sales, guest list, marketing
adminadmin@club.test admin123the only account that can touch money, identity and contracts
membermember@club.test member123holds a seeded pass and membership card to practise scans against

Steps 1–8 — Door Staff

their manual →
  1. 1
    Read the red screen out loud to the guest before doing anything else. The large text is the human wording of the reason code; the reason code itself is in the Recent scans row underneath.
    /scanner access
    Expected result One of: Unreadable Pass, Invalid Signature, Unknown Pass, Code Expired, Pass Revoked, Ticket Resold, Listed For Sale — Suspended, Ticket Not Paid, Event Not Active, Wrong Zone, Already Scanned, Not Checked In.
    Watch out for Saying the reason out loud is not a courtesy, it is the fastest triage you have. Half of these the guest can fix on their own phone in ten seconds once they know which one it is.
  2. 2
    Handle the three that mean 'the thing you scanned was not a real credential'. Unreadable Pass means the string was not a payload at all — usually a partial paste or a scuffed scan; try again. Invalid Signature means it was payload-shaped but the HMAC did not verify — a forgery, or a credential signed with a key that has since been marked compromised. Unknown Pass means the serial simply is not in the database.
    Expected result Unreadable Pass resolves on a clean re-scan. The other two do not.
    Watch out for Invalid Signature is the one to escalate rather than retry. A whole run of them at once usually means an admin marked a signing key compromised mid-shift, not that you have a queue of counterfeiters.
  3. 3
    Handle Code Expired. This only happens on the QR path and it means the eight-digit code was outside the fifteen-second step and its one-step tolerance. Ask the guest to open their live wallet page rather than a saved image, and scan again.
    Expected result A fresh code scans green.
    Watch out for There is a second, subtler cause: the same code already scanned green on a DIFFERENT reader within the same time step. That is a deliberate cross-reader replay guard and it surfaces as Already Scanned, not Code Expired — two people cannot walk two doors on one code.
  4. 4
    Handle the three ownership reasons. Pass Revoked means this credential was killed — refunded, resold, or revoked by an admin — and revocation is terminal, so it will never work again. Ticket Resold is what an old NFC payload shows after a transfer: the ticket is fine, but it belongs to somebody else now. Listed For Sale — Suspended means the guest currently has this exact ticket on the resale exchange.
    Expected result The guest recognises which of the three applies to them.
    Watch out for The suspended one has a real fix and is worth knowing: listing a ticket suspends its pass on purpose — you cannot sell it and also walk in on it. If the guest delists it, the same pass is restored and scans green. Nobody at the door can do that for them; it is their own listing to pull.
  5. 5
    Handle Ticket Not Paid. The pass is fine, but the order behind it is not in a paid state — nearly always a full refund, occasionally a checkout that never completed.
    Expected result A red that a manager can confirm from the order.
    Watch out for Partially refunded orders still scan green. If you see Ticket Not Paid, the whole order went, not one ticket of four.
  6. 6
    Handle Event Not Active. The credential is valid but you are outside the event's door window. A pass scans inside the window when the event is announced, on sale or sold out, and any time while it is in progress; before doors open or after teardown it goes red.
    Expected result A red that resolves by itself once doors open.
    Watch out for This is the most common red of the evening and it is almost always a clock question, not a ticket question. Check the event's doors-open time before you start telling people their ticket is broken.
  7. 7
    Handle Wrong Zone and the two anti-passback reasons, which are covered in detail in the scanner-screen workflow: wrong reader for the ticket, already inside, or trying to leave without having entered.
    Expected result You can name which of the three you are looking at without thinking.
    Watch out for Wrong Zone on a credential that worked ten minutes ago on the same reader means somebody presented a re-signed payload — the zones on the pass row are authoritative and a payload claiming different ones is rejected.
  8. 8
    When the guest genuinely should come in and the pass says otherwise, stop scanning and go to the guest list. Search their name — comps, plus-ones and prior overrides all live there.
    /scanner/guestlist guestlist
    Expected result Their entry, or nothing.
    Watch out for If there is nothing, the next step is a manager and a recorded walk-in override, not a decision by you. The scanner has no override control by design.

Steps 9 — Member

their manual →

Is the guest at the window, and usually holds the fix on their own phone.

  1. 9
    As the guest, do the two things that fix most reds without anybody's help: open your live wallet page rather than a saved screenshot, and check the status shown on the pass itself.
    Expected result Your passes with status active, suspended or revoked, and a QR that is regenerating rather than frozen.
    Watch out for A suspended pass means you listed that ticket on the exchange. Delist it and the same pass is restored — nobody at the door can do that for you, and arguing about it at the window costs you your place in the queue.

Steps 10–12 — Venue Manager

their manual →

Looks the scan up in the log and decides whether the guest gets in another way.

  1. 10
    As the manager, look the scan up rather than taking anyone's word for it. Filter by event, by reader, or by result and reason.
    Expected result The exact scan with its reason code, method, direction, latency and reader.
    Watch out for Scan logs are the record you will be asked for weeks later about capacity or comps. They are written on every scan, red ones included — a red is evidence, not a discarded attempt.
  2. 11
    If the reason was Pass Revoked or Ticket Resold, check the Revocation list panel to see the serial, the reason and when it happened.
    /admin/access access
    Expected result The blacklisted serial with reason resold, refunded, admin_revoke or superseded.
    Watch out for You cannot un-revoke anything from here, and neither can an admin — revocation is terminal by design. The guest's route back in is a new credential, not a repaired old one.
  3. 12
    If the reason was Ticket Not Paid, open the order to confirm the refund and see who processed it.
    /admin/orders/{order_id} payments
    Expected result The order with its status and refund history.
    Watch out for You can read this page but you cannot issue a refund from it — refunds are admin-only. Confirming what happened is your job; reversing it is not.

Steps 13–15 — Admin

their manual →

Owns the tools that reproduce a red on demand and the keys behind every signature.

  1. 13
    As an admin, reproduce a red deliberately when you are training staff or chasing a report. Simulate a scan against a ticket and a reader, optionally tampering the signature or the zone.
    Expected result A real scan envelope and a real scan_logs row, produced without a phone.
    Watch out for This writes genuine log rows. Do it on a demo event, or your training session shows up in the night's door numbers.
  2. 14
    When somebody insists their QR is correct, use the code oracle to see the previous, current and next valid codes for that ticket.
    /debug/access/totp/{ticket_id} access
    Expected result Three codes and the step boundaries.
    Watch out for If the guest's code matches the previous or next step it was a timing issue and a re-scan fixes it. If it matches nothing, they are not holding the credential they think they are.
  3. 15
    For a run of Invalid Signature reds, check the Signing keys panel: rotation keeps old payloads working because retired keys still verify, but a key marked compromised fails every payload signed with it.
    /admin/access access
    Expected result Keys with status active, retired or compromised.
    Watch out for Marking a key compromised is a venue-wide event. Every wallet pass signed under it dies at once — never do it mid-shift to test something.

Use your membership card at the bar (VIP)

Mint a membership card, see what VIP adds to it, and understand what a bar scan does and does not do.

Owned by VIP Member · 8 steps · about 12 minutes

Why this exists

A membership card is a pass with no ticket behind it. It exists so that being a member is itself a credential: it opens the zones your groups map to, plus the bar pseudo-zone, and it is re-issued automatically whenever your groups change.

This is where VIP status stops being a label. The zone map gives an ordinary member the general zone and a VIP member the general zone and Zone B — so the same card, scanned at the same reader, opens a room for one person and not the other. Nothing about the scan is a judgement call by staff.

The bar reader is a special case in the scan pipeline: it returns green with your credit summary attached — prepaid balance and remaining tab headroom — and it deliberately does not record a check-in. It is a look-up, not a turnstile, and above all it charges nothing. The scan tells the bartender what you are good for; the actual spend is a separate transaction on the ledger. Read that twice before you demo it, because "the scan took my money" is a misconception that spreads fast.

The plumbing behind the re-issue is worth knowing: any grant or revoke of a zone-carrying group fires a pass refresh, which revokes the old card and mints a new one with your current zones. That is why losing VIP takes Zone B away immediately rather than at the end of the month.

Corrected 2026-08-24 (end2end). This section used to warn of "one documented gap — editing the zone map for a whole group does not refresh cards that are already issued". Plan 21 closed it. set_group_zones now fans a refresh out to existing holders, queued rather than run inline so that re-zoning a four-thousand-member tier does not block the admin's request. A card in a wallet catches up on the next sweep, not on the next re-issue.

Before you start

  • A member or VIP session.
  • For the scan step: an employee session (door staff, venue manager or admin) or a provisioned bar reader.
  • An admin session for the tab step.

Practise with

PersonaEmailPasswordNote
vip_membervip@club.test vip123seeded membership card, $250 tab limit with $80 used
membermember@club.test member123an ordinary card, for the side-by-side zone comparison
door_staffdoor@club.test door123works the bar reader
adminadmin@club.test admin123sets and freezes tab limits

Steps 1–2 — Member

their manual →

Holds the same card without Zone B — the contrast is the lesson.

  1. 1
    As an ordinary member with no card yet, open your wallet and press Get my membership card.
    Expected result A card appears with the general zone and the bar zone.
    Watch out for Corrected 2026-08-24 (end2end): this used to say the no-script fallback posted to a route that does not exist. It posts to the real endpoint and carries a CSRF field, so it works without script. What it does not do is come back to a page — the endpoint answers with JSON, so a browser with script blocked lands on the raw record instead of the wallet. Use the button.
  2. 2
    Call the issuing endpoint yourself to see that it is idempotent.
    Expected result The same live card returned again, not a second one.
    Watch out for One live membership card per user is a database constraint, not a convention. You cannot accumulate cards.

Steps 3–4 — VIP Member

their manual →
  1. 3
    Now do the same as the VIP account and compare the zone list on the two cards.
    Expected result The VIP card carries Zone B in addition to the general zone and the bar zone.
    Watch out for The zones are snapshotted into the card when it is minted. This is why the platform re-mints on every group change instead of computing zones at scan time.
  2. 4
    Pull the rotating code for the card, exactly as for a ticket pass.
    /api/access/passes/{pass_id}/qr access
    Expected result A payload with the seconds remaining on the current 15-second step.

Steps 5 — Door Staff

their manual →

Scans the card at the bar and reads the credit summary off the verdict.

  1. 5
    At the bar reader, scan the card.
    /api/access/scan POST access
    Expected result Green, the holder's name, and a credit block showing prepaid balance and remaining tab headroom.
    Watch out for This scan records no check-in and moves no money. It authorises; it does not charge. Serving the drink and charging the tab are separate acts, and the second one is not this endpoint.

Steps 6 — VIP Member

their manual →
  1. 6
    Open your credit page and confirm the numbers the bartender just saw.
    /my/credit ledger
    Expected result Prepaid, earned, and the tab with its limit, used and headroom.
    Watch out for The tab is a limit on what you may owe, not a balance you hold. Headroom shrinking is you borrowing more, not you spending savings.

Steps 7–8 — Admin

their manual →

Owns the tab limit and its frozen state; VIP status alone creates no headroom.

  1. 7
    As an admin, set the VIP's tab limit — or freeze it.
    /api/admin/credit/users/{user_id}/tab PUT ledger
    Expected result The tab row with its new limit or status.
    Watch out for Freezing stops further spending; it does not forgive the balance. Revoking VIP status freezes the tab automatically for the same reason, and the money stays owed.
  2. 8
    Revoke the VIP grant on a throwaway account and then look at that account's wallet.
    /admin/users/{user_id} rbac
    Expected result The old card is revoked and superseded by a fresh card without Zone B.
    Watch out for Corrected 2026-08-24 (end2end): the refresh fires on grant and revoke, AND on a zone-map edit — plan 21 added the fan-out that this line used to say was missing. The fan-out is queued, so a card in a wallet catches up on the next sweep rather than the instant an admin saves the map.

Run the door offline

Pull the bundle before doors, queue scans on the device, and sync the batch back afterwards.

Owned by Venue Manager · 11 steps · about 20 minutes

Why this exists

Offline is a first-class mode here, not a degraded fallback. The assumption behind the design is that a warehouse door will lose the network at exactly the wrong moment, and that a queue of two hundred people is not the time to discover it. So the device is prepared before doors: a manager pulls a bundle containing the night's valid pass payloads, their time-code secrets, the revocation list, the verification key fingerprints and a snapshot of who is already checked in.

That bundle is the reason offline sync has a stricter permission than scanning itself. Anyone who can scan can scan; but the bundle is replayable credentials for the entire venue, so pulling it needs a venue_manager-or-above session naming a reader, or the reader's own provisioned device token. A plain door_staff session gets a 403, and the scanner page hides the control from them rather than teasing it — they see a badge reading bundle: manager only. That is not distrust of the person on the door, it is a decision about which device a venue's whole credential set is allowed to land on.

The second half is the sync. Offline scans are queued in the browser with a client-generated id, and pushed back as a batch of up to five hundred. The server trusts the results in that batch — it does not re-validate them, because the device was the thing that made the decision — but it replays the check-in state changes in scanned-at order and dedupes on the client id. So syncing twice is safe, and a scan that arrives late still lands in the right place in the night's timeline.

The trap, and you must teach it explicitly: the built scanner queues every offline scan as green / ok. It does not validate anything locally against the bundle. Offline mode on this build means 'let everyone in and record it', not 'validate against the bundle'. That is a deliberate simplification of the reference UI, and it means offline mode is a decision about crowd flow, not a security-neutral toggle.

Before you start

  • A venue manager or admin session for the bundle (door staff cannot pull it).
  • An event in announced, on_sale, sold_out or in_progress with issued passes.
  • A reader selected on the scanner — the sync endpoints require one.

Practise with

PersonaEmailPasswordNote
venue_managermanager@club.test manager123shift lead: implies door_staff, plus intake, sales, guest list, marketing
door_staffdoor@club.test door123door shift account: scanner + guest list, nothing else
adminadmin@club.test admin123the only account that can touch money, identity and contracts

Steps 1–4 — Venue Manager

their manual →
  1. 1
    Well before doors, sign in on the door device as the manager, pick the reader, and pick the event in the Event (for door bundle) dropdown.
    /scanner access
    Expected result Both dropdowns set, and a Download door bundle button — visible because you are a manager.
    Watch out for If you see a badge reading bundle: manager only instead of the button, you are signed in as door staff. Swap the session before the queue starts, not during it.
  2. 2
    Press Download door bundle. The page stores it in the device's local storage and the status badge changes to show how many passes came down.
    Expected result A badge reading bundle: N passes.
    Watch out for The bundle never contains the raw signing key — HMAC is symmetric, so publishing it would let the device mint credentials. It ships key fingerprints only, which are enough to notice a rotation and nothing else.
  3. 3
    Confirm the live push feed is running: the badge next to the reader picker should read push: live with a cursor number. This is the long-poll that tells a device about revocations and check-ins that happened elsewhere.
    Expected result push: live @ some number, refreshing every few seconds.
    Watch out for The push feed needs the same manager-or-token permission as the bundle. On a door_staff session it reads push: manager only and never polls — so a door staff device will not learn about a mid-shift revocation on its own.
  4. 4
    Brief the door before you hand the device over: while Offline mode is ticked, every scan is accepted and queued. Nobody is turned away by the device.
    Expected result The person on the door understands they are recording, not validating.
    Watch out for This is the single most important sentence in this workflow. Do not leave Offline mode ticked because it 'feels faster' — you have turned the door into a counter.

Steps 5–7 — Door Staff

their manual →

Actually works the queue in offline mode and presses Sync now when the network returns.

  1. 5
    When the network drops, tick Offline mode and keep scanning. The queue counter next to it climbs with every scan.
    /scanner access
    Expected result Each scan shows a green panel reading Queued (offline) with queued offline in place of a latency figure.
    Watch out for Queued (offline) is not a verdict. It means 'stored on this phone', nothing more. Watch the queue counter — if it stops climbing, the browser storage has a problem and you are now admitting people with no record at all.
  2. 6
    As soon as the network returns, untick Offline mode and press Sync now.
    Expected result The status badge changes to a sync line with the accepted count and the duplicate count.
    Watch out for Do not clear the browser data, close the tab into oblivion, or hand the device to the next shift before syncing. The queue lives in that browser and nowhere else.
  3. 7
    Understand what a duplicate means: every queued scan carries a client-generated id, and the server skips any id it has already stored. Pressing Sync now twice is harmless.
    Expected result Duplicates counted separately from accepted, and no double entries.
    Watch out for A scan queued without a client id is skipped entirely and reported as a conflict. If accepted plus duplicates does not equal what you scanned, say so — the difference is people you admitted with no log row.

Steps 8–9 — Venue Manager

their manual →
  1. 8
    After the sync, pull the scan log for the event and look at the offline flag column to see which rows arrived by batch.
    Expected result Synced rows flagged as offline, carrying their original scanned-at time rather than the sync time.
    Watch out for Batch scans keep the time they happened, which is why the door timeline still makes sense afterwards. If you see a wall of identical timestamps, someone queued without a real clock.
  2. 9
    Check the Readers table's cache column — push lag and last bundle time — to confirm the device really did sync and is not quietly stale.
    /admin/access access
    Expected result A recent last-sync time against that reader.
    Watch out for Conflicts reported by a batch are worth reading, not dismissing. The common one is a serial that was already checked in, which the server records without double-counting the entry — usually two devices scanning one queue.

Steps 10–11 — Admin

their manual →

Provisions the reader device tokens that make a device able to sync without any session at all.

  1. 10
    For a permanent door device, provision a reader properly instead of relying on a manager session. The creation response contains the device token, and that is the only time it is ever shown.
    /api/access/readers POST access
    Expected result A reader row and a one-time device token.
    Watch out for Copy the token then — the API never shows it again, and rotating is the supported way to get a working one. Do NOT treat that as the token being unrecoverable: it is still held in the reader row, so anyone with database or admin-console access can still obtain it. Rotate whenever a device leaves your control, not only when you have lost the copy.
  2. 11
    If a device is lost, rotate its token rather than deleting the reader.
    /api/access/readers/{reader_id}/rotate-token POST access
    Expected result A new token; the old one stops authenticating immediately.
    Watch out for Deleting the reader would orphan its scan history. Rotate, always — the reader row is the identity that the night's log hangs off.

Appendix A — Persona ↔ RBAC group

PersonaRBAC groupAuthenticatedScoped OwnsTakes part in
Customer (Guest) no no 4 2
Member member yes no 13 18
VIP Member vip_member yes no 2 7
Host / Promoter host yes yes 11 11
Door Staff door_staff yes no 5 14
Venue Manager venue_manager yes no 17 24
Admin admin yes no 24 43

Appendix B — Route index

Every application route this manual references, and the workflows that use it. A test asserts each of these resolves against the mounted router, so the manual cannot silently rot when a route moves.

RouteUsed by
DELETE /api/admin/resale/events/{event_id}/lock open-or-close-the-exchange
DELETE /api/guestlist/buckets/{bucket_id} allocate-comps-to-a-promoter
DELETE /api/reservations/{reservation_id} hold-a-checkout-reservation
DELETE /debug/mock-behaviors/{behavior_id} script-a-mock-failure
GET / browse-events-as-a-guest, buy-a-ticket
GET /account convert-an-application-into-an-event, grant-and-revoke-roles, build-a-role-from-capabilities, create-your-member-account, manage-your-account-and-sessions
GET /account/passes read-the-scanner-screen, diagnose-a-red-scan, map-groups-to-door-zones, self-cancel-a-ticket, add-your-ticket-to-your-wallet, use-your-membership-card-at-the-bar, resell-a-ticket-you-cant-use, buy-a-resale-ticket, claim-your-comp-invite, get-through-the-door
GET /admin find-your-way-around-as-staff, edit-any-row-with-admin-crud, find-anything-in-admin
GET /admin/access read-the-scanner-screen, diagnose-a-red-scan, run-the-door-offline, map-groups-to-door-zones, manage-your-account-and-sessions, add-your-ticket-to-your-wallet, get-through-the-door, scan-guests-at-the-door, find-anything-in-admin
GET /admin/annotations leave-feedback-on-the-app, run-the-feedback-backlog, preview-the-site-as-a-guest
GET /admin/annotations/export leave-feedback-on-the-app, run-the-feedback-backlog
GET /admin/annotations/{annotation_id} run-the-feedback-backlog, preview-the-site-as-a-guest
GET /admin/announcements get-into-a-member-only-drop
GET /admin/audit grant-and-revoke-roles, deactivate-a-user, create-your-member-account
GET /admin/capabilities build-a-role-from-capabilities
GET /admin/contract-riders author-a-rider-so-a-host-can-ask-for-it
GET /admin/contract-riders/new author-a-rider-so-a-host-can-ask-for-it
GET /admin/contract-riders/recent author-a-rider-so-a-host-can-ask-for-it
GET /admin/contract-riders/{rider_key} author-a-rider-so-a-host-can-ask-for-it
GET /admin/contracts review-the-riders-approval-created, sign-and-countersign-a-contract, approve-a-host-application
GET /admin/contracts/{contract_id} review-the-riders-approval-created, sign-and-countersign-a-contract, create-and-launch-an-event, approve-a-host-application
GET /admin/credit/users/{user_id} post-a-house-credit-adjustment, pay-with-house-credit
GET /admin/data edit-any-row-with-admin-crud
GET /admin/data/audit edit-any-row-with-admin-crud, delete-rows-safely
GET /admin/data/ledger_entries edit-any-row-with-admin-crud, read-the-trial-balance
GET /admin/data/qa_invariant_violations run-the-qa-invariant-sweep
GET /admin/data/sandbox_parents edit-any-row-with-admin-crud
GET /admin/data/{table}/new edit-any-row-with-admin-crud
GET /admin/data/{table}/{pk} edit-any-row-with-admin-crud, delete-rows-safely
GET /admin/events find-your-way-around-as-staff, monitor-live-sales-on-the-night, understand-your-events-tiers-and-releases
GET /admin/events/backload understand-your-events-tiers-and-releases
GET /admin/events/{event_id} monitor-live-sales-on-the-night, convert-an-application-into-an-event, grant-and-revoke-roles, get-into-a-member-only-drop, get-your-host-account-and-event-access, understand-your-events-tiers-and-releases, watch-your-event-sell-live, create-and-launch-an-event
GET /admin/events/{event_id}/record keep-the-permanent-event-record
GET /admin/events/{event_id}/record/documents/{doc_id} keep-the-permanent-event-record
GET /admin/events/{event_id}/refund-policy refund-a-ticket-or-order, self-cancel-a-ticket
GET /admin/groups find-your-way-around-as-staff, map-groups-to-door-zones, build-a-role-from-capabilities
GET /admin/groups/new build-a-role-from-capabilities
GET /admin/groups/{slug} build-a-role-from-capabilities
GET /admin/guestlist find-your-way-around-as-staff, allocate-comps-to-a-promoter
GET /admin/guestlist/{event_id} check-in-a-guest-from-the-list, issue-a-vip-walk-in-override, allocate-comps-to-a-promoter, revoke-a-comp, get-your-host-account-and-event-access, invite-a-guest-and-track-plus-ones
GET /admin/intake find-your-way-around-as-staff, convert-an-application-into-an-event, apply-to-host-an-event, build-your-run-of-show-backwards, create-and-launch-an-event, approve-a-host-application, run-the-staff-channel-on-live-telegram
GET /admin/intake/rider-requests author-a-rider-so-a-host-can-ask-for-it
GET /admin/intake/{app_id} fast-track-from-telegram, review-the-riders-approval-created, convert-an-application-into-an-event, apply-to-host-an-event, re-apply-with-autofill, respond-to-a-request-for-more-information, author-a-rider-so-a-host-can-ask-for-it, build-your-run-of-show-backwards, create-and-launch-an-event, approve-a-host-application, run-the-staff-channel-on-live-telegram
GET /admin/ledger read-the-trial-balance, reconcile-tax-accruals, top-up-your-house-credit, read-your-credit-statement, run-the-quarterly-tax-filing
GET /admin/ledger/transactions refund-a-ticket-or-order, read-the-trial-balance, process-a-payout-run, run-the-vip-tab-settlement, reconcile-tax-accruals, pay-with-house-credit, use-and-settle-your-vip-tab
GET /admin/ledger/transactions/{txn_id} post-a-house-credit-adjustment, read-the-trial-balance, top-up-your-house-credit
GET /admin/marketing find-your-way-around-as-staff, compose-and-publish-a-social-post, configure-marketing-channels-and-rules
GET /admin/marketing/events/{event_id} compose-and-publish-a-social-post, build-a-trigger-rule, configure-marketing-channels-and-rules
GET /admin/marketing/events/{event_id}/rules/new build-a-trigger-rule, configure-marketing-channels-and-rules
GET /admin/marketing/posts/new compose-and-publish-a-social-post
GET /admin/marketing/posts/{post_id} compose-and-publish-a-social-post, build-a-trigger-rule
GET /admin/marketing/reports trace-a-click-to-revenue, report-marketing-attribution
GET /admin/marketing/rules/{rule_id}/edit build-a-trigger-rule
GET /admin/orders monitor-live-sales-on-the-night, refund-a-ticket-or-order, watch-your-event-sell-live
GET /admin/orders/{order_id} diagnose-a-red-scan, monitor-live-sales-on-the-night, trace-a-click-to-revenue, refund-a-ticket-or-order, self-cancel-a-ticket, buy-a-ticket
GET /admin/payouts process-a-payout-run, get-paid-your-host-royalty
GET /admin/qa run-the-qa-invariant-sweep
GET /admin/records keep-the-permanent-event-record
GET /admin/records/devices keep-the-permanent-event-record
GET /admin/records/metrics keep-the-permanent-event-record
GET /admin/resale open-or-close-the-exchange
GET /admin/resale/{event_id} open-or-close-the-exchange, resell-a-ticket-you-cant-use, get-paid-your-host-royalty, buy-a-resale-ticket, understand-your-credit-and-the-exchange
GET /admin/tab-settlements script-a-mock-failure, run-the-vip-tab-settlement, use-and-settle-your-vip-tab
GET /admin/tax approve-a-filing-from-telegram, reconcile-tax-accruals, run-the-quarterly-tax-filing
GET /admin/tax/periods/{period_id} approve-a-filing-from-telegram, run-the-quarterly-tax-filing
GET /admin/tax/rates maintain-the-tax-rate-matrix
GET /admin/tax/settings approve-a-filing-from-telegram
GET /admin/telegram run-the-staff-channel-on-live-telegram
GET /admin/users grant-and-revoke-roles
GET /admin/users/{user_id} convert-an-application-into-an-event, grant-and-revoke-roles, deactivate-a-user, build-a-role-from-capabilities, create-your-member-account, manage-your-account-and-sessions, use-your-membership-card-at-the-bar, get-your-host-account-and-event-access
GET /admin/venues set-up-a-venue-and-its-door-zones
GET /admin/venues/new set-up-a-venue-and-its-door-zones
GET /admin/venues/{slug} set-up-a-venue-and-its-door-zones
GET /admin/venues/{venue_id}/layout draw-a-venue-map
GET /api/access/passes/mine add-your-ticket-to-your-wallet
GET /api/access/passes/{pass_id}/gpass add-your-ticket-to-your-wallet
GET /api/access/passes/{pass_id}/pkpass add-your-ticket-to-your-wallet, get-through-the-door
GET /api/access/passes/{pass_id}/qr read-the-scanner-screen, add-your-ticket-to-your-wallet, use-your-membership-card-at-the-bar, get-through-the-door
GET /api/access/scan-logs read-the-scanner-screen, diagnose-a-red-scan, run-the-door-offline, monitor-live-sales-on-the-night, get-through-the-door, scan-guests-at-the-door
GET /api/access/sync/bundle run-the-door-offline, scan-guests-at-the-door
GET /api/access/sync/push run-the-door-offline
GET /api/access/tickets/{ticket_id}/secret add-your-ticket-to-your-wallet
GET /api/admin/data/{table}/rows/{pk}/dependencies delete-rows-safely
GET /api/admin/events/{event_id}/reservations monitor-live-sales-on-the-night, hold-a-checkout-reservation, watch-your-event-sell-live
GET /api/admin/events/{event_id}/sales monitor-live-sales-on-the-night, watch-your-event-sell-live
GET /api/admin/ledger/accounts read-the-trial-balance
GET /api/admin/ledger/transactions read-the-trial-balance
GET /api/admin/ledger/trial-balance read-the-trial-balance
GET /api/admin/ledger/users/{user_id} post-a-house-credit-adjustment
GET /api/admin/refunds refund-a-ticket-or-order
GET /api/admin/resale/settlements get-paid-your-host-royalty
GET /api/annotations/list run-the-feedback-backlog
GET /api/annotations/pages run-the-feedback-backlog
GET /api/contracts/riders/selectable author-a-rider-so-a-host-can-ask-for-it
GET /api/credit/balance pay-with-house-credit, top-up-your-house-credit, read-your-credit-statement, get-paid-your-host-royalty, use-and-settle-your-vip-tab
GET /api/credit/statement read-your-credit-statement
GET /api/events/{event_id}/availability browse-events-as-a-guest, get-into-a-member-only-drop, understand-your-events-tiers-and-releases
GET /api/events/{event_id}/refund-policy self-cancel-a-ticket
GET /api/events/{event_id}/zones set-up-a-venue-and-its-door-zones
GET /api/guestlist/door/{event_id}/search check-in-a-guest-from-the-list, allocate-comps-to-a-promoter, claim-your-comp-invite
GET /api/guestlist/events/{event_id}/buckets allocate-comps-to-a-promoter, revoke-a-comp, invite-a-guest-and-track-plus-ones
GET /api/guestlist/events/{event_id}/entries invite-a-guest-and-track-plus-ones
GET /api/guestlist/events/{event_id}/overrides issue-a-vip-walk-in-override
GET /api/guestlist/events/{event_id}/settings issue-a-vip-walk-in-override
GET /api/intake/public/applications/{app_id}/edit respond-to-a-request-for-more-information
GET /api/marketing/channels configure-marketing-channels-and-rules
GET /api/marketing/links trace-a-click-to-revenue, report-marketing-attribution
GET /api/marketing/reports/channels trace-a-click-to-revenue, report-marketing-attribution
GET /api/marketing/reports/events/{event_id} trace-a-click-to-revenue, report-marketing-attribution
GET /api/marketing/rules/{rule_id}/firings build-a-trigger-rule, configure-marketing-channels-and-rules
GET /api/rbac/groups grant-and-revoke-roles
GET /api/rbac/matrix build-a-role-from-capabilities
GET /api/rbac/users/{user_id}/capabilities build-a-role-from-capabilities
GET /api/records/compare keep-the-permanent-event-record
GET /api/records/{event_id}/seal-check keep-the-permanent-event-record
GET /api/records/{event_id}/verify keep-the-permanent-event-record
GET /api/resale/events/{event_id} buy-a-resale-ticket
GET /api/resale/my/purchases buy-a-resale-ticket
GET /api/reservations/{reservation_id} hold-a-checkout-reservation
GET /api/tax/agencies maintain-the-tax-rate-matrix
GET /api/tax/documents/{doc_id} approve-a-filing-from-telegram
GET /api/tax/liability reconcile-tax-accruals, run-the-quarterly-tax-filing
GET /api/tax/periods reconcile-tax-accruals
GET /api/tax/quote maintain-the-tax-rate-matrix
GET /api/tax/rates maintain-the-tax-rate-matrix
GET /api/venues/{venue_id}/zones set-up-a-venue-and-its-door-zones
GET /apply/edit/{token} fast-track-from-telegram, respond-to-a-request-for-more-information, author-a-rider-so-a-host-can-ask-for-it, build-your-run-of-show-backwards, approve-a-host-application
GET /apply/thanks/{app_id} apply-to-host-an-event, re-apply-with-autofill, build-your-run-of-show-backwards, create-and-launch-an-event
GET /auth/me manage-your-account-and-sessions
GET /cart hold-a-checkout-reservation
GET /checkout/{order_id} hold-a-checkout-reservation, pay-with-house-credit, get-into-a-member-only-drop, buy-a-ticket, use-and-settle-your-vip-tab
GET /clips/{workflow_slug} leave-feedback-on-the-app
GET /contracts/{contract_id}/preview review-the-riders-approval-created
GET /contracts/{contract_id}/redlines review-the-riders-approval-created, sign-and-countersign-a-contract
GET /contracts/{contract_id}/sealed sign-and-countersign-a-contract
GET /debug use-the-debug-console
GET /debug/access/totp/{ticket_id} diagnose-a-red-scan
GET /debug/adminsuite/dependencies delete-rows-safely
GET /debug/adminsuite/policies edit-any-row-with-admin-crud
GET /debug/annotations/audit run-the-feedback-backlog
GET /debug/annotations/orphans run-the-feedback-backlog
GET /debug/audit use-the-debug-console, script-a-mock-failure
GET /debug/events/reservations/sweep-preview hold-a-checkout-reservation
GET /debug/events/{event_id}/state watch-your-event-sell-live
GET /debug/frontend/preview preview-the-site-as-a-guest
GET /debug/frontend/preview/gate preview-the-site-as-a-guest
GET /debug/guestlist/audit/{event_id} check-in-a-guest-from-the-list
GET /debug/intake/deeplinks run-the-staff-channel-on-live-telegram
GET /debug/intake/rate-limits re-apply-with-autofill
GET /debug/intake/timeline build-your-run-of-show-backwards
GET /debug/intake/timeline-render/{app_id} build-your-run-of-show-backwards
GET /debug/intake/tz build-your-run-of-show-backwards
GET /debug/ledger/integrity post-a-house-credit-adjustment, read-the-trial-balance
GET /debug/marketing/links/{short_code} trace-a-click-to-revenue, report-marketing-attribution
GET /debug/marketing/outbound configure-marketing-channels-and-rules
GET /debug/mock-behaviors script-a-mock-failure
GET /debug/outbound-calls fast-track-from-telegram, use-the-debug-console, script-a-mock-failure, approve-a-filing-from-telegram, apply-to-host-an-event, run-the-staff-channel-on-live-telegram
GET /debug/qa/health run-the-qa-invariant-sweep
GET /debug/qa/invariants run-the-qa-invariant-sweep
GET /debug/qa/invariants/runs run-the-qa-invariant-sweep
GET /debug/qa/journeys run-the-qa-invariant-sweep
GET /debug/rbac/graph build-a-role-from-capabilities
GET /debug/rbac/pass-refresh-queue build-a-role-from-capabilities
GET /debug/rbac/state grant-and-revoke-roles, deactivate-a-user
GET /debug/rbac/zones map-groups-to-door-zones
GET /debug/record/verify-all keep-the-permanent-event-record
GET /debug/record/{event_id} keep-the-permanent-event-record
GET /debug/resale/integrity/{event_id} open-or-close-the-exchange
GET /debug/resale/split-preview open-or-close-the-exchange, resell-a-ticket-you-cant-use
GET /debug/scheduler/runs use-the-debug-console
GET /debug/settings use-the-debug-console
GET /debug/state use-the-debug-console
GET /debug/tax/rate-resolution maintain-the-tax-rate-matrix
GET /debug/tax/reconcile reconcile-tax-accruals
GET /debug/telegram/webhook-info run-the-staff-channel-on-live-telegram
GET /debug/venues/zone-map set-up-a-venue-and-its-door-zones
GET /events build-a-role-from-capabilities, browse-events-as-a-guest, leave-feedback-on-the-app, preview-the-site-as-a-guest, buy-a-ticket
GET /events/{event_id} monitor-live-sales-on-the-night, build-a-trigger-rule, browse-events-as-a-guest, create-your-member-account, get-into-a-member-only-drop, sign-and-countersign-a-contract, get-your-host-account-and-event-access, understand-your-events-tiers-and-releases, watch-your-event-sell-live, preview-the-site-as-a-guest, create-and-launch-an-event, buy-a-ticket, understand-your-credit-and-the-exchange
GET /events/{event_id}/buy browse-events-as-a-guest
GET /events/{event_id}/checkout hold-a-checkout-reservation, get-into-a-member-only-drop, buy-a-ticket
GET /events/{event_id}/exchange open-or-close-the-exchange, buy-a-resale-ticket, get-paid-your-host-royalty, understand-your-credit-and-the-exchange
GET /exchange browse-events-as-a-guest, buy-a-resale-ticket
GET /guestlist/claim/{token} claim-your-comp-invite, invite-a-guest-and-track-plus-ones
GET /host/apply browse-events-as-a-guest, apply-to-host-an-event, re-apply-with-autofill, author-a-rider-so-a-host-can-ask-for-it, build-your-run-of-show-backwards, create-and-launch-an-event
GET /l/{short_code} trace-a-click-to-revenue, report-marketing-attribution
GET /login deactivate-a-user, browse-events-as-a-guest, create-your-member-account, get-your-host-account-and-event-access
GET /me/comps claim-your-comp-invite
GET /my create-your-member-account, get-into-a-member-only-drop, preview-the-site-as-a-guest
GET /my/annotations leave-feedback-on-the-app, run-the-feedback-backlog
GET /my/credit refund-a-ticket-or-order, run-the-vip-tab-settlement, open-or-close-the-exchange, pay-with-house-credit, self-cancel-a-ticket, use-your-membership-card-at-the-bar, resell-a-ticket-you-cant-use, top-up-your-house-credit, read-your-credit-statement, get-paid-your-host-royalty, buy-a-ticket, use-and-settle-your-vip-tab, understand-your-credit-and-the-exchange
GET /my/credit/payouts process-a-payout-run, get-paid-your-host-royalty, understand-your-credit-and-the-exchange
GET /my/credit/statement post-a-house-credit-adjustment, pay-with-house-credit, top-up-your-house-credit, read-your-credit-statement, get-paid-your-host-royalty, use-and-settle-your-vip-tab
GET /my/orders self-cancel-a-ticket
GET /my/orders/{order_id} refund-a-ticket-or-order, self-cancel-a-ticket
GET /my/tickets add-your-ticket-to-your-wallet, resell-a-ticket-you-cant-use, buy-a-ticket, get-through-the-door, understand-your-credit-and-the-exchange
GET /register create-your-member-account, buy-a-ticket
GET /resale/my/listings resell-a-ticket-you-cant-use, understand-your-credit-and-the-exchange
GET /resale/sell/{ticket_id} open-or-close-the-exchange, resell-a-ticket-you-cant-use, understand-your-credit-and-the-exchange
GET /scanner find-your-way-around-as-staff, read-the-scanner-screen, diagnose-a-red-scan, run-the-door-offline, revoke-a-comp, grant-and-revoke-roles, map-groups-to-door-zones, deactivate-a-user, get-through-the-door, scan-guests-at-the-door
GET /scanner/guestlist find-your-way-around-as-staff, diagnose-a-red-scan, check-in-a-guest-from-the-list, issue-a-vip-walk-in-override, invite-a-guest-and-track-plus-ones, scan-guests-at-the-door
GET /sign/{token} sign-and-countersign-a-contract, create-and-launch-an-event
GET /sign/{token}/status sign-and-countersign-a-contract
GET /training/role/host_promoter find-your-way-around-as-staff
GET /ui/preview preview-the-site-as-a-guest
GET /venues/{venue_id}/map draw-a-venue-map
PATCH /api/admin/data/{table}/rows/{pk} edit-any-row-with-admin-crud
PATCH /api/contracts/riders/{rider_key} author-a-rider-so-a-host-can-ask-for-it
PATCH /api/guestlist/buckets/{bucket_id} allocate-comps-to-a-promoter
PATCH /api/marketing/channels/{platform} compose-and-publish-a-social-post, configure-marketing-channels-and-rules
PATCH /api/marketing/posts/{post_id} compose-and-publish-a-social-post
PATCH /api/tax/rates/{rate_id} maintain-the-tax-rate-matrix
PATCH /api/tiers/{tier_id} understand-your-events-tiers-and-releases
PATCH /api/venues/{venue_id}/zones/{zone_id} set-up-a-venue-and-its-door-zones
PATCH /contracts/{contract_id}/sections/{section_id} review-the-riders-approval-created
PATCH /contracts/{contract_id}/variables review-the-riders-approval-created
POST /admin/annotations/{annotation_id} leave-feedback-on-the-app, run-the-feedback-backlog
POST /admin/annotations/{annotation_id}/delete run-the-feedback-backlog
POST /admin/annotations/{annotation_id}/reply run-the-feedback-backlog
POST /admin/annotations/{annotation_id}/restore run-the-feedback-backlog
POST /admin/announcements get-into-a-member-only-drop
POST /admin/venues/new set-up-a-venue-and-its-door-zones
POST /api/access/membership-card use-your-membership-card-at-the-bar
POST /api/access/readers run-the-door-offline, scan-guests-at-the-door
POST /api/access/readers/{reader_id}/rotate-token run-the-door-offline
POST /api/access/scan read-the-scanner-screen, use-your-membership-card-at-the-bar, resell-a-ticket-you-cant-use, buy-a-resale-ticket, get-through-the-door, scan-guests-at-the-door
POST /api/access/scan/batch run-the-door-offline
POST /api/admin/credit/users/{user_id}/kyc/revoke process-a-payout-run
POST /api/admin/data/{table}/delete delete-rows-safely
POST /api/admin/data/{table}/rows/{pk}/restore delete-rows-safely
POST /api/admin/events/{event_id}/refund-all refund-a-ticket-or-order
POST /api/admin/ledger/adjustments post-a-house-credit-adjustment, read-your-credit-statement
POST /api/admin/orders/{order_id}/refunds refund-a-ticket-or-order
POST /api/admin/payouts/{payout_id}/process process-a-payout-run, get-paid-your-host-royalty
POST /api/admin/resale/events/{event_id}/lock open-or-close-the-exchange
POST /api/admin/tab-settlements/run run-the-vip-tab-settlement, use-and-settle-your-vip-tab
POST /api/admin/tab-settlements/{settlement_id}/retry run-the-vip-tab-settlement
POST /api/admin/tab-settlements/{settlement_id}/waive run-the-vip-tab-settlement
POST /api/annotations leave-feedback-on-the-app, run-the-feedback-backlog, preview-the-site-as-a-guest
POST /api/annotations/mode leave-feedback-on-the-app
POST /api/cart/items hold-a-checkout-reservation, buy-a-ticket
POST /api/checkout hold-a-checkout-reservation, buy-a-ticket
POST /api/contracts/rider-versions/{template_id}/approve author-a-rider-so-a-host-can-ask-for-it
POST /api/contracts/rider-versions/{template_id}/reject author-a-rider-so-a-host-can-ask-for-it
POST /api/contracts/rider-versions/{template_id}/submit author-a-rider-so-a-host-can-ask-for-it
POST /api/contracts/riders/{rider_key}/promote author-a-rider-so-a-host-can-ask-for-it
POST /api/credit/kyc process-a-payout-run, get-paid-your-host-royalty
POST /api/credit/payouts process-a-payout-run, get-paid-your-host-royalty
POST /api/credit/topup top-up-your-house-credit, understand-your-credit-and-the-exchange
POST /api/events/{event_id}/capacity-override set-up-a-venue-and-its-door-zones
POST /api/events/{event_id}/reservations hold-a-checkout-reservation
POST /api/events/{event_id}/tiers get-into-a-member-only-drop, understand-your-events-tiers-and-releases
POST /api/events/{event_id}/transition monitor-live-sales-on-the-night, understand-your-events-tiers-and-releases, create-and-launch-an-event
POST /api/events/{event_id}/zones set-up-a-venue-and-its-door-zones
POST /api/events/{event_id}/zones/{zone_ref}/activate set-up-a-venue-and-its-door-zones
POST /api/events/{event_id}/zones/{zone_ref}/retire set-up-a-venue-and-its-door-zones
POST /api/guestlist/buckets/{bucket_id}/entries allocate-comps-to-a-promoter, invite-a-guest-and-track-plus-ones
POST /api/guestlist/claim/{token} claim-your-comp-invite, invite-a-guest-and-track-plus-ones
POST /api/guestlist/claim/{token}/plus-ones claim-your-comp-invite
POST /api/guestlist/door/{event_id}/entries/{entry_id}/checkin check-in-a-guest-from-the-list, claim-your-comp-invite, invite-a-guest-and-track-plus-ones, scan-guests-at-the-door
POST /api/guestlist/door/{event_id}/entries/{entry_id}/issue-and-checkin check-in-a-guest-from-the-list
POST /api/guestlist/door/{event_id}/override issue-a-vip-walk-in-override
POST /api/guestlist/entries/{entry_id}/resend revoke-a-comp, invite-a-guest-and-track-plus-ones
POST /api/guestlist/entries/{entry_id}/revoke revoke-a-comp, invite-a-guest-and-track-plus-ones
POST /api/guestlist/events/{event_id}/buckets allocate-comps-to-a-promoter, invite-a-guest-and-track-plus-ones
POST /api/guestlist/my/entries/{entry_id}/claim claim-your-comp-invite
POST /api/intake/applications/{app_id}/approve fast-track-from-telegram, respond-to-a-request-for-more-information, create-and-launch-an-event, approve-a-host-application
POST /api/intake/applications/{app_id}/author-rider author-a-rider-so-a-host-can-ask-for-it
POST /api/intake/applications/{app_id}/convert convert-an-application-into-an-event, get-your-host-account-and-event-access, create-and-launch-an-event
POST /api/intake/applications/{app_id}/decline fast-track-from-telegram
POST /api/intake/applications/{app_id}/notes fast-track-from-telegram
POST /api/intake/applications/{app_id}/recompute-viability fast-track-from-telegram
POST /api/intake/applications/{app_id}/request-info respond-to-a-request-for-more-information, approve-a-host-application
POST /api/intake/public/applications apply-to-host-an-event, re-apply-with-autofill, author-a-rider-so-a-host-can-ask-for-it, build-your-run-of-show-backwards, create-and-launch-an-event, run-the-staff-channel-on-live-telegram
POST /api/intake/public/prefill/start re-apply-with-autofill
POST /api/intake/public/prefill/verify re-apply-with-autofill
POST /api/intake/public/rider-requests author-a-rider-so-a-host-can-ask-for-it
POST /api/intake/public/timeline/preview build-your-run-of-show-backwards
POST /api/intake/rider-requests/{request_id}/author author-a-rider-so-a-host-can-ask-for-it
POST /api/intake/rider-requests/{request_id}/decline author-a-rider-so-a-host-can-ask-for-it
POST /api/intake/rider-requests/{request_id}/triage author-a-rider-so-a-host-can-ask-for-it
POST /api/marketing/events/{event_id}/rules build-a-trigger-rule, configure-marketing-channels-and-rules
POST /api/marketing/posts compose-and-publish-a-social-post
POST /api/marketing/posts/{post_id}/cancel compose-and-publish-a-social-post
POST /api/marketing/posts/{post_id}/retry compose-and-publish-a-social-post
POST /api/orders/{order_id}/cancel hold-a-checkout-reservation, self-cancel-a-ticket
POST /api/orders/{order_id}/pay hold-a-checkout-reservation, pay-with-house-credit, buy-a-ticket
POST /api/rbac/groups build-a-role-from-capabilities
POST /api/rbac/tiers/reorder build-a-role-from-capabilities
POST /api/rbac/users/{user_id}/deactivate deactivate-a-user
POST /api/rbac/users/{user_id}/groups grant-and-revoke-roles, build-a-role-from-capabilities, get-your-host-account-and-event-access
POST /api/rbac/users/{user_id}/groups/{grant_id}/revoke grant-and-revoke-roles
POST /api/rbac/users/{user_id}/reactivate deactivate-a-user
POST /api/rbac/users/{user_id}/sessions/revoke-all deactivate-a-user
POST /api/record/devices keep-the-permanent-event-record
POST /api/record/devices/{device_id}/rotate-token keep-the-permanent-event-record
POST /api/record/ingest keep-the-permanent-event-record
POST /api/record/metrics keep-the-permanent-event-record
POST /api/records/{event_id}/amendments keep-the-permanent-event-record
POST /api/records/{event_id}/measurements keep-the-permanent-event-record
POST /api/records/{event_id}/measurements/import keep-the-permanent-event-record
POST /api/records/{event_id}/open keep-the-permanent-event-record
POST /api/records/{event_id}/seal keep-the-permanent-event-record
POST /api/resale/listings resell-a-ticket-you-cant-use
POST /api/resale/listings/{listing_id}/purchase buy-a-resale-ticket
POST /api/tax/packages/{package_id}/approve run-the-quarterly-tax-filing
POST /api/tax/packages/{package_id}/retry-mail approve-a-filing-from-telegram
POST /api/tax/packages/{package_id}/send-approval approve-a-filing-from-telegram, run-the-quarterly-tax-filing
POST /api/tax/periods/{period_id}/regenerate reconcile-tax-accruals
POST /api/tax/rates maintain-the-tax-rate-matrix
POST /api/telegram/test-send run-the-staff-channel-on-live-telegram
POST /api/telegram/webhook/delete run-the-staff-channel-on-live-telegram
POST /api/telegram/webhook/register run-the-staff-channel-on-live-telegram
POST /api/venues/{venue_id}/archive set-up-a-venue-and-its-door-zones
POST /api/venues/{venue_id}/default set-up-a-venue-and-its-door-zones
POST /api/venues/{venue_id}/layout draw-a-venue-map
POST /api/venues/{venue_id}/shapes draw-a-venue-map
POST /api/venues/{venue_id}/zones set-up-a-venue-and-its-door-zones
POST /api/venues/{venue_id}/zones/{zone_id}/attach set-up-a-venue-and-its-door-zones
POST /auth/login get-your-host-account-and-event-access
POST /auth/password manage-your-account-and-sessions
POST /auth/register create-your-member-account, buy-a-ticket
POST /auth/sessions/{session_id}/revoke manage-your-account-and-sessions
POST /contracts/{contract_id}/countersign sign-and-countersign-a-contract, create-and-launch-an-event
POST /contracts/{contract_id}/lock review-the-riders-approval-created, sign-and-countersign-a-contract, create-and-launch-an-event
POST /contracts/{contract_id}/resync-riders review-the-riders-approval-created
POST /debug/access/simulate-scan diagnose-a-red-scan
POST /debug/adminsuite/sandbox/seed delete-rows-safely
POST /debug/adminsuite/sql edit-any-row-with-admin-crud
POST /debug/clock use-the-debug-console
POST /debug/db/reset use-the-debug-console
POST /debug/events/tiers/{tier_id}/force-cascade get-into-a-member-only-drop, understand-your-events-tiers-and-releases
POST /debug/guestlist/reset-override-cap/{event_id} issue-a-vip-walk-in-override
POST /debug/intake/press-button fast-track-from-telegram
POST /debug/intake/resend-telegram fast-track-from-telegram
POST /debug/ledger/attempt-mutation post-a-house-credit-adjustment
POST /debug/ledger/promote-payouts process-a-payout-run
POST /debug/ledger/run-tab-settlement script-a-mock-failure, run-the-vip-tab-settlement
POST /debug/ledger/simulate-ach process-a-payout-run
POST /debug/marketing/posts/{post_id}/force-publish compose-and-publish-a-social-post
POST /debug/marketing/rules/{rule_id}/force-fire build-a-trigger-rule, configure-marketing-channels-and-rules
POST /debug/marketing/rules/{rule_id}/reset build-a-trigger-rule, configure-marketing-channels-and-rules
POST /debug/marketing/simulate-click trace-a-click-to-revenue, report-marketing-attribution
POST /debug/marketing/simulate-conversion trace-a-click-to-revenue, report-marketing-attribution
POST /debug/marketing/tick build-a-trigger-rule, configure-marketing-channels-and-rules
POST /debug/mock-behaviors script-a-mock-failure
POST /debug/payments/expire-holds hold-a-checkout-reservation
POST /debug/qa/invariants/run run-the-qa-invariant-sweep, process-a-payout-run, reconcile-tax-accruals
POST /debug/rbac/simulate build-a-role-from-capabilities
POST /debug/scheduler/tick use-the-debug-console
POST /debug/tax/replay-telegram-callback approve-a-filing-from-telegram
POST /debug/tax/run-scheduler run-the-quarterly-tax-filing
POST /debug/tax/seed-accruals reconcile-tax-accruals
POST /debug/webhooks/simulate script-a-mock-failure
POST /debug/webhooks/{webhook_id}/replay script-a-mock-failure
POST /preview/enter preview-the-site-as-a-guest
POST /preview/exit preview-the-site-as-a-guest
POST /resale/listings/{listing_id}/delist resell-a-ticket-you-cant-use
POST /resale/sell/{ticket_id} resell-a-ticket-you-cant-use
POST /sign/{token} sign-and-countersign-a-contract, create-and-launch-an-event
POST /webhooks/telegram run-the-staff-channel-on-live-telegram
POST /webhooks/telegram/tax approve-a-filing-from-telegram
PUT /api/admin/credit/users/{user_id}/tab post-a-house-credit-adjustment, run-the-vip-tab-settlement, use-your-membership-card-at-the-bar, use-and-settle-your-vip-tab
PUT /api/admin/events/{event_id}/refund-policy refund-a-ticket-or-order, self-cancel-a-ticket
PUT /api/admin/resale/events/{event_id}/config open-or-close-the-exchange, resell-a-ticket-you-cant-use, get-paid-your-host-royalty
PUT /api/admin/resale/tiers/{tier_id}/mode open-or-close-the-exchange
PUT /api/intake/public/applications/{app_id} respond-to-a-request-for-more-information, build-your-run-of-show-backwards, approve-a-host-application
PUT /api/intake/settings apply-to-host-an-event
PUT /api/rbac/groups/{group_name}/zones map-groups-to-door-zones, build-a-role-from-capabilities
PUT /api/rbac/groups/{slug}/capabilities build-a-role-from-capabilities
PUT /api/rbac/groups/{slug}/implications build-a-role-from-capabilities
PUT /api/rbac/tiers/{slug}/benefits build-a-role-from-capabilities
PUT /api/tax/packages/{package_id}/allocations approve-a-filing-from-telegram, run-the-quarterly-tax-filing
PUT /api/tiers/{tier_id}/zones understand-your-events-tiers-and-releases
PUT /debug/settings/{key} use-the-debug-console

Appendix C — Demo accounts

Practice logins on the demo/test data set only. These render only outside production and only while training.show_demo_accounts is on.

PersonaEmailPasswordNote
Membermember@club.testmember123 A registered customer: buys, holds tickets, house credit and resale listings.
VIP Membervip@club.testvip123 A member with a house tab and Zone B access. Implies everything a member can do.
Host / Promoterhost@club.testhost123 An outside promoter running an event at the venue. Scoped to that one event.
Door Staffdoor@club.testdoor123 Works the door: scans credentials, searches the guest list, checks people in.
Venue Managermanager@club.testmanager123 Runs the floor and the calendar: intake review, live sales, the door, marketing.
Adminadmin@club.testadmin123 Owns money, identity and configuration. Implies every other group.