Skip to content

Get API access

Last verified
Last verified Sep 25, 2026

KORONA Event exposes two versioned GraphQL contracts. Choose the contract before requesting credentials or writing queries.

Choose an API

APIUse it forEndpointAuthentication
Booking v1Customer-facing offer discovery, carts, checkout, contacts, and payment intent creation/api/graphql/booking/v1Public discovery, customer login tokens, and request access tokens depending on the operation
Management v1A limited record index and change feed for eight event and catalog resource types/api/graphql/management/v1Dedicated Management API credential with events:read and/or catalog:read
Legacy GraphQLExisting integrations that require the linked back-office user's permissions/graphqlTenant-scoped user API key and X-Tenant-Domain

The legacy /graphql endpoint remains unchanged for existing integrations. New integrations should use a versioned endpoint so their contract is explicit.

Before you start

Confirm:

  • which account (tenant) the integration belongs to
  • whether you need booking workflows or back-office synchronization
  • which Management scopes are required, if applicable
  • whether an account owner or platform maintainer can create the legacy API key for the integration user; a role with permission to manage users can create only its own key
  • the expected request volume and credential owner
  • that your client can keep secrets outside source code and browser storage
  • that curl and jq are available for the examples below

Request Management access

Management credential administration is currently support-only. There is no customer-facing credential page. Ask KORONA Event support to list metadata, issue, rotate, or revoke a dedicated Management v1 credential. Provide the account, integration name, required scopes, expiry date, and expected volume.

The complete token is returned only in the successful issue or rotate response and cannot be displayed again. Support captures that one-time token and transfers it to you through an approved secure channel. Store it in your secret manager immediately on receipt, deploy it, and confirm the successful deployment to support. KORONA Event stores only a digest and cannot recover a lost secret.

If an active secret is lost or exposed, ask support to rotate it. If the predecessor must stop working immediately, ask support to revoke it instead of waiting for the rotation grace period. Rotation preserves the credential name and scopes. The predecessor remains valid until the earlier of its existing expiry or one hour after rotation, which gives you a bounded deployment window. After you confirm the replacement is deployed, support revokes the predecessor promptly. To change scopes, ask support to issue a replacement with the required scopes, deploy the replacement, confirm deployment, and have support revoke the old credential. An expired or revoked credential cannot be rotated and must be replaced.

Authenticate Management requests

Send the credential only in the Authorization header. The credential already identifies its account; do not send a conflicting X-Tenant-Domain header.

sh
export KORONA_EVENT_API_ORIGIN='https://<api-host>'
export KORONA_EVENT_MANAGEMENT_TOKEN='<token-from-your-secret-manager>'

curl -X POST "$KORONA_EVENT_API_ORIGIN/api/graphql/management/v1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $KORONA_EVENT_MANAGEMENT_TOKEN" \
  -d '{"query":"{ events(first: 10) { nodes { id number name updatedAt discardedAt tombstone } pageInfo { endCursor hasNextPage } } }"}'

events, eventTemplates, and admissions require events:read. The five catalog roots require catalog:read. Credentials expire within one year and stop working when expired or revoked. Invalid, expired, and revoked credentials return the same unauthorized response; check credential metadata instead of trying to infer their state from the error.

Management v1 deliberately exposes only id, name, number, updatedAt, discardedAt, and tombstone for each supported record. It does not expose complete event or catalog objects, nested private relationships, contacts, orders, payments, or tenant configuration. Use it to maintain a local index or detect changes, then agree on another supported integration path if those six fields are insufficient.

Use inclusive updatedSince and the returned cursor for synchronization. Pages contain at most 100 records, are ordered by updatedAt and id, and include discarded records with discardedAt and tombstone so you can remove stale local data. Treat each record as an upsert keyed by id; if an overlapping window returns the same id more than once, apply the version with the greatest updatedAt, including its tombstone state. Use the opaque endCursor only to continue the current pagination run while hasNextPage is true. Do not reuse a cursor as the checkpoint for a later run; start the next run with an inclusive updatedSince watermark.

Use a tenant-scoped legacy API key

Use a legacy API key only for an existing /graphql integration that needs the same permissions as a back-office user.

Only account owners and platform maintainers can create a key for another user, and nobody can create a key for a more privileged user. A non-owner role with permission to manage users can create a key only for itself.

  1. Create or choose a dedicated integration user. Avoid a personal staff account so the integration survives staff changes and receives only the permissions it needs.
  2. In the back office, open Admin > Users and select the integration user.
  3. Under API keys, select Create API key.
  4. Copy the key from New API key and store it in a secret manager immediately. The complete key is shown only once; the user page subsequently shows only its final four characters.

Send the key as Authorization: Bearer <token> and send the account or shop domain in X-Tenant-Domain:

sh
curl -X POST "https://<api-host>/graphql" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-api-key>" \
  -H "X-Tenant-Domain: <your-account-domain>" \
  -d '{"query":"{ tenant { name } }"}'

