Get API access
- Last verified
- Last verified Aug 30, 2026
KORONA Event exposes two versioned GraphQL contracts. Choose the contract before requesting credentials or writing queries.
Choose an API
| API | Use it for | Endpoint | Authentication |
|---|---|---|---|
| Booking v1 | Customer-facing offer discovery, carts, checkout, contacts, and payment intent creation | /api/graphql/booking/v1 | Public discovery, customer login tokens, and request access tokens depending on the operation |
| Management v1 | A limited record index and change feed for eight event and catalog resource types | /api/graphql/management/v1 | Dedicated Management API credential with events:read and/or catalog:read |
| Legacy GraphQL | Existing integrations that require the linked back-office user's permissions | /graphql | Tenant-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
curlandjqare 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.
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.
- 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.
- In the back office, open Admin > Users and select the integration user.
- Under API keys, select Create API key.
- 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:
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.
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:
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:
{
"data": {
"offers": {
"edges": [{ "node": { "__typename": "Event", "id": "offer-id" } }]
}
}
}Convert the returned GraphQL type to the matching OfferableTypeEnum, then request that offer:
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:
{
"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.
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:
| Credential | Placement |
|---|---|
Contact authToken | Authorization: Bearer <token> for contact-authenticated operations such as contactWhoAmI |
Request accessToken | The request query's id, a request mutation's input.id, or an item mutation's input.requestId, as declared by that operation |
Invoice accessToken | The 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
| Problem | What to check |
|---|---|
| Management returns unauthorized | The 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 denied | The credential includes events:read or catalog:read for that root. |
| The tenant header conflicts | Remove X-Tenant-Domain; the Management credential's tenant is authoritative. |
| A legacy API key is unauthorized | Confirm 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 request | The shop domain, request/contact token, and operation-specific storefront rules. |
| Synchronization misses removals | Persist updatedAt, follow cursors, and process records where tombstone is true. |
Either API returns 429 | Wait for the Retry-After interval, then retry with exponential backoff and jitter. Reduce concurrency and avoid retrying the same request in a tight loop. |