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:05:13Z · 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.

Money & House Credit

House credit, VIP tabs, payouts and the double-entry ledger behind them.

Use and settle your VIP tab

Spend against the house tab, read what you owe, and understand the monthly settlement.

Owned by VIP Member · 8 steps · about 15 minutes

Why this exists

The tab is the platform's most misunderstood feature, so the design intent matters more than the clicks here.

A tab is not a balance you own — it is a limit on what you may owe. Spending against it does not spend money; it records an obligation on a double-entry ledger. That is why your purchasing power is three separate things: prepaid credit you actually paid in, earned credit from resale you actually made, and tab headroom you have not paid at all. The platform spends them in exactly that order, so the tab is always the last resort.

Settlement is scheduled, not manual. On the first tick of a new month the platform settles the previous month for everyone carrying a balance: it sweeps your prepaid credit first, then charges your card for the remainder, and emails an invoice. A declined card marks the settlement failed and freezes the tab — the balance stays owed, because freezing a tab is a credit decision, not forgiveness.

The ledger behind all of this is append-only, enforced by database triggers. Nothing here is ever edited; corrections are new balanced entries.

Before you start

  • A VIP member session (vip_member implies member, so all member surfaces work too).
  • A tab with a limit — an admin sets it; VIP status alone does not create headroom.

Practise with

PersonaEmailPasswordNote
vip_membervip@club.test vip123the VIP persona for this workflow
adminadmin@club.test admin123sets tab limits and runs settlement

Steps 1–4 — VIP Member

their manual →
  1. 1
    Open your credit page and read the three numbers separately: prepaid, earned, and tab used against the limit.
    /my/credit ledger
    Expected result The balances plus your purchasing power.
    Watch out for Purchasing power adds tab headroom to real money. It is what you can spend, not what you have.
  2. 2
    Look at the same balances as the API returns them, so you can see the fields the pages are built from.
    Expected result Prepaid, earned, tab used, headroom and status.
  3. 3
    Pay for something with house credit and watch which pot it comes out of.
    /checkout/{order_id} payments
    Expected result Promo is consumed first, then prepaid, then earned, and only then does the tab absorb the remainder.
    Watch out for If the total is more than promo + prepaid + earned + headroom, the payment is refused outright — that sum is `purchasing_power_cents`, and it is the number the refusal is measured against. There is no partial settlement of an order.
  4. 4
    Read your statement as a running story: every top-up, purchase, resale payout and tab movement in order.
    Expected result A line per ledger movement with a running balance.
    Watch out for Nothing on this statement can be edited or deleted, by anyone. If something is wrong it is corrected with a new compensating entry, which will also appear here.

Steps 5–8 — Admin

their manual →

Sets the tab limit, runs settlement, and reads the ledger it posts to.

  1. 5
    As an admin, set or adjust a 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 a tab stops further spending; it does not clear what is owed. Revoking VIP status freezes it automatically for the same reason.
  2. 6
    Open the settlement dashboard and look at last month's run: who settled, who failed, who was waived.
    Expected result One row per user per period, with status.
  3. 7
    Run a settlement for a period manually when you need to — for a retry, or to see the mechanism in a demo.
    Expected result Per-user results: prepaid swept, card charged, invoice emailed.
    Watch out for It is idempotent per user and period. Running it twice does not charge twice — the unique constraint on user plus period is the guard, not the button being greyed out.
  4. 8
    Trace one settlement into the ledger and confirm it balances: every transaction has at least two entries and the debits equal the credits.
    Expected result The settlement's transaction with its entries.
    Watch out for Try to edit one and the database itself refuses. The append-only trigger is not a UI convention — it fires even from the admin data suite.

Understand your points and the exchange

Where your points come from, what they buy, when they can leave — and exactly how long your ticket is sellable.

Owned by Member · 13 steps · about 25 minutes

Why this exists

Your balance is counted in points, and what one point is worth is your club's own setting: a point is one cent on this demo, and a club may price it higher — lama.live sets 200, so a point there is two dollars. The books hold the same cash either way, and every screen that spends points prints the money beside them, which is the figure that never changes meaning. Behind the number are four kinds of points with four different rules: Promo (goodwill the club granted you — spendable, never cashable, and spent first precisely because it is the pot you can least afford to be left holding), Prepaid (points your money bought — yours, from the moment they land), Earned (proceeds from selling a ticket on the exchange — spendable instantly, but they can only leave the platform 24 hours after the event ends), and the VIP Tab (headroom against a limit — permission to go negative, not points you own). Spending order is always promo → prepaid → earned → tab; you cannot choose which pot pays. (The order is `post_sale_on_credit`'s, and its docstring states it: the manual said prepaid → earned → tab until 2026-08-26 and had never mentioned promo at all.) One footnote for the curious: the club's books keep this balance under its accounting name, house credit — members read points, accountants read credits, and both describe the same integer.

And the exchange: your ticket becomes sellable when its tier sells out (or when staff force the exchange open), not on any date. Listing suspends your pass on the spot. New listings are members-only for their first 48 hours, then public. Everything unsold is handed back shortly after doors — that lock, set per event, is the real deadline. There is no four-day rule, and the site's word for releasing a ticket is list; the reverse is delist.

Before you start

  • A member account (membership is free — register and you are one)
  • A published event with tickets on sale

Steps 1–4 — Member

