Subchapter 19.83
references/events/manage-event-guests.mdMarkdown27 KBView on GitHub
Read and change who is registered for an event. How guests are stored depends on the event’s
registration.initialType:
| Event type | Guests are | API |
|---|
RSVP | RSVPs — one per person who responded, with up to 10 additional guests each | RSVP V2, https://www.wixapis.com/events/v2/rsvps |
TICKETING | Orders (the buyer) containing tickets (one per attendee) | Orders and Tickets, https://www.wixapis.com/events/v1/... |
| either | A combined read-only guest view | Event Guests, https://www.wixapis.com/events/v2/guests/query |
Find the event and its type first — this exact body:
curl -X POST 'https://www.wixapis.com/events/v3/events/query' \
-H 'Content-Type: application/json' \
-H 'Authorization: <AUTH>' \
--data-binary '{
"query": {
"filter": { "title": { "$eq": "<EVENT_TITLE>" } },
"paging": { "limit": 100 }
},
"fields": ["REGISTRATION"]
}'paging is a sibling of filter inside query; nested inside filter it fails 400.filter, paging and sort are siblings under query; a sort inside filter, a nested
registration object or a registration.status filter fails 400; read
registration.initialType from the result instead.includeDrafts — true needs a draft-events permission and fails 403.events[].id, the type events[].registration.initialType. If several events match,
pick by date or ask (Manage Wix Events (opens in a new tab)).Registration rules (closing registration, guest limit, waitlist)
are event settings under event.registration, changed with PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID> (Manage Wix Events (opens in a new tab)).
Find the event first, then work only on its branch. Its registration.initialType decides
everything below: RSVP uses only the RSVP calls, TICKETING only the ticketing ones. Look up
schemas only for the calls of that branch — never prepare both.
Reference pages for the calls below — the full contract of each; use these rather than searching for them:
Follow only the branch for the event’s type: an RSVP event never needs the ticketing calls.
Every call here returns 428 WIX_EVENTS_APP_NOT_INSTALLED on a site without the Wix Events app.
Install it with appDefId 140603ad-af8d-84a5-2c80-a0f60cb47351:
POST https://www.wixapis.com/apps-installer-service/v1/app-instance/install with
{"tenant":{"tenantType":"SITE","id":"<SITE_ID>"},"appInstance":{"appDefId":"140603ad-af8d-84a5-2c80-a0f60cb47351"}} —
appDefId nests under appInstance. See Install Wix Apps (opens in a new tab).
Any event — the combined view. POST https://www.wixapis.com/events/v2/guests/query:
curl -X POST 'https://www.wixapis.com/events/v2/guests/query' \
-H 'Content-Type: application/json' \
-H 'Authorization: <AUTH>' \
--data-binary '{
"query": {
"filter": { "eventId": "<EVENT_ID>", "attendanceStatus": "ATTENDING" },
"cursorPaging": { "limit": 100 }
},
"fields": ["GUEST_DETAILS", "GUEST_TOTAL"]
}'Each of guests[] has guestType (RSVP, BUYER or TICKET_HOLDER), attendanceStatus
(ATTENDING, NOT_ATTENDING, IN_WAITLIST), rsvpId or orderNumber/ticketNumber,
contactId, totalGuests (only with GUEST_TOTAL), and — with GUEST_DETAILS —
guestDetails.{firstName, lastName, email, phone, formResponse, checkedIn}. Without
GUEST_DETAILS the names and emails are not returned; without GUEST_TOTAL there is no
totalGuests to count with. Page with pagingMetadata.cursors.next.
Count people, not records. On a ticketed event a buyer appears once as BUYER and once per
ticket as TICKET_HOLDER, so one order of 2 tickets is 3 guest records. To count attendees, filter
"guestType": "TICKET_HOLDER" (ticketed) or sum totalGuests over RSVP guests with
ATTENDING (RSVP), and say which you counted.
How many registered, for one event or many — in one call. Add "fields": ["DASHBOARD"] to the
Query Events call above (drop the title filter for every event, or use "id": { "$in": [...] }).
Each event then carries summaries.rsvps (totalCount, yesCount, noCount, waitlistCount)
and summaries.tickets (ticketsSold, totalOrders). Use yesCount (RSVP) or ticketsSold
(ticketed) for guests who are coming; use totalCount for everyone who responded, including “no”
and waitlisted. The reference defines the RSVP counts as
numbers of RSVPs, so an RSVP that brings additional guests counts once; for people, sum the guest
query’s totalGuests. Use this rather than a guest query per event when the user asks how many
registered for each event, or which events have guests.
Several events, or all of them — for an attendee list per event, or a spreadsheet of every
event’s guests. Use the same call with "eventId": { "$in": ["<EVENT_ID>", "<EVENT_ID>"] }, or
leave eventId out to get the guests of every event. Filter guestType to people, so each
attendee appears once, and attendanceStatus to those coming. The two calls below, their page
sizes and the response paths named here are complete and were run as written on a live site:
send them as shown, in one code run, without a docs search or a schema lookup for Query Event
Guests or Query Events first.
POST https://www.wixapis.com/events/v2/guests/query:
{
"query": {
"filter": {
"guestType": { "$in": ["RSVP", "TICKET_HOLDER"] },
"attendanceStatus": "ATTENDING"
},
"cursorPaging": { "limit": 1000 }
},
"fields": ["GUEST_DETAILS", "GUEST_TOTAL"]
}For everyone who responded, drop attendanceStatus and show it as a column. Each guest carries
eventId: group by it, and take the titles from
POST https://www.wixapis.com/events/v3/events/query with the distinct eventIds:
{
"query": {
"filter": { "id": { "$in": ["<EVENT_ID>", "<EVENT_ID>"] } },
"paging": { "limit": 100 }
}
}The guest query returns guests[] (fields as listed above) and
pagingMetadata.cursors.next; Query Events returns events[] with id and title. Query Events
returns at most 100 events per call, so with more than 100 distinct eventIds send them in $in
batches of 100, in the same code run. The guest query accepts cursorPaging.limit 1000
(checked live). For its next page send only { "query": { "cursorPaging": { "limit": 1000, "cursor": "<pagingMetadata.cursors.next>" } }, "fields": [...] };
the cursor keeps the first request’s filter. There is no next page when cursors.next is missing.
Unless the event collects one form per ticket, every TICKET_HOLDER of an order carries the
buyer’s name and email; say so in the list.
New registrations since a time (“how many RSVPs today”, “who signed up this week”). Both
calls filter on createdDate, an ISO time in UTC. For “today”, start at midnight in the event’s
dateAndTimeSettings.timeZoneId and convert it to UTC: midnight on 2 November 2026 in
America/New_York (UTC−5) is 2026-11-02T05:00:00Z.
POST https://www.wixapis.com/events/v2/rsvps/search with
{ "search": { "filter": { "eventId": "<EVENT_ID>", "status": "YES", "createdDate": { "$gte": "2026-11-02T05:00:00Z" } }, "cursorPaging": { "limit": 100 } }, "fields": ["DETAILS"] }.
The count is the number of rsvps[] across pages (the response has no total); the number of
people is the sum of their totalGuests.{ "eventId": "<EVENT_ID>", "guestType": "TICKET_HOLDER", "attendanceStatus": "ATTENDING", "createdDate": { "$gte": "..." } }
counts tickets sold since then.Attendance mapping: an RSVP YES is ATTENDING, WAITLIST is IN_WAITLIST, NO is
NOT_ATTENDING; an order that is PAID, FREE or OFFLINE_PENDING is ATTENDING, and PENDING
is NOT_ATTENDING.
Confirm a change from its response, not a search. The RSVP search lags a few seconds behind
writes; to read back a guest you just added or changed, use the create or update response or the
single GET, or wait a few seconds before searching.
RSVP events — with form answers. POST https://www.wixapis.com/events/v2/rsvps/search:
{
"search": {
"filter": { "eventId": "<EVENT_ID>", "status": "YES" },
"search": { "expression": "jane", "fields": ["firstName", "lastName", "email"] },
"cursorPaging": { "limit": 100 }
},
"fields": ["DETAILS", "CONTACT_DETAILS", "FORM"]
}rsvps[] carry id, revision, status, firstName, lastName, email (with
CONTACT_DETAILS), form.inputValues (with FORM), totalGuests, guestList.guests[] (each
with an integer guestId and checkInDetails), and fullyCheckedIn. Drop search.search to list
everyone. Totals per event: GET https://www.wixapis.com/events/v2/rsvps/summaries?eventId=<EVENT_ID>
returns yesGuestCount, noGuestCount, waitlistGuestCount, totalRsvpCount.
Ticketed events — orders and tickets.
GET https://www.wixapis.com/events/v1/orders?eventId=<EVENT_ID>&fieldset=DETAILS&fieldset=TICKETS&limit=100
— orders[] with orderNumber, fullName, email, status, ticketsQuantity, totalPrice,
tickets[]. Add &fieldset=FORM for the buyer’s form answers, &status=OFFLINE_PENDING to list
unpaid pay-at-the-door orders, &searchPhrase=<name or email> to find one.GET https://www.wixapis.com/events/v1/tickets?eventId=<EVENT_ID>&fieldset=GUEST_DETAILS&fieldset=TICKET_DETAILS&limit=100
— tickets[] with ticketNumber, orderNumber, ticketDefinitionId, name, guestFullName,
checkIn, orderStatus. Add &fieldset=GUEST_FORM for each ticket holder’s answers (filled only
when the event collects one form per ticket); otherwise the answers are on the order’s FORM.GET https://www.wixapis.com/events/v1/orders/summary?eventId=<EVENT_ID>.These two are GET list calls with repeated query parameters — there is no POST .../query for
orders or tickets. Exclude archived ones with &tag=NON_ARCHIVED (orders) or
&state=ORDER_ACTIVE&state=TICKET_ACTIVE (tickets). Both page by offset: the response carries
total, offset and limit; repeat the call with &offset=<offset + limit> until you have
total items, and don’t report a list or count as complete before then.
A request that names both the guest and the event is the user’s explicit instruction to add them — proceed without asking again. Ask first when the event or the guest is ambiguous, for a mandatory form answer you don’t have, or which ticket to use when it isn’t clear. Send the usual confirmation email unless the user said not to.
Take the branch from the event’s registration.initialType (from the find query) and read only
that branch: an RSVP event never needs ticket definitions, reservations or checkout.
RSVP event. The two calls below are complete for adding a guest — no schema lookup is needed.
GET https://www.wixapis.com/events/v3/events/<EVENT_ID>?fields=FORM — read
event.form.controls[].inputs[] for each input’s name, mandatory flag and options.POST https://www.wixapis.com/events/v2/rsvps:curl -X POST 'https://www.wixapis.com/events/v2/rsvps' \
-H 'Content-Type: application/json' \
-H 'Authorization: <AUTH>' \
--data-binary '{
"rsvp": {
"eventId": "<EVENT_ID>",
"firstName": "Jane",
"lastName": "Doe",
"email": "jane@example.com",
"status": "YES",
"form": {
"inputValues": [
{ "inputName": "firstName", "value": "Jane" },
{ "inputName": "lastName", "value": "Doe" },
{ "inputName": "email", "value": "jane@example.com" }
]
},
"disableNotifications": false
}
}'form is optional — the top-level firstName,
lastName and email are enough. Once the form has extra questions, form.inputValues must
use the input names from step 1 and include every mandatory one — otherwise
400 INVALID_FORM_RESPONSE (for example input custom [''] must not be empty). Ask the user for
mandatory answers you don’t have, or send "ignoreFormValidation": true in options if they
want the guest added without them.rsvps/search call
above, with search.expression). Only if that finds one, change it instead of adding another
(Get and Update RSVP below); otherwise go straight to the create.{ "inputName": "additionalGuests", "value": "2" } and
{ "inputName": "guestNames", "values": ["Pat Lee", "Sam Lee"] } to inputValues, and
"additionalGuestDetails": { "guestCount": 2, "guestNames": ["Pat Lee", "Sam Lee"] } — only when
the form has an additional-guests control. The RSVP then counts as 3 people: totalGuests 3
and one guestList.guests[] entry each (ids 1, 2, 3; ask for them with "fields": ["DETAILS"]),
and the event’s yesGuestCount goes up by 3 while totalRsvpCount goes up by 1. A count above the
control’s maxGuests fails 400 INVALID_FORM_RESPONSE; a count without names fails
400 ADDITIONAL_GUESTS_MUST_HAVE_NAMES.428 RSVPS_CLOSED — whether or not it has a
waitlist. Offer the user the waitlist first: send "status": "WAITLIST" (works when
waitlistEnabled is on). If they want the guest to take a place anyway, retry once with both
"options": { "ignoreLimits": true, "ignoreResponseStatus": true } — ignoreLimits alone still
fails RSVPS_CLOSED, and ignoreResponseStatus alone fails GUEST_LIMIT_EXCEEDED (“guest
limit … exceeded”).428 RSVPS_CLOSED / RSVPS_NOT_STARTED; if the
user wants the guest added anyway, retry once with "options": { "ignoreResponseStatus": true }.
In both cases tell the user the limit or closed registration was overridden. Don’t send these
options up front.disableNotifications: true turns notifications off for this RSVP. The API records the flag but
doesn’t report whether an email went out, so tell the user notifications were turned off rather
than that no email was sent.428 MEMBER_ALREADY_REGISTERED / MEMBER_EMAIL_ALREADY_REGISTERED — an RSVP for this member or
email already exists; find it with the search above and change it instead.400 UNEXPECTED_RSVP_STATUS — the event only accepts “yes” (rsvp.responseType YES_ONLY, the
default); to record a “no”, switch it with PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID> and
{ "event": { "registration": { "rsvp": { "responseType": "YES_AND_NO" } } } }.428 INVALID_EVENT_TYPE — the event is ticketed; use the ticket flow below.Ticketed event. A manual guest is a reservation followed by a checkout:
fixedPrice.value "0", feeType NO_FEE). If the event has none, offer to create a hidden
free ticket for them; never put comp guests on a paid ticket.POST https://www.wixapis.com/events/v1/ticket-reservations:{
"ticketReservation": {
"tickets": [ { "ticketDefinitionId": "<TICKET_DEFINITION_ID>", "quantity": 2 } ],
"additionalReservationSettings": { "enableHiddenTicketReservation": true }
}
}additionalReservationSettings goes inside ticketReservation. Send it only when the
ticket is hidden; a hidden ticket without it — or with it at the body’s top level — fails
428 TICKET_DEFINITION_HIDDEN.GET https://www.wixapis.com/events/v3/events/<EVENT_ID>?fields=REGISTRATION until
registration.status is OPEN_TICKETS, waiting about a second between reads (up to five
reads), then reserve. Doing this in the same script as the ticket creates saves a retry.428 REGISTRATION_CLOSED on a hidden ticket: registration hasn’t opened yet (see above) or
the event has no visible ticket on sale. Wait and retry once; if it persists, tell the user the
event needs a public ticket first (or reopen registration: PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID> with
{ "event": { "registration": { "registrationPaused": false } } })."disableTicketLimitValidation": true (same object) lets the reservation exceed the ticket’s
remaining quantity and can oversell the event — send it only after the user agrees to go over
the limit.ticketReservation.id. The reservation expires at expirationDate — check out straight
away.POST https://www.wixapis.com/events/v1/checkout, with one guests[] entry per ticket, each
form built from the event’s form as for RSVPs:{
"eventId": "<EVENT_ID>",
"reservationId": "<RESERVATION_ID>",
"buyer": { "firstName": "Jane", "lastName": "Doe", "email": "jane@example.com" },
"guests": [
{ "form": { "inputValues": [ { "inputName": "firstName", "value": "Jane" }, { "inputName": "lastName", "value": "Doe" }, { "inputName": "email", "value": "jane@example.com" } ] } },
{ "form": { "inputValues": [ { "inputName": "firstName", "value": "Pat" }, { "inputName": "lastName", "value": "Lee" }, { "inputName": "email", "value": "pat@example.com" } ] } }
],
"options": { "silent": false }
}The response’s order has the orderNumber and status — FREE for free tickets.
Who holds each ticket. Unless the event collects one form per ticket, every ticket is held
by the buyer: the extra guests[] entries are not stored, and the guest query shows the buyer’s
name and email on each ticket. With registration.tickets.guestsAssignedSeparately: true (set it
with PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID> and { "event": { "registration": { "tickets": { "guestsAssignedSeparately": true } } } },
with the user’s agreement) each guests[] entry becomes that ticket’s holder, under their own name — send one
entry per ticket.
checkout.options decide how a paid ticket is settled — set them from what the user said, never
by default:
"payInPerson": true — the guest pays at the door; the order is OFFLINE_PENDING, and its total
includes the service fee when the ticket’s feeType is FEE_ADDED_AT_CHECKOUT."markAsPaid": true — only when the user says the guest already paid outside the site. It
records the order as PAID; on a comp guest it reports revenue that was never received."silent": true — no confirmation email."ignoreFormValidation": true — skip mandatory form answers the user doesn’t have.If checkout fails, delete the reservation so its tickets are released:
DELETE https://www.wixapis.com/events/v1/ticket-reservations/<RESERVATION_ID>.
PATCH https://www.wixapis.com/events/v2/rsvps/{rsvp.id} with the RSVP’s current revision:
{
"rsvp": { "id": "<RSVP_ID>", "revision": "<REVISION>", "eventId": "<EVENT_ID>", "status": "YES" }
}status from WAITLIST to YES. If that fails because the event
is full, tell the user; only when they agree to go over the limit, retry once with
"options": { "ignoreLimits": true, "ignoreResponseStatus": true }.status NO.PATCH https://www.wixapis.com/events/v2/bulk/rsvps/update with
{ "rsvps": [ { "rsvp": { "id": "<RSVP_ID>", "revision": "<REVISION>", "status": "NO" } } ], "returnEntity": true },
then check results[].itemMetadata.success and report failures.revision from a fresh read (GET https://www.wixapis.com/events/v2/rsvps/<RSVP_ID>),
never from the create response. On INVALID_REVISION (single or bulk item), read the RSVP again
and retry once.Confirm first. Deleting an RSVP can’t be undone; archiving an order or ticket hides it from the
guest list and can be reversed by sending the same call with "archived": false.
DELETE https://www.wixapis.com/events/v2/rsvps/<RSVP_ID>.POST https://www.wixapis.com/events/v2/bulk/rsvps/delete-by-filter with
{ "filter": { "eventId": "<EVENT_ID>", "id": { "$in": ["<RSVP_ID>"] } } }. Always include
eventId: the filter otherwise spans every event, and an empty filter is refused
(400 EMPTY_FILTER). It runs asynchronously and returns a jobId: wait a few seconds, then
confirm with an RSVP search — not with the event’s summaries counts, which update later.PATCH https://www.wixapis.com/events/v1/events/<EVENT_ID>/orders
with { "orderNumber": ["<ORDER_NUMBER>"], "archived": true, "fields": { "paths": ["archived"] } }
— or a single ticket, PATCH https://www.wixapis.com/events/v1/tickets with
{ "eventId": "<EVENT_ID>", "ticketNumber": ["<TICKET_NUMBER>"], "archived": true }. Archiving
does not refund the payment.POST https://www.wixapis.com/events/v2/rsvps/<RSVP_ID>/check-in with
{ "guestIds": [1] } — integer guestList.guests[].guestId values read from that RSVP (search
with DETAILS), matched to the person by fullName; not GUIDs — a GUID fails
400 Not a numeric value. For a guest you just added, create the RSVP with
"fields": ["DETAILS"]: its response already carries guestList.guests[].guestId (the RSVP’s own
guest is 1), so no extra read is needed. Undo: same body to .../cancel-check-in.POST https://www.wixapis.com/events/v1/tickets/check-in with
{ "eventId": "<EVENT_ID>", "ticketNumber": ["<TICKET_NUMBER>"] } (up to 100). Undo:
DELETE https://www.wixapis.com/events/v1/tickets/check-in?eventId=<EVENT_ID>&ticketNumber=<TICKET_NUMBER>.428 GUEST_ALREADY_CHECKED_IN / GUEST_NOT_CHECKED_IN mean there is nothing to change. Count
checked-in guests with the guest query filtered "guestDetails.checkedIn": true.
POST https://www.wixapis.com/events/v1/events/<EVENT_ID>/orders/confirm with
{ "orderNumber": ["<ORDER_NUMBER>"] } moves OFFLINE_PENDING, PENDING or INITIATED orders to
PAID and emails the tickets to the buyer. PAID and FREE orders are unchanged. Confirm with
the user first. The order number is the code shown on the order, such as FM9X-EHKF-ZO. The
response is orders[] — read status PAID there; a separate order read immediately afterwards
can come back without a status.
Say so, and point the user to the event’s Guests or Orders page in the dashboard: