Eventjet GraphQL API documentation

Sale

Object

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

  1. Create an empty sale.

    You need the Company.id of 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
        }
      }
    }
    
  2. Add tickets to the sale.

    For general admission (standing room) tickets, use setSaleItemQuantity. You need the TicketType.id of each ticket type — query them via Event.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 addSeatToSale to 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 with SaleItem.canBeRemoved set to true can be removed — auto-generated fees cannot.

  3. (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 } }
      }
    }
    

    FormValueInput keys use dot notation for nested fields (e.g. address.postal_code). Boolean values must be encoded as bool:1 / bool:0.

  4. Complete the sale.

    Choose a SalePaymentType and finalize. You can query availablePaymentTypes to 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.

  1. Query the available POS readers for the company via Company.posReaders. The current user's assigned reader is also available via User.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
        }
      }
    }
    
  2. 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 } }
        }
      }
    }
    
  3. 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:

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 in items plus 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:

Mutations:

Fields

FieldDescriptionType
availablePaymentTypesAvailable payment types for this sale.[ SalePaymentType! ]!
canceledAtWhen the sale was canceled (null if not canceled).Date
committedAtWhen the sale was completed/committed (null if not yet committed).Date
committerThe staff member who completed/committed this sale.User
createdAtWhen the sale was created.Date
creatorThe staff member who created this sale.User
customerDataCustomer data (name, address, email, etc.) associated with the sale, if provided. Null if no customer data has been applied yet.CustomerData
eventsEvents associated with items in this sale.[ Event! ]!
expirationWhen the sale expires (for uncommitted sales).Date
feeBaseTotalSubtotal before fees.Money
grandTotalThe grand total of the sale.Money
hasDiscountsWhether any discounts have been applied.Boolean
idThe unique ID of the sale.ID!
invoiceDownloadUrlDownload URL for the invoice.Url
isEmptyWhether the sale has no items.Boolean
isExpiredWhether the sale is expired.Boolean
itemGroupsItems grouped by event for display purposes.[ SaleItemGroup! ]!
itemsExplicitly added tickets, products, and discounts.[ SaleItem! ]!
lineItemsAll line items including fees and other auto-generated entries.[ SaleItem! ]!
numberHuman-readable sale number for receipts and reference. Globally unique across all sales.String
numberOfItemsNumber of items in the sale.Int
paymentTypeThe payment type for this sale.SalePaymentType
stateThe current status of the sale.SaleState
ticketsDownloadUrlDownload URL for the tickets.Url
updatedAtWhen the sale was last updated.Date