their manual →
  1. 1
    Read your balance.
    /my/credit ledger
    Expected result Prepaid, earned and tab shown separately, in points, with purchasing power as the sum.
    Watch out for One number on the button, FOUR kinds of points behind it — promo, prepaid, earned and the tab. They do not behave the same.
  2. 2
    Load $20 from the wallet page (the form posts here).
    /api/credit/topup POST ledger
    Expected result A balanced transaction: card charged, prepaid points up — $20 buys 2,000 points.
    Watch out for Min $5, max $1000 — both settings. This is the only kind of points you can create yourself.
  3. 3
    Buy a ticket and choose points at checkout.
    /events/{event_id} events
    Expected result The order pays without touching a card; promo is spent first if you have any, then prepaid.
    Watch out for The spend order is promo → prepaid → earned → tab, always. You cannot pick the pot. Promo goes first on purpose: it is the pot you did not pay for, and the one you are most likely to lose. Note what cashing out actually reaches — a payout is drawn from earned alone, so promo AND prepaid can only ever be spent inside the platform. Two of the four pots never leave; only earned does.
  4. 4
    Try to sell the ticket before its tier sells out.
    /my/tickets frontend
    Expected result No Sell link — the exchange is not open.
    Watch out for The exchange opens on SELL-OUT, not on a date. This is the fact the feedback note got wrong: there is no four-day rule and no calendar involved.

Steps 5 — Admin

their manual →

Staff can force a tier's exchange open before it sells out; the automatic path is sell-out.

  1. 5
    Force the tier's exchange on.
    /admin/resale/{event_id} resale
    Expected result The tier flips to forced_on, and the change is audited.
    Watch out for Staff can open it early; the automatic path is sell-out. Either way it is a state, not a schedule.

Steps 6–11 — Member

their manual →
  1. 6
    List the ticket at a legal price.
    /resale/sell/{ticket_id} resale
    Expected result The floor/cap band, the fee preview, and your listing.
    Watch out for The band is anti-scalping and comes from the event's own config. Your pass is suspended the instant you list.
  2. 7
    Look at the ticket you just listed.
    /my/tickets frontend
    Expected result It shows SUSPENDED.
    Watch out for This answers "can I list it and still go if it does not sell?" — you can, but only after you delist.
  3. 8
    Find your own listing on the exchange.
    /events/{event_id}/exchange resale
    Expected result It sits under Member exchange with a countdown to public release.
    Watch out for Members get the first 48 hours; after that everyone sees it. The clock runs from when you listed — not from the show date.
  4. 9
    Delist it.
    Expected result Ticket back to issued, pass restored, with its ORIGINAL serial.
    Watch out for Delisting restores the original credential. A sale does not — the buyer gets a new one and yours dies.
  5. 10
    Re-list it, let another member buy it, then watch the money land.
    /my/credit ledger
    Expected result Earned points up by price minus the venue fee and any royalty.
    Watch out for This is Earned (2020), not Prepaid. They spend immediately — and pay out only after the post-event hold.
  6. 11
    Ask for a payout of the proceeds.
    Expected result Held until 24 hours after the event ends.
    Watch out for The hold exists because a cancelled show has to be able to claw the money back.

Steps 12 — VIP Member

their manual →

The tab is VIP-only headroom — the one kind of money you do not own.

  1. 12
    Look at the tab line as a VIP.
    /my/credit ledger
    Expected result Headroom against a limit, not a balance.
    Watch out for A tab is permission to go negative up to a limit — the only one of the four that is a DEBT rather than a balance you hold.

Steps 13 — Member

their manual →
  1. 13
    Come back after the post-doors lock.
    /events/{event_id}/exchange resale
    Expected result Active listings cancelled, tickets and passes handed back to their sellers.
    Watch out for The window closes shortly after doors — that is the real deadline, and the venue sets it per event.

Top up your house credit

Put money on your account with a card, and see it land as a balanced ledger posting.

Owned by Member · 7 steps · about 10 minutes

Why this exists

A top-up is the one moment a member deliberately turns money into house credit, and the platform treats it as exactly what it is: the venue taking your cash and recording a liability to you. Two entries, balanced, append-only, and a separate transaction for the processor's fee so the cost of taking the money is never hidden inside the revenue.

The part worth teaching is idempotency. The top-up endpoint accepts an idempotency key, and the key check, the card charge and the ledger postings all happen inside one write transaction. A duplicate request blocks until the first commits and then returns "not applied" without ever reaching the processor. That is why a double-tap on a bad connection cannot charge a card twice — the guarantee is structural, not a disabled button.

Know what prepaid credit is and is not. It is spendable on anything the platform sells and it is spent first, before earned credit and before a VIP tab. It is not cashable out: only earned balance from resale can be paid to a bank account. Money that comes in through this door does not go back out through that one.

Before you start

  • A member session.
  • An amount inside the configured limits: at least $5 and at most $1000 per top-up.

Practise with

PersonaEmailPasswordNote
membermember@club.test member123starts with nothing, so the first top-up is easy to see
adminadmin@club.test admin123reads the resulting transaction and the trial balance

Steps 1–5 — Member

their manual →
  1. 1
    Open your credit page and find the Top up card. Use a quick amount button or type an amount in cents.
    /my/credit ledger
    Expected result An amount field, a card token field, and an Add credit button.
    Watch out for Amounts are in cents everywhere in this platform. Typing 50 gets you fifty cents, not fifty dollars — and the minimum will reject it.
  2. 2
    Submit the top-up. In the demo the token tok_success succeeds and tok_decline is refused.
    /api/credit/topup POST ledger
    Expected result 201 with the transaction, the companion fee transaction, and your refreshed balances.
    Watch out for A declined card still records the attempt in the outbound call log. Failure is evidence here, not silence.
  3. 3
    Send the same request again with the same idempotency key to see the guard.
    /api/credit/topup POST ledger
    Expected result 200 with applied false, and no second charge.
    Watch out for Without a key, a second request is a second genuine top-up. The key is what makes a retry safe.
  4. 4
    Read your balances and identify which pot grew.
    Expected result Prepaid up by the amount; earned and tab untouched.
    Watch out for Purchasing power went up by the same amount, but they are not the same number. Purchasing power includes a VIP tab's headroom, which is not money.
  5. 5
    Find the top-up on your statement and read the running balance around it.
    Expected result A line for the top-up with the balance after it.
    Watch out for Nothing on this statement is editable by anyone, including admins. If it is wrong, the fix is a new compensating entry that also appears here.