The key can act only with the integration user's permissions in the account where it was created. If the key is lost, create a replacement and revoke the old key. Some older keys are marked Available in all accounts assigned to this user. Replace such a key with one key per account before revoking it when the integration still needs access to several accounts.

Use Booking v1

Booking v1 preserves the existing storefront authentication flow. The API origin is environment-specific; obtain it from your KORONA Event deployment or support contact and do not assume the documentation website is the API host. Public offer discovery can run without customer credentials but still needs the shop domain and operation identifiers such as the POS ID supplied for that shop. After login or account creation, send the returned contact authToken as Authorization: Bearer <token> for contact-authenticated operations. requestCreate returns the request's accessToken; pass that token in the operation's request id or requestId argument where the schema requires request access, rather than treating it as a Management credential. Restricted shops and other storefront rules still apply.

For Booking v1 offer discovery, use offers. The deprecated offersUnion query remains available with the same arguments and results for existing integrations.

sh
export KORONA_EVENT_API_ORIGIN='https://<api-host>'
export KORONA_EVENT_SHOP_DOMAIN='<shop-domain>'
export KORONA_EVENT_SHOP_POS_ID='<shop-pos-id>'

First, discover an offer that is visible to the supplied shop POS:

sh
DISCOVERY_RESPONSE="$(
  curl -sS -X POST "$KORONA_EVENT_API_ORIGIN/api/graphql/booking/v1" \
    -H "Content-Type: application/json" \
    -H "X-Tenant-Domain: $KORONA_EVENT_SHOP_DOMAIN" \
    --data "$(jq -n \
      --arg query 'query DiscoverOffers($posId: ID!) { offers(posId: $posId, first: 1) { edges { node { __typename ... on Admission { id } ... on Event { id } ... on EventDesc { id } ... on EventTemplate { id } ... on Product { id } ... on VoucherConfiguration { id } } } } }' \
      --arg posId "$KORONA_EVENT_SHOP_POS_ID" \
      '{query: $query, variables: {posId: $posId}}')"
)"

printf '%s\n' "$DISCOVERY_RESPONSE" | jq .

A successful discovery response contains an offer identifier and GraphQL type. The exact values depend on the shop:

json
{
  "data": {
    "offers": {
      "edges": [{ "node": { "__typename": "Event", "id": "offer-id" } }]
    }
  }
}

Convert the returned GraphQL type to the matching OfferableTypeEnum, then request that offer:

sh
export KORONA_EVENT_OFFER_ID="$(printf '%s\n' "$DISCOVERY_RESPONSE" | jq -r '.data.offers.edges[0].node.id')"
export KORONA_EVENT_OFFER_TYPE="$(printf '%s\n' "$DISCOVERY_RESPONSE" | jq -r '
  .data.offers.edges[0].node.__typename as $type |
  {Admission: "ADMISSION", Event: "EVENT", EventDesc: "EVENT_DESC", EventTemplate: "EVENT_TEMPLATE", Product: "PRODUCT", VoucherConfiguration: "VOUCHER_CONFIGURATION"}[$type]
')"

curl -sS -X POST "$KORONA_EVENT_API_ORIGIN/api/graphql/booking/v1" \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Domain: $KORONA_EVENT_SHOP_DOMAIN" \
  --data "$(jq -n \
    --arg query 'query GetOffer($id: ID!, $type: OfferableTypeEnum!, $posId: ID!) { offer(id: $id, type: $type, posId: $posId) { __typename ... on Admission { id } ... on Event { id } ... on EventDesc { id } ... on EventTemplate { id } ... on Product { id } ... on VoucherConfiguration { id } } }' \
    --arg id "$KORONA_EVENT_OFFER_ID" \
    --arg type "$KORONA_EVENT_OFFER_TYPE" \
    --arg posId "$KORONA_EVENT_SHOP_POS_ID" \
    '{query: $query, variables: {id: $id, type: $type, posId: $posId}}')" | jq .

The expected success shape is:

json
{
  "data": { "offer": { "__typename": "Event", "id": "offer-id" } }
}

The returned type and identifier match the discovery result. If the response contains "offer": null, the offer is not visible for that shop and POS, or its identifier and type do not match. Discover the offer again with the same shop context before continuing.

Read the shop context

Send X-Tenant-Domain on Booking v1 requests and query shop without an ID to read the shop selected by that domain:

graphql
query ShopContext {
  shop {
    id
    name
    currency
    availableLocales
    allowedShippingCountries
  }
}

Use currency to display shop prices and allowedShippingCountries for shipping-country choices. availableLocales starts with the shop's default language. Convert locale enum values to language tags before sending Accept-Language: for example, DE_CH becomes de-CH and EN becomes en. Keep the same domain on later booking requests. This query exposes customer-facing shop context; it does not accept a shop ID or expose storefront administration settings.

