One feature across two surfaces: mint a code in the room (App), manage and audit every code in the Console. Both call the same role-gated endpoint — the logic lives once on the backend, never in either frontend.
| Code | Grants | Use case | Minted by | State | |
|---|---|---|---|---|---|
| ACS-K7M2 | gatepro 30d | erik@relayctx.com | redeemed | ||
| ACS-T2HF | gatelooppro 14d | erik@relayctx.com | issued |
/api/admin/v1 in prod (the Applicants screen), so the in-room "Issue code" is the same role-gated endpoint the Console mint form uses — auth is the session JWT (Bearer or relay_jwt cookie), gated by role, not by which frontend called it. The mint/list/revoke logic lives once in a service; both surfaces are thin clients. An access code is a beta_codes row, extended with grant_loop, trial_days, use_case, source='demo', minted_by; activation (beta_service) reads the code instead of hardcoding beta — sets tier, optionally create_loop_membership(joined_via='demo') + set_user_trial — and logs the redemption (pre-cleared + audited). Safety invariant: every grant lands on the prospect's own org; never the rep's (the line vs the removed org-invite embed, #298). Now integrated: the App’s in-room mint and the Console management surface. This page stays as the one-screen explainer of the split. Entitlement policy governed by SPEC.org-plan-model.md in relay-board.