Steps 6–7 — Admin

their manual →

Verifies the posting balances and can make corrective adjustments if it does not.

  1. 6
    As an admin, open the transaction and read both sides plus the separate fee posting.
    /admin/ledger/transactions/{txn_id} ledger
    Expected result Cash clearing debited, the member's house-credit liability credited, and a second transaction for the processing fee.
    Watch out for The fee is a real expense posted separately on purpose. Netting it into the top-up would make the member's liability wrong by the fee.
  2. 7
    Check the trial balance still nets to zero after the movement.
    /admin/ledger ledger
    Expected result A balanced trial balance.
    Watch out for If it ever does not, stop and investigate rather than adjusting to taste. An unbalanced ledger on this platform means a bug, not a rounding difference — the posting rules make imbalance impossible to store.

Get paid: your host royalty and its settlement

How a host actually earns on this platform, when the money is releasable, and how to cash it out.

Owned by Host / Promoter · 13 steps · about 20 minutes

Why this exists

Be clear about what this platform does and does not pay a host, because the paperwork and the plumbing are not the same thing.

Primary ticket sales post to the venue's revenue account. There is no automatic revenue share that pays a host out of primary ticket sales — whatever door split your contract describes is settled by the venue as a commercial matter. What the platform genuinely pays you, automatically and on a ledger, is the resale royalty: a per-event share, in basis points, of every ticket resold on the exchange for your event.

When a resale settles, one balanced transaction splits the sale price three ways — a venue fee, your royalty, and the seller's net — and your share is credited to your earned balance. The split is integer arithmetic with the remainder going to the seller, and the parts always sum to the price; the database enforces it.

Earned balance is spendable inside the club immediately, but cashing it out is held: every credit creates an earning lot whose release time is the event's completion plus a hold (24 hours by default), so a promoter cannot cash out on a show that has not happened yet. Above a yearly threshold on secondary sales you must complete KYC before any external cash-out; the platform stores only a hash of your tax id and its last four digits.

Both of those hold, and here is what makes them hold — checked 2026-08-27 against the running code, because both are the kind of promise that is easy to write and hard to keep. The split is a table constraint, not a rule the code remembers to apply: resale_settlements carries CHECK (price_cents = venue_fee_cents + host_royalty_cents + seller_net_cents), so a row that does not add up cannot be written at all. The hold looks weaker than it is: the release time falls back to immediately available when an event has no end time, and main_end is a nullable column — which reads like a way to cash out early. It is not, and the reason is four links away: a royalty needs a settlement, a settlement needs a sold ticket, a ticket needs an event that reached sale, and an event cannot be announced or put on sale without main_end. Take that last requirement away and the hold quietly stops applying.

Built behaviour that the paperwork does not tell you: ticking "Request resale royalty" on your application, and the royalty rider it puts in your contract, do not switch the money on. The royalty rate and the recipient are fields on the event's resale configuration, and an admin has to set them. If nobody sets host_royalty_bps and the host user on that config, every resale on your event pays you nothing — and the contract will still say you have a royalty. Check it before the on-sale, not after.

Before you start

  • A resale royalty configured on your event by an admin (rate in basis points plus the recipient user).
  • At least one completed resale on that event.
  • For an external cash-out: matured earning lots, and KYC once you pass the yearly threshold.

Practise with

PersonaEmailPasswordNote
host_promoterharper.host@demo.club harper-pass-123the seeded royalty recipient on demo-event-0001 (5 percent, 500 basis points)
adminadmin@club.test admin123sets the royalty and is the only persona that can release a payout

Steps 1–2 — Admin

their manual →

Sets the royalty rate and recipient, and is the only one who can process a payout.

  1. 1
    Set the event's resale configuration: the host royalty in basis points and the host user who receives it, alongside the venue fee, the price floor and cap, and any post-doors lock.
    /api/admin/resale/events/{event_id}/config PUT resale
    Expected result The updated config, with the change written to the append-only activation log showing the old and new values.
    Watch out for This is the switch that actually pays the host. The application flag and the contract rider do not set it, and the recipient is a plain user id — it is not derived from the scoped host grant, so it can legitimately point at somebody who is not the event's host.
  2. 2
    Open the event's exchange console to confirm the configuration, the per-tier modes, the live listings and the settlements.
    /admin/resale/{event_id} resale
    Expected result Config, tier modes, lock state, listings with seller emails, settlements and the audit log.
    Watch out for Admin only. Neither the host nor the venue manager can open this page.

Steps 3–9 — Host / Promoter