Show calendar dates

For an EVENT_TEMPLATE or ADMISSION, use bookingCalendarAvailability to show date availability and price indications in a visible calendar range. The API resolves the shop POS from the request domain; this query does not accept posId.

graphql
query CalendarDates($type: OfferableTypeEnum!, $id: ID!, $span: TsRange!, $quantity: Int!, $pricings: [PricingInput!]) {
  bookingCalendarAvailability(type: $type, id: $id, span: $span, quantity: $quantity, pricings: $pricings) {
    dates(pricings: $pricings) {
      value
      remainingQuota
      maxQuota
      defaultPriceValue
      currentPriceValue
    }
  }
}

Set id to the discovered offer ID and span to a range such as [2026-10-01,2026-11-01), which includes the first date and excludes the last. Set quantity to the number of places requested. For a selected basket, supply the same pricings variable at the root and on dates: the root selects price candidates, and dates calculates their totals. For example, a pricing entry can contain priceOriginType: "PRICE_RULE", the selected price rule's priceOriginId, and quantity: 2. Omit pricings until price categories have been selected.

remainingQuota and maxQuota describe places on each returned date. currentPriceValue is the lowest applicable price for the selected basket; defaultPriceValue is its baseline comparison price. Prices can be null when no price applies. These dates and prices are advisory, and querying them does not reserve places. Select a concrete time slot and handle cart and checkout validation before confirming a booking.

Build an offer display

Select fields in the inline fragment matching the offer's __typename. Admission, Event, EventTemplate, Product, and VoucherConfiguration expose a human-readable number, checkoutTargetState, and shopSlug { slugTranslated }. Use the number for display and the id for API operations. The slug identifies the hosted shop route; it can be absent, so keep offer selection independent of a hosted link.

For Admission, Event, and EventTemplate, bookingAvailability reports AVAILABLE, SOLD_OUT, or EXCLUSIVELY_BOOKED. Supply requestAccessToken when checking availability for an existing anonymous cart. Availability is advisory: it can change, and a template without a selected time span can report AVAILABLE before a specific slot is checked. Handle cart and checkout validation errors even after an available result.

For voucher offers, select currentPriceValue, descTranslated, and images to present the configured price, description, and images. Use the Booking v1 reference for the image subfields. Treat an offer's checkoutTargetState as a preview; the cart's checkoutPolicy determines whether the selected offers can be submitted together and which flow applies.

Booking v1 exposes ShopSlug.slugTranslated for links. Shop SEO metadata and administrative fields are outside this public contract.

Use the GraphQL API reference chooser to open the exact Booking v1 or Management v1 schema. Each reference also provides normalized Markdown, the SDL, and a machine-readable manifest.

Handle Booking credentials and errors

Keep the three Booking credentials separate:

CredentialPlacement
Contact authTokenAuthorization: Bearer <token> for contact-authenticated operations such as contactWhoAmI
Request accessTokenThe request query's id, a request mutation's input.id, or an item mutation's input.requestId, as declared by that operation
Invoice accessTokenThe invoice query's id or paymentIntentCreate's input.invoiceToken

An item mutation's input.id identifies the item; it does not replace input.requestId. Request and invoice tokens are specific to their resource type and are not interchangeable.

For mutations, select errors { key message messageTranslated } alongside the operation's result fields. message is the machine-readable error code for application decisions; key identifies the affected input path. Show messageTranslated as the localized explanation. Use your own localized fallback if that explanation is absent. The deprecated messages and messagesTranslated arrays remain available for existing clients.

A successful HTTP response does not guarantee a successful operation: check top-level GraphQL errors, then the mutation payload's errors, before using the result. Payloads are operation-specific: requestCreate returns request, requestItemCreate returns request and requestItem, and paymentIntentCreate returns payment. They do not have a generic result field. A missing response after a write leaves its outcome uncertain; do not automatically repeat checkout or payment writes without confirming the operation's retry contract.

Expected result

Your integration uses the appropriate endpoint, stores its secret safely, has only the required scopes or user permissions, and can complete a first request.

Troubleshooting

ProblemWhat to check
Management returns unauthorizedThe complete bearer token is unchanged, unexpired, and not revoked. Ask support to rotate an active credential if its one-time secret was lost.
A Management root is deniedThe credential includes events:read or catalog:read for that root.
The tenant header conflictsRemove X-Tenant-Domain; the Management credential's tenant is authoritative.
A legacy API key is unauthorizedConfirm that the key is not revoked, X-Tenant-Domain matches the account where it was created, and the linked user still has the required permissions.
Booking rejects a requestThe shop domain, request/contact token, and operation-specific storefront rules.
Synchronization misses removalsPersist updatedAt, follow cursors, and process records where tombstone is true.
Either API returns 429Wait for the Retry-After interval, then retry with exponential backoff and jitter. Reduce concurrency and avoid retrying the same request in a tight loop.