Browse API docs
Promoter programs
Programs group shared customer discounts and event scope. Each assigned promoter receives an organizer-chosen personal promo code and link. No random codes are generated. One person may belong to several programs. Existing standalone promo codes remain supported.
A program has a kind: promo_code (the default) for ticket sales with personal promo codes, or guest_list for a Promoter + guest list, where promoters bring guests through their personal link instead of selling tickets. See Guest list programs.
Use organizer account Bearer tokens and Accept: application/json on the ordinary admin routes. Reads require a read or write token; writes require a write token. Personal co-organizer tokens can manage programs only when all selected events are currently assigned to that membership. Shop-wide programs and programs containing any unassigned event are excluded; their IDs return 404. New co-organizer programs default to shop_wide: false and require event_ids. Attempts to create or change a program to a shop-wide or unauthorized scope return 422. Promoter sessions cannot manage programs. Foreign program IDs return 404; invalid terms return 422 with {"errors":{"field":["message"]}}.
Program endpoints
| Method | URL | Purpose |
|---|---|---|
| GET | /admin/promoter_programs |
List programs in a programs array |
| POST | /admin/promoter_programs |
Create a program and optional personal codes; 201 with Location |
| GET | /admin/promoter_programs/:id |
Program, personal codes and pending invitations |
| PATCH | /admin/promoter_programs/:id |
Update common terms or pause with active: false |
| DELETE | /admin/promoter_programs/:id |
Delete a program without sales (204) or archive one with sales (200 with the program) |
:id is the program’s opaque public ID, not a database number.
{
"promoter_program": {
"name": "Club promoters",
"shop_wide": false,
"event_ids": [23, 24],
"discount_type": "percentage",
"discount_amount": "10.0",
"membership_ids": [42, 43],
"codes": { "42": "LISA-CLUB", "43": "BEN-CLUB" }
}
}
shop_wide defaults to true and includes future events automatically; selected event IDs are cleared in that mode. When false, choose at least one event from this account. Updating event_ids replaces the complete event selection. membership_ids on creation assigns accepted, active promoter memberships. Supply codes as an object keyed by membership ID for every new assignment. Codes must be 3–32 letters, numbers or hyphens and unique within the shop; they are trimmed and uppercased. Missing, invalid or duplicate codes return 422 and roll back the entire batch.
discount_type is percentage (default) or fixed. discount_amount defaults to zero: tracking without a customer discount. Percentage values are 0–100; fixed amounts are nonnegative major currency units. Optional expires_at is ISO 8601; usage_limit caps redemptions per personal code; max_per_customer caps redemptions per customer and personal code. Pass null to clear optional limits or expiry.
Responses expose id, name, kind, shop_wide, event_ids, discount_type, decimal-string discount_amount, expires_at (UTC or null), usage_limit, max_per_customer, active, leaderboard_enabled, archived_at (UTC or null), promo_codes, pending_invitations, signup (see Signup link and applications) and, for guest list programs only, guest_list (see Guest list programs). Each code has id (numeric code ID), code, promoter_membership_id, active, and share_url. Pending invitations have id, email, and code (null for older invitations without a reserved code). The promo_codes array contains assigned codes only, including inactive assignments until they are removed.
Guest list programs
Guest list programs belong to Promoter +, the paid add-on for promoter guest lists. Promoters share their personal link (/g/CODE on the shop domain), guests put themselves on the list for the current party, and the door gives them what the program’s benefit says, such as cheaper entry or a free drink. A guest list is not a ticket: nothing is ordered and no ticket is sent.
Without Promoter +, GET /admin/promoter_programs lists ticket programs only, and creating a program with kind: "guest_list" returns 422 with an error on kind. Guest data that already exists stays readable.
{
"promoter_program": {
"name": "Door crew",
"kind": "guest_list",
"shop_wide": true,
"guest_benefit": "Free shot until midnight",
"max_guest_companions": 3,
"guest_email": "required",
"guest_consent_required": false,
"guest_instagram": true,
"guest_capacity": 150,
"after_party": "continue",
"membership_ids": [42],
"codes": { "42": "LISA" }
}
}
| Field | Values |
|---|---|
kind |
promo_code (default) or guest_list. Set on creation. An update can switch it only while nothing ran through the program yet (no guests and no orders with its codes) and the shop has Promoter +; otherwise the value is ignored. |
guest_benefit |
What guests on the list get at the door, up to 200 characters. Shown on the signup page, in the guest’s confirmation and at the door. |
max_guest_companions |
How many people a guest may bring along, 0–19. Default 4. |
guest_email |
required (default) or optional. |
guest_consent_required |
true makes consent to the shop’s event news the condition for signing up through the link. Default false. Switching it on sets guest_email to required. |
guest_instagram |
Ask guests for their Instagram handle. Default true. |
guest_capacity |
Heads per event across all promoters of the program, or null (default) for no limit. |
after_party |
continue (default): after each party the list moves on to the next one by itself. pause: the list waits until the organizer starts the next party in the dashboard. Co-organizers cannot set it. |
Guest list programs ignore ticket terms: discount_type, discount_amount, usage_limit, max_per_customer and expires_at are cleared on save. Their codes never discount a checkout, are left out of the promo code list, and their share_url is the promoter’s guest list link. The signup link for new promoters is switched on from the start.
Program responses add a guest_list object for these programs:
{
"guest_list": {
"guest_email": "required",
"guest_consent_required": false,
"guest_instagram": true,
"guest_capacity": 150,
"benefit": "Free shot until midnight",
"max_companions": 3,
"after_party": "continue",
"paused": false,
"current_event_id": 23,
"guests": 48,
"heads": 97,
"arrived": 61
}
}
current_event_id is the party the list runs for now: the next published event the program covers, or with after_party: "pause" the party the organizer started. It is null while the list waits for the next party (paused: true) or no party is coming up. guests, heads (guests plus companions) and arrived (heads checked in) count all of the program’s events. Read and manage individual guests per event through Promoter guest lists, and message the crew through Crew news.
People and invitations
| Method | URL | Body / effect |
|---|---|---|
| POST | /admin/promoter_programs/:id/memberships |
{"membership_ids":[42,43],"codes":{"42":"LISA-CLUB","43":"BEN-CLUB"}}; add or reactivate personal codes, 201 |
| PATCH | /admin/promoter_programs/:id/memberships/:code_id |
No body; deactivate that personal code, 204 |
| DELETE | /admin/promoter_programs/:id/memberships/:code_id |
Remove an already inactive assignment, 204; an active code returns 422 |
| POST | /admin/promoter_programs/:id/invitations |
{"email":"lisa@example.com","code":"LISA-CLUB"}; reserve the chosen code and send an invitation linked to this program, 201 |
| DELETE | /admin/promoter_programs/:id/invitations/:invitation_id |
Remove this pending program assignment, preserve the team invitation, 204 |
Program invitation creation returns 201 with a Location header pointing to /admin/invitations.json. The response includes the invitation fields plus promoter_programs, an array of { "id": "PROGRAM_PUBLIC_ID", "name": "Club promoters", "code": "LISA-CLUB" } for its accessible assignments. Co-organizers never receive unrelated program names or codes from a shared invitation. Codes are null for legacy assignments without a reserved code. The promoter email identifies the shop and programs, includes reserved codes and links, and asks the recipient to accept before sharing.
Repeated membership assignment reuses the same code, including previous redemption counts. Inviting a removed member returns 422 and directs the organizer to add the member again instead; it does not reactivate or send an email. The codes value may be omitted for an existing assignment; a different value is rejected. Use the separate code-name edit to rename an existing code. A pending promoter invitation may belong to multiple programs; each program reserves its chosen code immediately as an inactive code. Acceptance assigns and activates those exact codes with current program terms, without another organizer action. Missing, invalid, or already used codes return 422 and do not send an invitation. Removing a pending assignment releases its reserved code. Older invitations without a reserved code still accept without generating a code. An existing invitation with a different role cannot be repurposed by this endpoint.
Deleting a program
DELETE /admin/promoter_programs/:id removes a program. Without redemptions or orders through its codes, the program is deleted with its codes, pending program invitations (releasing their reserved codes) and applications, and the response is 204. Otherwise it is archived and the response is 200 with the program and its archived_at: pending program invitations are removed, every code is deactivated and leaves the promoter dashboards, open applications are declined and the signup link closes. Orders keep their attribution, and the promoters’ codes stay in the promoter report. Archived programs no longer appear in the program list and cannot be restored. If a code cannot be changed, nothing is removed and the response is 422 with errors.
Signup link and applications
A program can have a public signup link on the shop domain where people apply with their name, email, a code they would like and answers to your questions. The program’s signup object shows its state:
{
"enabled": true,
"url": "https://club.usetix.io/promoter-signup/…",
"questions": [{ "id": "insta", "label": "Instagram name", "required": true }],
"pending_applications": 2
}
url is null while the link is off. The link also stops working while the program is paused.
| Method | URL | Body / effect |
|---|---|---|
| PATCH | /admin/promoter_programs/:id/signup |
{"signup":{"enabled":true,"questions":[{"label":"Instagram name","required":true}]}}; returns the signup object |
| POST | /admin/promoter_programs/:id/signup_rotation |
No body; issue a new link so shared copies stop working, returns the signup object |
| GET | /admin/promoter_programs/:id/applications |
Optional status: pending (default), accepted or rejected; 25 per page |
| POST | /admin/promoter_programs/:id/applications/:application_id/acceptance |
Optional {"code":"LISA-10"} to assign another code; returns the application |
| POST | /admin/promoter_programs/:id/applications/:application_id/rejection |
No body; returns the application |
questions is the complete list of up to ten questions and replaces the previous one; omit it to change only enabled. Keep an existing question’s id to keep it, or pass "remove": true to drop it. Question IDs are 1–16 lowercase letters or digits; other IDs are replaced by new ones. Stored answers keep their question labels, so editing questions never changes past answers.
The applications list returns an applications array and counts per status:
{
"applications": [
{
"id": "APPLICATION_PUBLIC_ID",
"name": "Lisa",
"email": "lisa@example.com",
"code": "LISA-10",
"status": "pending",
"answers": [{ "id": "insta", "label": "Instagram name", "value": "@lisa" }],
"created_at": "2026-09-29T10:00:00Z",
"reviewed_at": null
}
],
"counts": { "pending": 1 }
}
Accepting makes the applicant an active promoter right away with the wished code, or code when given, and emails them a one-time link into their promoter dashboard (valid for 7 days). No invitation needs to be accepted. Someone who was in the program before gets their earlier code back. A code taken in the meantime or an application that was already reviewed returns 422 with errors; a pending application stays pending. Rejecting only marks the application; no email is sent. Each email can apply once per program. New applications notify owners and managers in the dashboard; they are not a webhook event.
Terms and attribution
Common terms are edited on the program. Personal code names can still be changed through the promo-code endpoint; independent term/scope/ownership changes return 422. Program-managed codes additionally expose promoter_program_id (public ID) and event_ids in promo-code and promoter-report JSON. For these codes, use the program scope rather than legacy event_id.
Changes affect future redemptions. Existing purchases and reserved checkouts retain their prices and purchased event attribution. In mixed carts only covered event tickets are discounted and attributed; voucher products are excluded. Deactivate a personal code before removing its assignment. Deactivation stops new uses; removal hides the assignment from the program and promoter sharing dashboard while keeping sales history and program totals. Re-adding the person restores the same code. Pausing a program also blocks new uses. Read performance through Promoters.
Optional promoter leaderboard
Programs show their leaderboard to the active promoters assigned to them: leaderboard_enabled defaults to true for new programs and is returned in program responses. Set it to false to disable access. The dashboard ranks the current party first (the next event the program covers, so every party starts from zero) and all time on a second tab, with counts and names only, a top-three podium and the current promoter highlighted. Each program is ranked separately. Other programs, accounts, emails, codes, links, revenue and buyer details are excluded. Pausing a program or deactivating a membership or its program code removes leaderboard access. This is an authenticated HTML dashboard feature; there is no public or JSON leaderboard endpoint.
Co-organizer scope
The same restriction applies to every program, code, membership, invitation, signup link, application and delete action in the dashboard, personal API, assistant and MCP. Existing membership IDs must belong to promoters already visible through accessible codes; invite a new person using their exact email and chosen code. Co-organizers can rename and reactivate program codes, update common terms, and remove inactive assignments. They can remove a pending program invitation, but cannot resend or revoke the entire team invitation or change team roles. Program and People sales exclude unassigned events, including historical purchases made before a program changed its event scope. Revoked assignments take effect on the next request.