their manual →
  1. 3
    As the host, look at your event's exchange the way a buyer does: which tiers are trading and at what prices.
    /events/{event_id}/exchange resale
    Expected result Active listings per tier with the price floor and cap derived from face value.
    Watch out for Seller identity is never exposed on the exchange, to you or to anyone. You see prices, not people.
  2. 4
    Open your wallet and read the balances separately: prepaid credit, earned balance, and how much of the earned balance is actually eligible for payout.
    /my/credit ledger
    Expected result Your royalties sitting in the earned balance, with the payout-eligible figure lower until the hold matures.
    Watch out for Earned and payout-eligible are two different numbers and people confuse them constantly. Spending inside the club ignores the hold; cashing out does not.
  3. 5
    Pull the same balances as fields, including your year-to-date secondary sales and your KYC status.
    Expected result Prepaid, earned, payout-eligible earned, tab (if any), purchasing power, YTD secondary sales and KYC state.
    Watch out for The YTD figure is what the tax module reads for a 1099-K. Resales that were later unwound because an event was cancelled are excluded from it.
  4. 6
    Read the statement: every royalty arrives as its own line, tied to the settlement that created it, with a running balance.
    Expected result One line per ledger movement, newest first, filterable by date and account.
    Watch out for Nothing on this statement can ever be edited or deleted, by anyone — the ledger is append-only and enforced by database triggers, not by UI convention. A mistake is corrected with a new balanced entry, which also appears here.
  5. 7
    Submit KYC once your year-to-date secondary sales pass the threshold: legal name and tax id.
    /api/credit/kyc POST ledger
    Expected result A KYC record, and external cash-out unblocked.
    Watch out for Only a SHA-256 hash of the tax id and its last four digits are stored — the raw number is never persisted, and your user row carries only a masked form. Until it is submitted, a payout request above the threshold is refused with KYC_REQUIRED.
  6. 8
    Open the payouts page and check what is releasable and when the rest matures.
    Expected result Your payout history, the eligible amount and a countdown on held funds.
    Watch out for The hold is per earning lot and it is measured from the event's completion, not from the sale. Royalties from a show next month are simply not cashable this month.
  7. 9
    Request a payout for an amount you have available, with your ACH routing and account numbers.
    /api/credit/payouts POST ledger
    Expected result A payout request that starts held and becomes eligible once its lots have matured.
    Watch out for Only the last four digits of the account number are stored. Requests below the minimum payout setting are refused, as are amounts above your eligible earned balance. You can cancel your own request while it is still held or eligible — not after it is processing.

Steps 10–12 — Admin

their manual →

Sets the royalty rate and recipient, and is the only one who can process a payout.

  1. 10
    As an admin, review the payout queue: who is asking, how much, and whether their KYC is in order.
    Expected result Payout requests grouped by status with their lots.
    Watch out for Admin only — a venue manager is 403 on every ledger and payout surface. That boundary is deliberate and worth teaching rather than papering over.
  2. 11
    Process an eligible payout to send it to the ACH rail.
    /api/admin/payouts/{payout_id}/process POST ledger
    Expected result A transfer to the payments mock, a balanced payout posting, and the request moving toward paid on the settlement webhook.
    Watch out for Eligible only, and KYC is re-checked at process time rather than trusted from when the request was made. A failed transfer posts a reversal and restores the money to the host's earned balance as an immediately available lot.
  3. 12
    Reconcile the period: pull the settlements for the event with their totals.
    Expected result Gross, venue fee, host royalty and seller net across completed and failed settlements.
    Watch out for Those totals are the honest answer to 'what did the promoter earn'. They come from the settlement rows, which carry a database check that the three parts sum exactly to the sale price.

Steps 13 — Host / Promoter

their manual →
  1. 13
    Understand what happens if your event is cancelled: every resale on it is unwound, the buyer is refunded in full on the rail they paid on, and your royalty is clawed back.
    Expected result A reversal posting that mirrors the original settlement exactly.
    Watch out for The clawback can only take what you still have. Earned balances are never allowed to go negative, so if you already spent or cashed out the royalty, the shortfall is written off by the venue and recorded as an unrecovered receivable against your name. It is not forgiven quietly — it is a number somebody will call you about.

Read your credit statement

Understand the three balances, the running statement, and why nothing on it can be edited.

Owned by Member · 7 steps · about 10 minutes

Why this exists

Most members meet the ledger exactly once: when they want to know why their balance is what it is. So the statement is built to be read as a story rather than queried as a table — every movement in order, with a running balance, and no line that exists without a cause.

The mental model that makes it click is that your balance is several separate things. Promo is credit the venue gave you, backed by nothing you paid. Prepaid is money you put in. Earned is money you made selling tickets to other members. Tab, for VIPs, is money you have not paid at all. They behave differently on the way out — the spend order is promo, then prepaid, then earned, then tab, always, so the venue's gift goes first and your own money is touched only after it is gone — and on the way to a bank account, where only earned can ever leave the platform, and only after its hold has passed.

Promo is easy to miss and it is the one spent first. Your wallet shows it beside the others; the statement's per-account columns and its account filter do not yet, so a promo movement is in the running balance without a column of its own. If the numbers look off by exactly the size of a gift you were given, that is where it went.

The reason nothing here can be corrected in place is the whole point of a double-entry ledger: the tables are append-only, enforced by database triggers, so an UPDATE or DELETE aborts even from the admin data suite or a raw SQL console. A mistake is fixed by posting a new, balanced, clearly labelled adjustment — which also appears on your statement, so the correction is part of the story rather than a quiet rewrite of it.

Before you start

  • A member session with some history — the seeded credit demo accounts are ideal.
  • An admin session for the last two steps.

Practise with

PersonaEmailPasswordNote
membersam.seller@demo.club sam-pass-1234earned balance from a resale plus one completed payout — the richest statement
memberalice.credit@demo.club alice-pass-123prepaid only, for the simplest possible reading
vip_membervip@club.test vip123shows the tab block with limit, used and headroom
adminadmin@club.test admin123sees the same movements as balanced transactions

Steps 1–4 — Member

their manual →
  1. 1
    Open your credit page and name each figure out loud before moving on.
    /my/credit ledger
    Expected result Prepaid, earned, the payout-eligible slice of earned, and purchasing power.
    Watch out for Earned and payout-eligible earned are different. Money from a sale is spendable here immediately but not cashable out until its hold expires, roughly a day after the event.
  2. 2
    Read the same thing as JSON so you know the field names when you are debugging someone else's screenshot.
    Expected result The balances, the tab block or null, purchasing power, year-to-date secondary sales and your KYC status.
  3. 3
    Open the statement and follow the running balance from the top.
    Expected result Newest first, one line per movement, with the balance after each.
    Watch out for The page is print-friendly on purpose. It is the artefact you hand someone who is disputing a balance.
  4. 4
    Narrow it down: filter by date range or by account when a member asks about one specific week.
    Expected result The same rows, filtered and paged.
    Watch out for Everything here is strictly self-scoped. There is no member-facing way to read anyone else's statement — not by id, not by guessing.

