RSVP & Guests
What it is
A guest is a person invited to (or self-registered for) an event. Guests can be added by the planner directly, or self-RSVP through one of several public flows depending on whether the event has paid items/tickets attached.
Core concepts
- Guest status (
GuestStatusenum):INVITED,PENDING,CONFIRMED,DECLINED,CANCELLED,ATTENDED,CHECKED_IN,NO_SHOW. Note the RSVP list endpoint (getEventGuests) normalizesCHECKED_INback toCONFIRMEDin its response — checked-in is a check-in-desk concept, not a distinct RSVP state as far as most planner UI is concerned. - RSVP reference: generated as
RSVP-{eventId}-{randomCode}(orGUESTPURCHASE-{eventId}-{randomCode}for guest purchases), an 8-character random code. This reference is what a guest uses to look up or resend their confirmation, and what payment tracking keys off for RSVPs that require payment. - Event sections & permissions: guest-management endpoints are gated by
isEventPlanner([EventSections.Rsvp, EventSections.TicketHero], 'OR')— the planner needs access to either the RSVP section or the Ticket Hero (check-in/ticketing) section, not necessarily both.
How it works
Planner-added guests
POST /v1/guests/event/{eventId}/guests — a planner adds a guest directly (name, phone, optional email). Requires EventSections.Rsvp access.
Self-service RSVP (public, no auth)
There are three parallel public RSVP endpoints depending on what the event requires:
rsvp-free— no items/tickets involved, just a headcount. Guests + a submitter (phone required) are posted; each guest in the array gets created and receives its own RSVP reference. Blocked outright for events on an internal exclusion list that require an item selection ("Please select an item to continue").rsvp-with-items— for events with zero-value items/tickets that still need to be recorded (e.g. free ticket tiers). The endpoint validates the total value of everything selected is exactly zero server-side and rejects otherwise ("This endpoint is only for free items... Please use the payment flow for paid items") — it's a distinct code path from paid RSVP, not the same handler with a discount applied.rsvp-with-discount-code— same idea, with a discount code applied; there's a separatevalidate-discount-codeendpoint to preview the discount (per-item applicability breakdown, original/discount/final totals) before the guest commits.
All three accept an optional isLiveGuest + guestId pair to add items to an existing guest instead of creating a new one, and an oneCodePerSlot flag so a multi-quantity purchase (e.g. a pack of 8 tickets) can mint one RSVP code per slot instead of one code for the whole order.
Discount codes
A code has a discountType (percentage, flat, or free), an optional minimumSpend (checked against the pre-discount total, not just the discounted items), and an optional applicableTicketTypes allowlist (empty/null means it applies to every item in the cart). validate-discount-code returns a per-item breakdown (isApplicable, itemTotal) plus the aggregate calculation, so a client can show exactly which cart lines the code affects before the guest commits. rsvp-with-discount-code then branches on the post-discount amount: exactly 0 completes the RSVP immediately (no payment step); anything above 0 returns a payment reference for the normal bank-transfer flow in Payments & Payouts. Usage is tracked per guest phone number, so a usagePerGuest: 1 code can't be reused by the same phone under a different name/email.
Guest self-service (public, no auth)
PATCH /rsvp-submission/update-guest-note— a guest can add/update their own note (dietary restrictions, special requests) using just their RSVPreference+phoneNumberas proof of ownership, no account required. The note round-trips into the planner's guest list (guestNotefield) and the CSV export.POST /event/{eventId}/guests/download— a planner emails themselves a CSV of the full guest list (filterable bystatus/guestGroup/search). It's sent as an email attachment rather than a direct download specifically to avoid request-timeout issues on very large guest lists (fetches up to 100,000 guests, uncapped by pagination).
Bulk operations
Planners can bulk-create guests directly (POST /guests/bulk) or upload a CSV (POST /guests/bulk-upload, optionally assigning all rows to one ticket).
Key business rules & edge cases
- 18+ gating is checked twice, deliberately. When an event has
eighteenPlusCheckenabled, guest date-of-birth is validated once at payment initiation (before money moves) and again when the payment webhook actually confirms — the code comment on this is explicit: initiation-time checking exists specifically "to prevent money from being debited in a state that can never be processed," because by the time the webhook fires it's too late to just reject the request. Don't remove the initiation-time check as a "duplicate" of the webhook check — they serve different failure windows. dobCheckon a guest isDD-MM-YYYY, not ISO — a common source of silent validation mismatches if a client sendsYYYY-MM-DD.guests.routes.tshas no route-layer validation on any endpoint — required fields are only enforced inside the controllers (e.g.processRSVPWithoutItemsmanually checksguestsis a non-empty array andsubmitter.phoneis present, throwing a 400 otherwise). Treat the OpenAPI doc-only schemas for this module as best-effort documentation of controller-level expectations, not a validated contract.
Related endpoints
See the API Reference — POST /v1/guests/event/{eventId}/guests, GET /v1/guests/event/{eventId}/guests, POST /v1/guests/event/{eventId}/guests/rsvp-free.
Also: POST /v1/event/{eventId}/guests/validate-discount-code, POST /v1/event/{eventId}/guests/rsvp-with-discount-code, POST /v1/event/{eventId}/guests/download, PATCH /v1/rsvp-submission/update-guest-note — not yet in the generated OpenAPI spec.
Related code
src/modules/guests/guest.routes.ts,guests.controllers.ts,guests.service.tssrc/shared/enum/guest.enum.ts—GuestStatussrc/shared/utils/random.util.ts—generateReferencesrc/shared/utils/csv.util.ts— guest-list CSV generationsrc/modules/guests/guests.openapi.ts— generated-docs schema (doc-only)