Booking API quickstart

Last verified
Last verified Sep 29, 2026

This tutorial sells two tickets for an event with a fixed date, such as a concert, and hands payment to the shop's hosted payment page. It uses seven requests: find the event, read its price categories, add the tickets to a cart, add the customer, read the required legal documents, complete checkout and check the payment. Other offers shows what changes for general admission, time slots and vouchers.

Before you start

  • A shop in KORONA Event with at least one published event that has places left and is configured for booking with online payment.
  • The shop's domain, for example tickets.example.com, and your KORONA Event API host.
  • A tool that sends HTTP POST requests with a JSON body, such as curl or your application's HTTP client.

For reservations, booking inquiries or payment at the venue, follow Complete checkout and take payment with the Booking API.

Send every request as POST https://<api-host>/api/graphql/booking/v1 with these headers:

http
Content-Type: application/json
X-Tenant-Domain: tickets.example.com
Accept-Language: en

The body is a JSON object with query and variables. How the Booking API works explains the records and tokens used below.

1. Find the event

List the shop's events. posId: "auto" selects the shop's own sales channel.

graphql
query DiscoverEvents {
  offers(posId: "auto", offerableTypes: [EVENT], first: 20) {
    edges {
      node {
        ... on Event {
          id
          nameTranslated
          span
          timeZone
          locationName
          bookingAvailability
        }
      }
    }
  }
}
json
{
  "data": {
    "offers": {
      "edges": [
        {
          "node": {
            "id": "4711",
            "nameTranslated": "Jazz evening",
            "span": "[2026-11-12T18:00:00Z,2026-11-12T21:00:00Z)",
            "timeZone": "Europe/Berlin",
            "locationName": "Main hall",
            "bookingAvailability": "AVAILABLE"
          }
        }
      ]
    }
  }
}

span is the event's start and end in UTC; show them in the event's timeZone, here 19:00 to 22:00 in Berlin. Offer only events whose bookingAvailability is AVAILABLE; SOLD_OUT and EXCLUSIVELY_BOOKED events cannot be booked.

2. Read the price categories

An event is sold in price categories, such as adult and reduced tickets. Read the event's price rules: each one is a price category with its price.

graphql
query EventPrices($id: ID!) {
  offer(id: $id, type: EVENT, posId: "auto") {
    ... on Event {
      id
      remainingQuota
      priceRules(posId: "auto") {
        id
        priceCategory {
          nameTranslated
        }
        product {
          currentPriceValue
        }
      }
    }
  }
}
json
{ "id": "4711" }
json
{
  "data": {
    "offer": {
      "id": "4711",
      "remainingQuota": "120",
      "priceRules": [
        {
          "id": "31",
          "priceCategory": { "nameTranslated": "Adult" },
          "product": { "currentPriceValue": { "amount": 2500, "currency": "EUR" } }
        },
        {
          "id": "32",
          "priceCategory": { "nameTranslated": "Reduced" },
          "product": { "currentPriceValue": { "amount": 1500, "currency": "EUR" } }
        }
      ]
    }
  }
}

Money values contain the amount in the currency's smallest unit, so 2500 is 25.00 EUR. remainingQuota is the number of places left, sent as a string.

3. Add the tickets to a cart

The first cart mutation without a requestId creates the cart. Keep the returned accessToken: it is the cart token for every later request. Add one pricing per price category the customer chose, with the price rule's id and the number of tickets.