Steps 5 — VIP Member

their manual →

Has the fourth number — the tab — that ordinary members never see.

  1. 5
    As a VIP, read the tab card: limit, used, headroom and status.
    /my/credit ledger
    Expected result The tab shown separately from real money.
    Watch out for A frozen tab still shows what you owe. Freezing removes headroom, not debt, and the monthly settlement will still come for the balance.

Steps 6–7 — Admin

their manual →

Reads the same money from the ledger side and owns corrections.

  1. 6
    As an admin, open the trial balance and find the account that holds members' prepaid credit.
    /admin/ledger ledger
    Expected result Every account with its debits, credits and net, summing to zero.
    Watch out for A member's balance is a per-user slice of one platform account. There is no per-member table to fix — the balance is derived from entries.
  2. 7
    Correct something the right way: post a balanced adjustment with a memo explaining it.
    Expected result A new transaction, visible on the member's statement.
    Watch out for This is the only correction mechanism. Attempting to edit or delete an existing entry is refused by the database itself, and the debug console has a probe that demonstrates exactly that.

Read the trial balance

Understand the chart of accounts, prove the books balance, and trace one number to its transaction.

Owned by Admin · 8 steps · about 20 minutes

Why this exists

The house-credit economy is a real double-entry ledger, not a balance column on the user row. Every movement of money is a transaction with at least two entries whose debits equal its credits, and the sum of every entry ever written is zero. That single property is what lets you answer "where did it go" without ever guessing.

Why go to that trouble for an in-app wallet? Because the platform holds customer money in three genuinely different ways and they must not be allowed to blur. Prepaid credit is money customers paid in and the venue owes back. Earned balance is money resale sellers made and the venue owes them. Tab used is money customers owe the venue. A single wallet number would hide all three, and the difference between them is the difference between a liability, a payable and a receivable.

The chart of accounts is small and fixed. Cash and payout clearing, the four per-user accounts — the three above plus promotional credit, which the venue mints rather than the customer paying in — two tax liability accounts, ticket revenue and royalty fees, processing expense, and a write-off account for resale clawbacks the venue absorbed. Per-user accounts carry a user id on every entry, enforced by the database, which is what makes a per-person sub-balance meaningful.

The books are append-only, enforced by triggers that fire on raw SQL as readily as on service calls. Reading the trial balance is therefore a genuinely trustworthy act: you are not reading a cache or a view someone could have edited, you are reading the sum of everything that ever happened.

Before you start

  • An admin session. This entire area is admin-only; a venue manager gets 403 even on the read-only routes.
  • A seeded database, so there is enough traffic for the numbers to be interesting.

Practise with

PersonaEmailPasswordNote
adminadmin@club.test admin123the only persona that can open any ledger surface

Steps 1–8 — Admin

their manual →
  1. 1
    Open the trial balance. Read it top to bottom once before you look for anything in particular.
    /admin/ledger ledger
    Expected result Every account with its debit total, credit total and net, plus a balanced flag and a grand total net that must be zero.
    Watch out for If the grand total is not zero, stop everything else you were doing. It means the append-only guarantee has been circumvented, and no other number on the platform can be trusted until you know how.
  2. 2
    Pull the chart of accounts and learn the codes. You will be typing them into adjustments and filters for the rest of your time here.
    Expected result Each account with its normal side and whether it is per-user.
    Watch out for CORRECTED 2026-08-30: FOUR accounts are per-user, not three. Prepaid member credit (2010), promotional credit (2015), seller earned balance (2020) and VIP tab used (2030) — 2015 arrived with the promo pot and this sentence did not move. Those four are the only ones where a per-person number exists at all, and if you are reconstructing somebody's credit from account filters, leaving 2015 out under-reports what they can spend.
  3. 3
    Pull the trial balance as of a specific instant when you need to answer a question about a moment in the past rather than about now.
    Expected result The same structure, computed as of the timestamp you asked for.
    Watch out for As-of is computed from the entries every time — `trial_balance` sums `ledger_entries` with a date filter and reads no stored total, which is why it can be trusted. CORRECTED 2026-08-30: the reason this step used to give was wrong twice. There IS a month-end close (close, lock and reopen a period; it closes months in order and refuses over a red invariant), and closing DOES store a trial-balance snapshot on the period row. Neither invalidates the as-of figure, because nothing reads that snapshot to answer this question — but do not go to a closed month expecting to post into it. An adjustment there is 409 PERIOD_CLOSED, and a locked month refuses even the prior-period escape.
  4. 4
    Move from totals to movements. Filter by kind to see one class of thing at a time — sales, refunds, top-ups, resale settlements, payout holds and releases, tab settlements, adjustments.
    Expected result A paged list with memos, kinds and references.
    Watch out for The reference type and id are the join back to the rest of the platform. A refund transaction points at a refund; a settlement points at a resale settlement. Follow the reference rather than matching amounts by eye.
  5. 5
    Open one transaction and confirm the double entry with your own eyes: debits on one side, credits on the other, the same total.
    /admin/ledger/transactions/{txn_id} ledger
    Expected result Entries with account codes, amounts and the user attribution on per-user lines.
    Watch out for Card sales post the processing fee as a SEPARATE transaction, not as a line on the sale. If a sale's numbers look too round, the fee is next door.
  6. 6
    Use the API when a question needs filtering the page cannot do — by user, by account, by date range, by reference.
    Expected result The filtered transactions as JSON.
    Watch out for Filtering by account plus user id is how you reconstruct one person's history of one balance. That is usually the fastest answer to a support question about credit.
  7. 7
    Run the integrity report and read every check, not just the overall flag.
    Expected result Balanced transactions, earning lots matching the earned account, no negative sub-balances, payout clearing matching live payouts, the trial balance balanced, and the protective triggers present.
    Watch out for The triggers check is the load-bearing one. Everything else on this page assumes those triggers exist.
  8. 8
    Finally, open the ledger entries in the universal data suite and try to change something.
    Expected result A read-only grid. The suite classified it automatically because the table carries append-only triggers.
    Watch out for There is no admin override. The one legitimate way to change a balance is a new balanced adjustment posting — which is a different workflow, and deliberately so.

