Sale
A sale represents a box office transaction.
While Carts are created by guests in the online shop, sales are created by staff
members in the box office. All sale operations require authentication.
Basic sale flow
-
Create an empty sale.
You need the
Company.idof the company you are selling for.mutation CreateSale($companyId: ID!) { createSale(input: { companyId: $companyId }) { sale { id # Keep this — you will pass it to every subsequent mutation. state # OPEN } } }mutation CreateSale($companyId: ID!) { createSale(input: { companyId: $companyId }) { sale { id # Keep this — you will pass it to every subsequent mutation. state # OPEN } } } -
Add tickets to the sale.
For general admission (standing room) tickets, use
setSaleItemQuantity. You need theTicketType.idof each ticket type — query them viaEvent.ticketTypes.mutation AddItems($saleId: ID!, $locale: Locale!) { setSaleItemQuantity(input: { saleId: $saleId items: [ { ticketTypeId: "tt_1", quantity: 2 } { ticketTypeId: "tt_2", quantity: 1 } ] }) { sale { items { name(locale: $locale) quantity price { amount currency { code } } } grandTotal { amount currency { code } } } } }mutation AddItems($saleId: ID!, $locale: Locale!) { setSaleItemQuantity(input: { saleId: $saleId items: [ { ticketTypeId: "tt_1", quantity: 2 } { ticketTypeId: "tt_2", quantity: 1 } ] }) { sale { items { name(locale: $locale) quantity price { amount currency { code } } } grandTotal { amount currency { code } } } } }For seated events, use
addSeatToSaleto add a specific seat:mutation AddSeat($saleId: ID!) { addSeatToSale(input: { saleId: $saleId seatmapId: "sm_1" seatId: "seat_42" ticketTypeId: "tt_1" }) { sale { items { id name(locale: "en") } } } }mutation AddSeat($saleId: ID!) { addSeatToSale(input: { saleId: $saleId seatmapId: "sm_1" seatId: "seat_42" ticketTypeId: "tt_1" }) { sale { items { id name(locale: "en") } } } }For general admission areas within a seatmap (standing zones, etc.), use
setSaleVolumeQuantities:mutation AddVolume($saleId: ID!) { setSaleVolumeQuantities(input: { saleId: $saleId seatmapId: "sm_1" volumeId: "vol_1" quantities: [ { ticketTypeId: "tt_1", quantity: 3 } ] }) { sale { items { id name(locale: "en") quantity } } } }mutation AddVolume($saleId: ID!) { setSaleVolumeQuantities(input: { saleId: $saleId seatmapId: "sm_1" volumeId: "vol_1" quantities: [ { ticketTypeId: "tt_1", quantity: 3 } ] }) { sale { items { id name(locale: "en") quantity } } } }To remove an item, use
removeItemFromSale. Only items withSaleItem.canBeRemovedset totruecan be removed — auto-generated fees cannot. -
(Optional) Attach customer data.
Customer data is optional for box office sales, but may be needed for invoice payments or confirmation emails.
mutation ApplyCustomerData($saleId: ID!) { applyCustomerDataToSale(input: { saleId: $saleId data: [ { key: "first_name", value: "Jane" } { key: "last_name", value: "Doe" } { key: "email", value: "jane@example.com" } { key: "address.postal_code", value: "1010" } ] }) { sale { customerData { firstName lastName } } } }mutation ApplyCustomerData($saleId: ID!) { applyCustomerDataToSale(input: { saleId: $saleId data: [ { key: "first_name", value: "Jane" } { key: "last_name", value: "Doe" } { key: "email", value: "jane@example.com" } { key: "address.postal_code", value: "1010" } ] }) { sale { customerData { firstName lastName } } } }FormValueInputkeys use dot notation for nested fields (e.g.address.postal_code). Boolean values must be encoded asbool:1/bool:0. -
Complete the sale.
Choose a
SalePaymentTypeand finalize. You can queryavailablePaymentTypesto see which payment types are available.mutation CompleteSale($saleId: ID!) { completeSale(input: { saleId: $saleId paymentType: CASH }) { sale { state # COMMITTED number # Human-readable sale number committedAt invoiceDownloadUrl ticketsDownloadUrl } } }mutation CompleteSale($saleId: ID!) { completeSale(input: { saleId: $saleId paymentType: CASH }) { sale { state # COMMITTED number # Human-readable sale number committedAt invoiceDownloadUrl ticketsDownloadUrl } } }Once completed, the sale is frozen and cannot be modified.
POS terminal payments
For card payments via a POS terminal reader, use a three-step flow instead of calling
completeSale directly.
-
Query the available POS readers for the company via
Company.posReaders. The current user's assigned reader is also available viaUser.posReader.query PosReaders($companyId: ID!) { company(id: $companyId) { posReaders { id label status # ACTIVE, INACTIVE, or DISCONNECTED } } }query PosReaders($companyId: ID!) { company(id: $companyId) { posReaders { id label status # ACTIVE, INACTIVE, or DISCONNECTED } } } -
Initiate the payment — this sends a charge request to the physical card reader:
mutation InitiatePayment($saleId: ID!, $readerId: ID!) { initiatePosPaymentForSale(input: { saleId: $saleId readerId: $readerId }) { payment { id # Pass this to capture or cancel. status # Expect REQUIRES_TERMINAL_ACTION initially. amount { amount currency { code } } } } }mutation InitiatePayment($saleId: ID!, $readerId: ID!) { initiatePosPaymentForSale(input: { saleId: $saleId readerId: $readerId }) { payment { id # Pass this to capture or cancel. status # Expect REQUIRES_TERMINAL_ACTION initially. amount { amount currency { code } } } } } -
After the customer taps/inserts their card and the terminal confirms, capture the payment:
mutation CapturePayment($saleId: ID!, $paymentId: String!) { capturePosPaymentForSale(input: { saleId: $saleId paymentId: $paymentId }) { payment { status } sale { state # COMMITTED number } } }mutation CapturePayment($saleId: ID!, $paymentId: String!) { capturePosPaymentForSale(input: { saleId: $saleId paymentId: $paymentId }) { payment { status } sale { state # COMMITTED number } } }If the payment should not proceed, cancel it instead with
cancelPosPaymentForSale.
Payment link flow
Instead of collecting payment at the point of sale, you can send the customer a payment
link to pay online. Set the payment type to SalePaymentType.PAYMENT_LINK using setSalePaymentType.
This transitions the sale to the PAYMENT_PENDING state. Once the customer pays (or the
link is canceled via cancelPaymentLink), the state is re-evaluated.
Canceling a sale
Use cancelSale at any point in the sale's lifecycle:
- On an uncommitted sale (
OPENorOVERDUE): transitions toWITHDRAWN. - On a committed sale (
COMMITTED): transitions toSaleState.CANCELED.
Both WITHDRAWN and SaleState.CANCELED are terminal states.
mutation CancelSale($saleId: ID!) { cancelSale(input: { saleId: $saleId }) { sale { state } success } }mutation CancelSale($saleId: ID!) { cancelSale(input: { saleId: $saleId }) { sale { state } success } }
Querying sales
Retrieve a single sale via Company.sale:
query GetSale($companyId: ID!, $saleId: ID!, $locale: Locale!) { company(id: $companyId) { sale(id: $saleId) { id number state creator { name } committer { name } grandTotal { amount currency { code } } paymentType items { name(locale: $locale) quantity price { amount currency { code } } } } } }query GetSale($companyId: ID!, $saleId: ID!, $locale: Locale!) { company(id: $companyId) { sale(id: $saleId) { id number state creator { name } committer { name } grandTotal { amount currency { code } } paymentType items { name(locale: $locale) quantity price { amount currency { code } } } } } }
List sales with filtering and pagination via Company.sales:
query ListSales($companyId: ID!, $locale: Locale!) { company(id: $companyId) { sales(first: 20, filter: { state: [OPEN, COMMITTED] search: "Jane" }) { edges { node { id number state grandTotal { amount currency { code } } createdAt } } pageInfo { hasNextPage endCursor } totalCount } } }query ListSales($companyId: ID!, $locale: Locale!) { company(id: $companyId) { sales(first: 20, filter: { state: [OPEN, COMMITTED] search: "Jane" }) { edges { node { id number state grandTotal { amount currency { code } } createdAt } } pageInfo { hasNextPage endCursor } totalCount } } }
See SaleFilter for the full list of available filter fields.
items vs lineItems
Sale exposes two item lists:
items— tickets, products, and discounts that were explicitly added. Use this to display what the staff member selected.lineItems— everything initemsplus automatically generated entries such as fees. Use this when you need the full cost breakdown.
Use itemGroups for a display-friendly grouping by event.
Root fields
Queries:
Company.sale— fetch a single sale by IDCompany.sales— paginated list with filteringCompany.posReaders— available POS terminal readers
Mutations:
Fields
| Field | Description | Type |
|---|---|---|
availablePaymentTypes | Available payment types for this sale. | [ SalePaymentType! ]! |
canceledAt | When the sale was canceled (null if not canceled). | Date |
committedAt | When the sale was completed/committed (null if not yet committed). | Date |
committer | The staff member who completed/committed this sale. | User |
createdAt | When the sale was created. | Date |
creator | The staff member who created this sale. | User |
customerData | Customer data (name, address, email, etc.) associated with the sale, if provided. Null if no customer data has been applied yet. | CustomerData |
events | Events associated with items in this sale. | [ Event! ]! |
expiration | When the sale expires (for uncommitted sales). | Date |
feeBaseTotal | Subtotal before fees. | Money |
grandTotal | The grand total of the sale. | Money |
hasDiscounts | Whether any discounts have been applied. | Boolean |
id | The unique ID of the sale. | ID! |
invoiceDownloadUrl | Download URL for the invoice. | Url |
isEmpty | Whether the sale has no items. | Boolean |
isExpired | Whether the sale is expired. | Boolean |
itemGroups | Items grouped by event for display purposes. | [ SaleItemGroup! ]! |
items | Explicitly added tickets, products, and discounts. | [ SaleItem! ]! |
lineItems | All line items including fees and other auto-generated entries. | [ SaleItem! ]! |
number | Human-readable sale number for receipts and reference. Globally unique across all sales. | String |
numberOfItems | Number of items in the sale. | Int |
paymentType | The payment type for this sale. | SalePaymentType |
state | The current status of the sale. | SaleState |
ticketsDownloadUrl | Download URL for the tickets. | Url |
updatedAt | When the sale was last updated. | Date |