graphql
mutation AddToCart($input: RequestItemCreateInput!) {
  requestItemCreate(input: $input) {
    request {
      accessToken
      totalGrossValue
      checkoutHold {
        expiresAt
        secondsRemaining
      }
    }
    requestItem {
      id
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "offerableType": "EVENT",
    "offerableId": "4711",
    "pricings": [
      { "quantity": 1, "priceOriginType": "PRICE_RULE", "priceOriginId": "31" },
      { "quantity": 1, "priceOriginType": "PRICE_RULE", "priceOriginId": "32" }
    ]
  }
}

The response contains the cart token, the cart total and the checkout hold. The total can be higher than the ticket prices when the venue charges fees. To add another event to the same cart, send the cart token as requestId in the next requestItemCreate.

4. Add the customer

Send the customer's details with the cart token as id. Add a billing address; mark it with billing: true.

graphql
mutation AddCustomer($input: RequestUpdateInput!) {
  requestUpdate(input: $input) {
    request {
      contact {
        email
      }
      billingAddress {
        city
      }
      checkoutPolicy {
        compatible
        targetState
      }
      requiresShipping
      customFieldsCompletion {
        allRequiredComplete
      }
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "id": "<cart token>",
    "contact": {
      "firstName": "Alex",
      "lastName": "Example",
      "email": "[email protected]",
      "customer": {
        "addresses": [
          {
            "billing": true,
            "street": "Example Street 1",
            "postalCode": "12345",
            "city": "Berlin",
            "country": "DE"
          }
        ]
      }
    }
  }
}

Check the returned cart before checkout:

FieldWhat to do
checkoutPolicy.targetState is BOOKEDContinue with this tutorial.
checkoutPolicy.compatible is falseThe cart mixes offers that cannot be checked out together. Remove the lines listed in checkoutPolicy.conflicts.
requiresShipping is trueAdd an address with shipping: true in a country from shop.allowedShippingCountries.
customFieldsCompletion.allRequiredComplete is falseCollect the required answers before checkout; see the note below the table.

When required questions are open, Collect booking and attendee details with the Booking API shows how to answer them.

Customers must accept the shop's required legal documents. Show a checkbox for each document labelled with checkboxLabelTranslated, or with nameTranslated when no label is set, and link to the document text from legalDocument(id:).

graphql
query RequiredDocuments {
  legalDocuments(required: [true], first: 20) {
    edges {
      node {
        id
        nameTranslated
        checkboxLabelTranslated
        publishedAt
      }
    }
  }
}

Keep each document's id and publishedAt for the next step. publishedAt identifies the version the customer accepted. If the shop has more than 20 required documents, page through them as the checkout guide shows.

6. Complete checkout

After the customer accepts the documents, complete checkout. The response contains the invoice and the hosted payment link.

graphql
mutation CompleteCheckout($input: RequestPaymentInitiateInput!) {
  requestPaymentInitiate(input: $input) {
    request {
      state
    }
    invoice {
      accessToken
      paymentState
      paymentLink
    }
    errors {
      key
      message
      messageTranslated
    }
  }
}
json
{
  "input": {
    "id": "<cart token>",
    "legalDocumentAcceptances": [
      { "legalDocumentId": "1", "legalDocumentVersion": "2026-09-23T19:21:22+00:00" },
      { "legalDocumentId": "2", "legalDocumentVersion": "2026-09-23T19:21:22+00:00" }
    ]
  }
}
json
{
  "data": {
    "requestPaymentInitiate": {
      "request": { "state": "BOOKED" },
      "invoice": {
        "accessToken": "<invoice token>",
        "paymentState": "REQUIRES_PAYMENT",
        "paymentLink": "https://tickets.example.com/en/payments/<token>"
      },
      "errors": null
    }
  }
}

Redirect the customer's browser to paymentLink. The customer chooses a payment method and pays on the hosted payment page, which uses the language of your Accept-Language header.

7. Check the payment

Read the invoice with the invoice token to follow the payment. paymentState becomes PAID when the payment succeeds.

graphql
query PaymentStatus($invoiceToken: IdOrNumberOrToken!) {
  invoice(id: $invoiceToken) {
    paymentState
    paymentIntentCreateError
    payments {
      state
      providerInitializationFailed
    }
    request {
      state
      checkoutHold {
        secondsRemaining
      }
      ticketsUrl: shareablePublicTicketsUrl(format: PDF)
    }
  }
}

The tickets URL can be present while ticket activation is pending. It may open the hosted ticket page, which makes the tickets available when activation finishes. Do not use the URL's presence as a signal that a PDF is ready.

Other offers

The same steps sell every kind of offer. Only finding the offer and the AddToCart variables change.

General admission. An admission without time slots, such as a day ticket, works like an event: use offerableTypes: [ADMISSION], read the price rules with ... on Admission and add the tickets with offerableType: "ADMISSION". When adding fails with an error for requestItems.0.offerableSpan, the admission has time slots.

Time slots. Event templates and admissions with time slots need a date and time. Show dates and time slots with the Booking API shows the calendar, the slots of a day and how to add a slot.

Vouchers. Find the shop's vouchers and add one with the voucher's id as offer and price origin:

graphql
query DiscoverVouchers {
  offers(posId: "auto", offerableTypes: [VOUCHER_CONFIGURATION], first: 20) {
    edges {
      node {
        ... on VoucherConfiguration {
          id
          nameTranslated
          currentPriceValue
        }
      }
    }
  }
}
json
{
  "input": {
    "offerableType": "VOUCHER_CONFIGURATION",
    "offerableId": "<voucher id>",
    "pricings": [{ "quantity": 1, "priceOriginType": "VOUCHER_CONFIGURATION", "priceOriginId": "<voucher id>" }]
  }
}

Check your work

  • You stored the cart token from step 3 and used it in steps 4 and 6.
  • The customer accepted every required legal document before step 6.
  • The customer landed on the hosted payment page from paymentLink.

Ready to Get Started?

Book a free demo or reach out — we’d love to hear from you.