Process a payout run

Take sellers' earned balances out to the bank — through the hold, the KYC gate and the ACH result.

Owned by Admin · 10 steps · about 25 minutes

Why this exists

A payout is the only route by which money leaves this platform to a person's bank, so it is the most gated thing in it. Three gates sit in a row, and each one exists for a different reason.

The hold. Resale proceeds are spendable inside the club immediately but are not cashable until a hold expires — by default a day after the event finishes. The reason is that an event can still be cancelled after it sells, and a cancellation claws proceeds back from sellers. Money that has left for a bank account cannot be clawed back, and the ledger will not let an earned balance go negative, so the venue would absorb it as a write-off. The hold is what keeps that window small.

The KYC gate. Over the reporting threshold of gross secondary sales in a calendar year, a cash-out requires a verified tax identity. The check is on external cash-outs only — spending your earned balance inside the club is never gated, because that is not reportable income leaving the platform. The identity itself is stored as a hash plus the last four digits; the raw number is never persisted anywhere, and the user row keeps only a masked marker. The gate is re-checked at processing time, not just at request time, so revoking someone's verification actually stops a payout that is already in the queue.

The clearing account. Requesting a payout does not pay anybody. It moves the amount out of the seller's earned balance into a payout clearing account, which is where it sits while the transfer is in flight. It leaves clearing only when the bank tells us what happened: settled posts a release, failed posts a reversal and gives the seller their balance back as an immediately available lot. The clearing account balance should always equal the money genuinely in flight, and the integrity report checks exactly that.

Worth knowing what the platform does not do: it holds the KYC record and computes year-to-date gross secondary sales, but it does not generate a 1099-K document. The tax module's filing package produces federal, state and municipal returns, not information returns for sellers.

Before you start

  • An admin session for the processing half; a member session for the requesting half.
  • A seller with an earned balance (sam.seller@demo.club has one, plus a completed payout to read).
  • Debug endpoints enabled if you want to skip the hold or simulate the bank result.

Practise with

PersonaEmailPasswordNote
adminadmin@club.test admin123processes payouts and can revoke a KYC verification
membersam.seller@demo.club sam-pass-1234$46.75 earned and one already-paid payout to inspect

Steps 1–3 — Member

their manual →

It is their money leaving the platform: they request it and they are the one the identity gate applies to.

  1. 1
    As the seller, open your payouts page. Read the eligible amount, which is not the same as your earned balance.
    Expected result Your earned balance, the portion that has cleared its hold, any pending requests with a countdown, and a KYC panel if you are over the threshold.
    Watch out for Earned and payout-eligible are different numbers on purpose. Money from an event that has not finished yet is spendable in the club and not yet cashable.
  2. 2
    Submit your legal name and tax id if the page asks for them.
    /api/credit/kyc POST ledger
    Expected result A verified record. Only a hash and the last four digits are stored.
    Watch out for Formats are checked strictly and a bad one is 422 TAX_ID_INVALID. Under the threshold this step does not exist at all — the platform does not collect identity it does not need.
  3. 3
    Request the payout: an amount and your bank details.
    /api/credit/payouts POST ledger
    Expected result A request that is either held with a countdown or immediately eligible, and a ledger posting that moves the amount from your earned balance into payout clearing.
    Watch out for 409 for more than your earned balance, 422 for malformed bank details, 403 KYC_REQUIRED if you are over the threshold without verification. Only the last four digits of the account number are ever stored.

Steps 4–10 — Admin

their manual →
  1. 4
    As an admin, open the payout queue and filter by status. The lifecycle is held, eligible, processing, paid — or failed or cancelled.
    Expected result Requests with amounts, users, hold expiry and status.
    Watch out for Only eligible requests can be processed. A held one is not yours to hurry along by hand — the scheduler promotes it when the hold expires.
  2. 5
    In training, promote matured holds immediately instead of waiting, so you can see the rest of the workflow today.
    Expected result The ids that moved from held to eligible.
    Watch out for This runs the same promotion the scheduler runs; it does not skip an unexpired hold. To skip a hold in a demo, make the lot available first — do not edit the row.
  3. 6
    Process an eligible payout. This is the call that hands the transfer to the bank rail.
    /api/admin/payouts/{payout_id}/process POST ledger
    Expected result The request moves to processing and a transfer record is created with the processor's transfer id.
    Watch out for 409 PAYOUT_NOT_ELIGIBLE for anything not in eligible status. And the KYC gate is re-checked HERE, so a verification revoked after the request was made stops the payout at this point with 403 KYC_REQUIRED.
  4. 7
    Simulate the bank's answer both ways: settle one payout and fail another with a failure code.
    Expected result Settled posts the release out of payout clearing and marks the request paid. Failed reverses it and restores the seller's earned balance as an immediately available lot.
    Watch out for In production this arrives as a webhook, and both handlers are idempotent — replaying a settlement does not pay twice. Rehearse the failure path too: a seller whose transfer bounced gets their balance back, not an apology.
  5. 8
    Trace the whole life of one payout through the ledger: the hold, then the release or the reversal.
    Expected result Two transactions per completed payout, both balanced, both referencing the payout.
    Watch out for The payout clearing account should net to exactly the money in flight. If it does not, the integrity report will say so before anyone else notices.
  6. 9
    Learn the revocation path: an admin can revoke a verification when something about it turns out to be wrong.
    /api/admin/credit/users/{user_id}/kyc/revoke POST ledger
    Expected result The record moves out of verified status.
    Watch out for Revoking does not claw back payouts already paid. It stops the next one, at processing time. Treat it as a stop, not as an undo.
  7. 10
    Finish by running the payout gate invariant and reading its answer.
    Expected result Confirmation that paid payouts carry a release posting, that none was processed before its hold expired, and that none over the threshold was processed without verified identity.
    Watch out for This invariant is the reason the gates above are worth obeying rather than working around. If you find a way past a gate, this check is what will find it too.

