Manage the ticket types ("ticket definitions") of a ticketed event. A ticket definition is one type of ticket — "General Admission", "VIP" — with its price, quantity and sale period. Individual tickets that guests bought are orders and tickets, in Manage Event Guests.
Not here:
PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID> with
{ "event": { "registration": { "registrationPaused": true } } } (Manage Wix Events).
Do not close an event's registration by ending every ticket's sale period.event.registration.tickets (ticketLimitPerOrder, taxSettings, reservationDurationInMinutes)
with the same PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID>.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.
Tickets exist only on events created with registration.initialType: "TICKETING". On an RSVP event
every call here fails 428 INVALID_EVENT_TYPE, and the event can't be converted. Find the event and
check its type first — this exact body:
paging is a sibling of filter inside query; nested inside filter it fails 400.sort — a nested registration filter or a sort
inside filter fails 400. Read the type and dates 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).If it is RSVP, create nothing in this turn: tell the user an RSVP event can't be converted, that a ticketed copy would be a new event and its RSVPs would not carry over, and ask whether to create it — and with which tickets and prices. See Manage Wix Events.
Reference pages for these calls — use them rather than searching: Query Ticket Definitions, Create Ticket Definition, Update Ticket Definition; to find the event, Query Events.
The examples use https://www.wixapis.com/events/v3/ticket-definitions. The reference's
https://www.wixapis.com/events-ticket-definitions/v3/ticket-definitions is the same API — either
base works.
Always filter by eventId — without it the query returns every event's tickets. The response is
ticketDefinitions[], with paging under metadata (not pagingMetadata). Each definition carries:
id and revision — both needed to update it;name, pricingMethod, feeType, hidden, initialLimit, salePeriod;saleStatus — SALE_SCHEDULED (not on sale yet), SALE_STARTED or SALE_ENDED;SALES_DETAILS: salesDetails.soldCount, unsoldCount (null when unlimited),
reservedCount, soldOut.Other filters: name, hidden, saleStatus, pricingMethod.free, feeType ($eq, $in, …).
To find a ticket by the name the user gave, filter eventId and name together.
POST https://www.wixapis.com/events/v3/ticket-definitions, one call per ticket type, up to 100 per
event. Create them one after another, not in parallel; on 429, wait and retry that call before
moving to the next, and count the successes before reporting. Then list the event's tickets once
(List an event's tickets) and report what is stored.
salePeriod dates are instants: "stop selling on November 15" for a New York event is the start
of that day in America/New_York — 2026-11-15T05:00:00Z — not T00:00:00Z, which is 7pm the
evening before. Convert
from the event's dateAndTimeSettings.timeZoneId, and say the local date and time you set.
Before sending, check the instant: converted back to the event's time zone it must read 00
A sale date the user gives without a year is the next such date from today — on 20 October
2026, "stop selling on December 1st" is 1 December 2026. A sale period can never end after the
event starts: check salePeriod.endDate against the event's dateAndTimeSettings.startDate
before sending, and ask if that date falls after the event starts.
Currency: use the one the user names, as given ("20 euros" is EUR) — a ticket in that
currency is accepted even when the event has no tickets yet. When the user names none, use the
event's existing currency (pricingMethod.fixedPrice.currency of its other tickets, or
registration.tickets.currency on the event). To move an event that already has tickets to
another currency, see Change the currency. The response is { "ticketDefinition": { "id", "revision": "1", "saleStatus", ... } }.
| Ticket | pricingMethod | feeType |
|---|---|---|
| Fixed price | { "fixedPrice": { "value": "25.00", "currency": "USD" } } | FEE_ADDED_AT_CHECKOUT |
| Free | { "fixedPrice": { "value": "0", "currency": "USD" } } | NO_FEE |
Donation / pay what you want (minimum may be "0") | { "guestPrice": { "value": "5.00", "currency": "USD" } } | FEE_ADDED_AT_CHECKOUT |
| Several prices in one ticket | { "pricingOptions": { "optionDetails": [ { "optionId": "<NEW_GUID>", "name": "Adult", "price": { "value": "30.00", "currency": "USD" } }, { "optionId": "<NEW_GUID>", "name": "Child", "price": { "value": "15.00", "currency": "USD" } } ] } } | FEE_ADDED_AT_CHECKOUT |
NO_FEE. Paid ticket: FEE_ADDED_AT_CHECKOUT (the guest pays the
fee on top), or FEE_INCLUDED when the user wants to absorb the fee. On 400 INVALID_FEE_TYPE,
read the error's description: feeRequired=false means the site collects no ticket fees — retry
that ticket once with NO_FEE; feeRequired=true means NO_FEE was sent on a paid ticket —
retry once with FEE_ADDED_AT_CHECKOUT.optionId on every option — a GUID you generate. Without it the create
fails with a bare 500 Internal Server Error. Keep the ids from the response: an update must send
them back.value is a string — "value": 10 fails 400 Unexpected value for field value.initialLimit; omit it for unlimited. If the event has a seating map, the seats
set the quantity and initialLimit is ignored.name is 1–30 characters, description up to 500, policyText up to 1000; a price option
name up to 200 and up to 100 options.pricingMethod.free, pricingMethod.pricingType, saleStatus,
limited, actualLimit, limitPerCheckout, salesDetails. They are ignored without an error, so
a "free" flag or a per-checkout limit you send silently does nothing. The number of tickets one
buyer can take per order is the event's registration.tickets.ticketLimitPerOrder.428 INVALID_SALE_PERIOD — salePeriod.startDate is not before endDate (on create and update).PATCH https://www.wixapis.com/events/v3/ticket-definitions/{ticketDefinition.id}. The current
revision is required (400 revision must not be empty without it). Read the ticket right before
updating and send its id, current revision, and the fields you are changing.
Price changes need "mask": "pricingMethod". The ticket update takes its field mask under the
key mask (not fieldmask); send it even though the reference schema omits it. Without it:
fixedPrice change returns 200 and bumps the revision, but the price is not changed;pricingOptions change fails 400 INVALID_PATCH … field path is illegal.A narrower path such as pricingMethod.fixedPrice.value doesn't work either. Name, description,
policy, initialLimit, hidden and salePeriod apply without a mask.
Example — change a price:
pricingMethod for the ticket's pricing kind, with
"mask": "pricingMethod". To change one price option of a several-prices ticket, send the full
pricingOptions.optionDetails array — every option, each with its existing optionId, and the new
price on the one that changes — with the same mask.name, description, policyText.initialLimit — not below the number already sold.ticketDefinition must show the new value as well as a
revision one higher — a higher revision alone proves nothing. If the value did not change, do
not report success: read the ticket again and tell the user what is stored.PATCH as above with "hidden": true to hide a ticket from the event page so it can't be bought,
or "hidden": false to show it again. A hidden ticket can still be given to guests from the
dashboard; there is no private link or code that unlocks a hidden ticket for chosen buyers — say
so if the user asks for one.
salePeriod controls when one ticket can be bought:
| The user wants | salePeriod |
|---|---|
| Tickets go on sale later | { "startDate": "<ISO>" } |
| Stop selling this ticket on a date (early bird) | { "endDate": "<ISO>" } |
| Show the ticket while it's not on sale | add "displayNotOnSale": true |
| Stop selling this ticket now | { "endDate": "<now, ISO>" } |
| Reopen an ended ticket | { "endDate": "<a future ISO date>" } — saleStatus goes from SALE_ENDED back to SALE_STARTED |
Dates are ISO-8601 strings, rounded down to the minute. After the change, read saleStatus in the
response: SALE_STARTED means it is buyable now. If a reopened ticket still reads SALE_ENDED,
also check the event: a past event, a cancelled event, or registration.registrationPaused: true
stops all sales regardless of the ticket's sale period — reopen registration with PATCH https://www.wixapis.com/events/v3/events/<EVENT_ID> and
{ "event": { "registration": { "registrationPaused": false } } }.
Ending sales for every ticket is not how to close an event's registration — pause registration on the event instead.
POST https://www.wixapis.com/events/v3/ticket-definitions/reorder, moving one ticket before or
after another; this sets the order on the event page and in the dashboard:
Send exactly one of beforeDefinitionId or afterDefinitionId (neither fails
400 INVALID_REORDER_INSTRUCTION). For a full new order, move the tickets one at a time from
second to last, each with afterDefinitionId set to the ticket that should precede it. The call
returns {}. The list call sorts by creation date by default, not by this order, so don't use it
to check the result — report the order you applied and whether every reorder call returned 2xx.
DELETE https://www.wixapis.com/events/v3/ticket-definitions/{ticketDefinitionId}.POST https://www.wixapis.com/events/v3/bulk/ticket-definitions/delete
with a single-level filter — { "filter": { "eventId": "<EVENT_ID>" } }. Unlike the Events
bulk calls, there is no inner filter. Always include eventId, or the call reaches other
events' tickets. It returns {}, and a read right afterwards can still list the tickets — wait a
few seconds and query again before reporting what was deleted.Deleting a ticket that has sales is allowed — nothing stops it. Before deleting one
(salesDetails.soldCount above 0), tell the user how many were sold and confirm; hiding it is
usually what they want. Fetching a deleted ticket afterwards returns 404.
POST https://www.wixapis.com/events/v3/ticket-definitions/currency with
{ "eventId": "<EVENT_ID>", "currency": "EUR" } changes the currency of all the event's tickets.
Amounts are not converted — 10 USD becomes 10 EUR — so update prices afterwards if the user wants
converted prices. Before calling, read the event with "fields": ["DASHBOARD"]: when
summaries.tickets.currencyLocked is true the call fails 428 CURRENCY_LOCKED — tell the user
the currency can no longer be changed. Decide from that flag, not from whether the event has orders.
POST https://www.wixapis.com/events/v3/ticket-definitions/count with
{ "filter": { "eventId": "<EVENT_ID>" }, "facet": ["saleStatus"] } returns counts per sale status,
as facets.saleStatus.counts (for example { "SALE_STARTED": 2 }).
For sold and remaining tickets, use the list call with SALES_DETAILS and sum soldCount and
unsoldCount.
Say so plainly rather than approximating:
Last updated: 22 October 2026