Post a house credit adjustment

Move a balance by hand the only way the platform allows: a new, balanced, append-only entry.

Owned by Admin · 8 steps · about 18 minutes

Why this exists

Sooner or later somebody's balance is wrong and you have to fix it. This workflow is about the fact that "fix" here never means "edit". The ledger is append-only, enforced by database triggers rather than by convention, and there is no admin surface anywhere — not the data suite, not raw SQL through the console — that can update or delete a posted entry. The only way to change a balance is to post a new balanced transaction that says what you changed and why. Undoing one whole transaction has a control of its ownReverse this transaction, on any transaction's page — which posts the exact mirror with a reason code and links the pair both ways. Compose an adjustment by hand when you are correcting something in part; reverse when the whole posting was wrong.

That sounds bureaucratic until the first time somebody asks what happened to their money. Because nothing is ever rewritten, the statement is a complete and ordered story: the mistake is there, the correction is there, and the reason is attached to both. An editable ledger cannot make that promise no matter how careful its admins are.

Adjustments are ordinary double-entry postings, so the ordinary rules apply. At least two entries. Every entry is on exactly one side. Debits must equal credits to the cent. Per-user accounts require a user id — member house credit, seller earned balance and VIP tab used are all per-user, and the database refuses an entry on them without one. And no posting may drive a user's sub-balance negative: there is no overdraft, no receivable wallet, just a refusal.

Memo discipline matters more here than anywhere else on the platform. The memo you type is the explanation an auditor reads. "Adjustment" is not an explanation.

Before you start

  • An admin session — every ledger admin route is admin-only and a venue manager gets 403.
  • The chart of accounts to hand: the account codes are on the trial balance page.
  • A decided, written reason. Post the memo you would be happy to read aloud.

Practise with

PersonaEmailPasswordNote
adminadmin@club.test admin123the only persona that can post an adjustment
memberalice.credit@demo.club alice-pass-123seeded with $150 prepaid house credit — a safe balance to practise on

Steps 1–4 — Admin

their manual →
  1. 1
    Open the user's credit page. This is the adjustment composer as well as the readout: balances, recent movements, and the chart of accounts to pick from.
    /admin/credit/users/{user_id} ledger
    Expected result Prepaid, earned and tab figures for that user, plus their recent transactions.
    Watch out for Read the three balances as three different things. Prepaid is money they paid in, earned is money they made from resale, tab is money they owe. Correcting the wrong one is a bigger mess than the original error.
  2. 2
    Pull the raw blob for the same user when you want the underlying numbers rather than the page's presentation of them.
    /api/admin/ledger/users/{user_id} ledger
    Expected result Balances, earning lots, tab state and KYC status.
    Watch out for Earning lots matter for cash-out timing, not for spending. A user can spend earned credit immediately and still not be able to withdraw it yet.
  3. 3
    Post the correction: a memo that explains it, and the entries. Give every entry an account code, a direction and an amount, and put the user id on any per-user account.
    Expected result 201 with the new transaction id. The transaction is recorded with kind adjustment.
    Watch out for An empty memo is 422 MEMO_REQUIRED. Unbalanced entries are 422 UNBALANCED_ENTRIES and nothing is written. A per-user account without a user id is 422 USER_REQUIRED. A posting that would take a sub-balance below zero is 409 WOULD_GO_NEGATIVE. Every one of those refusals leaves the ledger exactly as it was.
  4. 4
    Open the transaction you just posted and check it reads the way you meant: debits on the left, credits on the right, equal totals, your memo on top. If it is wrong, use the Reverse this transaction card on this page — pick a reason code, say why in a sentence, confirm.
    /admin/ledger/transactions/{txn_id} ledger
    Expected result The entries with the user attribution on the per-user lines, and a Reverse card above them on any transaction that is not itself a reversal.
    Watch out for Reverse posts the exact mirror of every entry and links the two both ways, so the pair reads as one story. Do not hand-compose the mirror in the composer: it is the same money and about eleven presses instead of four, and the result is an unlinked transaction with no reason code — nothing joins it to the mistake it corrects. One reversal per transaction, ever, and a reversal cannot itself be reversed (409 CANNOT_REVERSE_REVERSAL — re-post the original instead). There is still no edit button anywhere, which is the point: two honest rows beat one tidy one.

Steps 5 — Member

their manual →

It is their balance that moves, and the correction lands on their statement in full view.

  1. 5
    Look at the correction from the member's side, on their statement.
    Expected result Your adjustment appears in the running story with its memo, between the ordinary top-ups and purchases.
    Watch out for The member sees the memo. Write it for them, not for you.

Steps 6–8 — Admin

their manual →
  1. 6
    Understand the neighbouring control that is not an adjustment: setting or freezing a VIP's tab limit.
    /api/admin/credit/users/{user_id}/tab PUT ledger
    Expected result The tab row with its new limit or status.
    Watch out for A tab limit is permission to owe, not a balance. Raising it gives nobody any money, and freezing it stops further spending without cancelling a penny of what is already owed.
  2. 7
    Prove the append-only claim to yourself rather than believing this page: run the mutation attempt probe.
    Expected result A report showing every attempted update and delete against the ledger being rejected by the database.
    Watch out for If any attempt ever succeeds, this endpoint fails loudly with a 500. That is intentional — a silently mutable ledger is worse than a broken endpoint.
  3. 8
    Finish with the integrity report so you know your correction did not break something else.
    Expected result Balanced transactions, lots reconciling to the earned account, no negative sub-balances, payout clearing matching live payouts, and a balanced trial balance.
    Watch out for Fix a red check before you move on. Ledger errors compound: the next report you run is built on the mess you left.

Run the VIP tab settlement

Close out a month of tab spending: sweep credit, charge cards, and handle the failures.

Owned by Admin · 9 steps · about 20 minutes

Why this exists

A VIP tab is not a balance the member owns; it is a limit on what they may owe. Spending against it moves no money at all — it records an obligation. That is why the platform spends real money first: prepaid credit, then earned balance, and only then the tab. The tab is always the last resort, which keeps the amount to be collected as small as it honestly can be.

Settlement is scheduled, not manual. On the first tick of a new calendar month the platform settles the previous month for everybody carrying a tab balance: it sweeps their prepaid credit first, charges their card for whatever remains, and emails an invoice. Doing it on a schedule rather than on demand means nobody has to remember, and nobody chooses who gets chased.

The admin surface exists for the exceptions, and the exceptions are the interesting part. A declined card marks the settlement failed and freezes the tab. The balance is still owed — freezing is a credit decision, not forgiveness. Retry re-attempts the charge. Waive is meant to write it off deliberately and on the record — as of 2026-08-30 it does neither: it relabels the row, posts nothing, and next month's run charges the same card for the same money. Plan 675 is the fix; until it ships, treat a waive as a note to yourself and not as forgiveness. Both are still actions with names, which is better than an admin quietly adjusting a balance to make a problem disappear.

The idempotency guard is a unique constraint on user plus period, not a greyed-out button. Running settlement twice for the same month cannot charge twice even if two admins press it at the same second — which is exactly the property you want in the thing that touches customers' cards.

Before you start

  • An admin session. Tab settlement, tab limits and the settlement dashboard are all admin-only.
  • A VIP with tab usage to settle (vip@club.test is seeded with $80 used against a $250 limit).
  • Know which period you mean. Periods are calendar months and the endpoint takes one.

Practise with

PersonaEmailPasswordNote
adminadmin@club.test admin123runs settlement, retries, waives and sets limits
vip_membervip@club.test vip123the seeded tab: $250 limit, $80 used

Steps 1–2 — Admin

their manual →
  1. 1
    Open the settlement dashboard. It shows one row per user per period, and tells you the last period the scheduler settled.
    Expected result Recent settlements with status paid, failed or waived, and a default period suggestion of last month.
    Watch out for The last-settled marker is how the monthly tick knows not to repeat itself. Do not edit that setting by hand — you will make settlement skip or repeat a month.
  2. 2
    Set a tab limit for a VIP before the month they will use it, or freeze an existing tab.
    /api/admin/credit/users/{user_id}/tab PUT ledger
    Expected result The tab row with its new limit or status.
    Watch out for VIP status alone does not create a tab limit. Granting vip_member gives somebody Zone B and a tab that may have no headroom at all until you set one.

Steps 3 — VIP Member

their manual →

It is their tab: the sweep takes their prepaid credit first and their card second.

  1. 3
    As the VIP, look at what settlement is going to act on: your prepaid balance — the only pot the run sweeps — your earned balance, and your tab used against its limit.
    /my/credit ledger
    Expected result Three separate numbers plus a combined purchasing power figure.
    Watch out for Purchasing power adds tab headroom to real money. It is what you can spend, not what you have. Settlement collects the difference — out of prepaid credit first and then the card. It never touches earned or promo balance, so a VIP can be charged while the platform still owes them money.

Steps 4–9 — Admin

their manual →
  1. 4
    Run settlement for a period. Leave the period out and it settles the previous month.
    Expected result Per-user results: prepaid swept, card charged for the remainder, invoice emailed. Counts of settled, failed and skipped.
    Watch out for Idempotent per user and period. Pressing it twice does not charge twice — the unique constraint is the guard, and it holds under concurrency.
  2. 5
    Read the results and separate the failures from the successes before you touch anything.
    Expected result Failed rows for declined cards, alongside paid rows.
    Watch out for A failed settlement also froze that person's tab. Somebody at the door or the bar will be told they cannot charge, and they will not know why unless you tell them.
  3. 6
    Retry a failed settlement once the member has fixed their card.
    /api/admin/tab-settlements/{settlement_id}/retry POST ledger
    Expected result A fresh charge attempt against the same period, and the tab unfrozen on success.
    Watch out for 409 SETTLEMENT_NOT_RETRYABLE if it is not in a retryable state. Retry is for failed rows; it is not a way to re-run a month that already settled.
  4. 7
    Waive a settlement when the venue has decided to absorb it — a goodwill gesture, a disputed charge, a member who has left.
    /api/admin/tab-settlements/{settlement_id}/waive POST ledger
    Expected result The settlement row moves to waived. CORRECTED 2026-08-30: that is ALL it does — no ledger transaction is posted, the tab usage is untouched, and next month’s run charges the card for it again. Plan 675.
    Watch out for The button’s own confirmation says the debt is written off permanently, and that is not true today, so do not repeat it to a member. Nothing records who waived it either — there is no such column. What is still right is the principle: forgiving a balance is a named act, and adjusting the ledger to make the number look nicer is not.
  5. 8
    In training, settle a single user for a single period so you can watch one person's mechanics end to end.
    Expected result The same settlement logic scoped to one user.
    Watch out for Admin-only debug. The production path is the monthly tick; this is a microscope, not a substitute.
  6. 9
    Trace one settlement into the ledger: the prepaid sweep, the card charge and its separate processing fee.
    Expected result Balanced transactions of kind tab settlement, referencing the settlement.
    Watch out for Try to edit one and the database itself refuses. The append-only trigger is not a UI convention — it fires from the data suite and from raw SQL exactly the same way.

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.