Booking Service API Changes - v1.73.0

Release: v1.73.0 Date: 2026-09-04


Summary

Backward Compatible: MOSTLY — the booking v2 API gains endpoints, and lock, sale and directsale gain one new 409; the editor API removes one endpoint and changes one success response New Endpoints: 11 on booking-service (6 public session, 4 private session, 1 session configuration) and 1 on editor-service (section photo status) Modified Endpoints: v2 lock, sale and directsale answer 409 for a seat a live session holds; POST /api/section-photo/ answers 202 when the conversion queue is on; GET /api/instance-info gains two fields Removed or Deprecated: GET /api/export/{id}/pdf/{eventId}/ removed; the converter’s POST /convert_pdf route removed with it

Migration Required: NO for integrations that do not use PDF export or call the section photo endpoint directly.


Booking sessions (SEAT-1226)

A booking session is a server-held cart for one event. Seats and general-admission capacity held by a session are owned by it: no other session, and no lock or sale from the existing endpoints, can take them while the session is live.

Lifecycle

State Holds inventory Reached by
ACTIVE yes create; every lock and unlock keeps it here
PENDING_PAYMENT yes checkout; the cart is frozen and the payment grace period starts
CONFIRMED no, sold private confirm; every held line becomes SOLD in one transaction
CANCELLED no public or private cancel
EXPIRED no the hold time (ACTIVE) or the grace period (PENDING_PAYMENT) ran out

Defaults are a fifteen-minute hold and a ten-minute payment grace; both are operator settings (see the Deployment Guide). The public session payloads carry expiresAt, expiresInSeconds and serverTime, so a page can run its own countdown without trusting the browser clock; create answers expiresAt and serverTime. The private session payload carries expiresAt and expiresInSeconds, and the list summaries carry expiresInSeconds alone.

Enabling public sessions

Every public session call, get and cancel included, answers 403 with NOT_ENABLED until the organisation enables public sessions:

PUT /api/private/management/v2.0/session-config/
X-API-Key: {organizationToken}
Content-Type: application/json

{ "publicSessionsEnabled": true }

The private session endpoints work regardless of this setting.

Public endpoints

Base path /api/public/v2.0/session/. No API key: the session id returned by create is the credential for every later call, so treat it as a secret and store it where a page reload can find it again. Like the other public endpoints, these are not part of the published OpenAPI document; this page and the renderer’s BookingSessionClient are the reference.

Method Path Purpose
POST / Open a session. Body { "eventId": "...", "publicKey": "..." }, header Idempotency-Key required. Repeating the same key returns the same session.
GET /{id} State, cart with captured prices, total and the expiry countdown.
POST /{id}/lock Hold seats and general-admission capacity. Seats are additive and tolerate ids the session already owns; a GA capacity is the desired total for that area, so repeats are safe. All-or-nothing.
POST /{id}/unlock Release part of what the session holds. Owner-scoped and idempotent: lines the session does not hold are a no-op.
POST /{id}/checkout Lock anything in the (optional) body that is not held yet, then freeze the cart and start the payment grace. A repeat on a frozen session returns the same payload.
POST /{id}/cancel Release the session. From ACTIVE it releases everything; a session already frozen for payment requires the body { "startOver": true }.

create answers:

{
  "sessionId": "2c1f0e3a-...",
  "expiresAt": "2026-09-11T10:15:00",
  "serverTime": "2026-09-11T10:00:00",
  "maxSeats": 10,
  "ttlSeconds": 900
}

maxSeats caps what one session may hold, counted as seats plus general-admission capacity; ttlSeconds is the hold time the countdown restarts from.

lock, unlock and checkout take a selection:

{
  "seats": [1001, 1002],
  "groupOfSeats": [{ "id": 77, "capacity": 2 }]
}

Each list holds at most 1000 entries. Every other public call answers the session:

{
  "sessionId": "2c1f0e3a-...",
  "eventId": "8f4b...",
  "state": "ACTIVE",
  "cart": {
    "seats": [{ "id": 1001, "priceId": 12, "priceName": "Stalls" }],
    "groupOfSeats": [{ "id": 77, "capacity": 2, "priceId": 14, "priceName": "Standing" }]
  },
  "total": 145.0,
  "expiresAt": "2026-09-11T10:15:00",
  "expiresInSeconds": 842,
  "serverTime": "2026-09-11T10:00:58"
}

Prices are captured when a line is locked and carried on the cart from then on.

Private endpoints

Base path /api/private/v2.0/session/, authenticated with X-API-Key: {organizationToken} (the Secret API key from the Editor login response, sent verbatim).

Method Path Purpose
GET / List sessions for an event, newest first. Query eventId (required), state (optional filter), limit (default 50, capped at 200), offset (default 0).
GET /{id} The full session: id, eventId, state, cart, reference, total, expiresAt, expiresInSeconds, createdAt. The private payloads name the session id and carry no serverTime.
POST /{id}/confirm Convert every held line to SOLD atomically and store the order reference. Body { "reference": "..." } (optional, 255 characters). Valid from PENDING_PAYMENT; a repeat on a CONFIRMED session returns the stored result.
POST /{id}/cancel Release everything the session holds and mark it CANCELLED. Repeats on a CANCELLED or EXPIRED session return the current state.

The list answers summaries: id, state, cartSize (seats plus GA capacity), total, createdAt, expiresInSeconds.

Errors

Errors use the Problem Details body the v2 API already returns, with errorCode and, where noted, extra members.

Status errorCode When Extra members
403 NOT_ENABLED Any public call, get and cancel included, for an organisation that has not enabled public sessions
404 ENTITY_NOT_FOUND Unknown session id, or an eventId and publicKey pair that does not resolve on create
409 SEAT_CONFLICT A lock or checkout line could not be acquired, or a held line can no longer be sold on confirm; the whole call was rolled back conflicts: { seats: [...], groupOfSeats: [...] }
409 SESSION_FROZEN lock or unlock on a session frozen for payment state
409 SESSION_DEAD lock, unlock or checkout on a CONFIRMED, CANCELLED or EXPIRED session, or on one whose hold has run out; cancel on a CONFIRMED session; confirm on a CANCELLED or EXPIRED one state
409 SESSION_NOT_CHECKED_OUT confirm on a session that has not been checked out state
409 START_OVER_REQUIRED Public cancel on a frozen session without startOver: true state
422 EMPTY_CART checkout on a session that holds nothing
429 CAP_SEATS lock or checkout that would take the session past maxSeats maxSeats

get never fails on a dead session: it answers 200 with the stored state and expiresInSeconds: 0, so a page that reads a session back after a reload must check state before reusing the id. A repeat confirm on a CONFIRMED session and a repeat cancel on a CANCELLED or EXPIRED session also answer 200 with the stored state.

Effect on the existing booking endpoints

The v2 lock, sale and directsale endpoints now refuse a seat that a live booking session holds:

{
  "status": 409,
  "errorCode": "SESSION_HELD",
  "detail": "Seats are held by an active booking session",
  "conflicts": { "seats": [1001] }
}

The seat is reported rather than skipped, so a direct sale cannot silently drop part of an order. An integration that does not use sessions is unaffected: seats are only ever held by a session when one is created.

Operator switch

Operators can close the session endpoints with the Helm value global.bookingSessions.enabled (default true). While closed, the session endpoints are unavailable and existing holds still expire normally.


Section photos and conversions (SEAT-1238)

These are editor-service endpoints. They are not part of the Booking API integration surface and need an Editor login; they are listed because their contract changed for anything that drives the editor API directly.

POST /api/section-photo/

When the conversion queue is enabled (the default), the upload is accepted immediately:

HTTP/1.1 202 Accepted

{ "status": "accepted", "requestId": "..." }

Poll for the result:

GET /api/section-photo/{requestId}/

The answer is 200 with one of three statuses:

{ "status": "pending", "requestId": "..." }
{ "status": "ready", "requestId": "...", "url": "https://...", "thumbUrl": "https://..." }
{ "status": "failed", "requestId": "..." }

An unknown request id answers 400; a request id issued to another organisation answers 403. With the queue disabled the upload still answers 200 with status: "ready" and both urls directly, as before.

Removed: GET /api/export/{id}/pdf/{eventId}/

PDF export no longer exists, in the editor and in the API. Use the SVG export, GET /api/export/{id}/, which is unchanged. The converter’s POST /convert_pdf route, which never produced a document, is removed with it.

Converter routes

The converter’s own routes (POST /, /background, /thumbnail, /photo, GET /jobs/{id}) require Authorization: Bearer <value> and answer 401 without it. Editor-service sends the value itself; only a caller that talks to the converter directly needs it. /health and /metrics carry no token. Configuration is in the Deployment Guide.


Instance information (SEAT-1205)

GET /api/instance-info on editor-service gains two fields:

{
  "installationId": "...",
  "environment": "production"
}

environment is the value declared with the Helm value instance.environment, or unspecified when the deployment has not declared one.


Credential naming (SEAT-1231)

The OpenAPI document now describes the value sent in X-API-Key as the Secret API key from the Editor login response, and the renderer key as the Public API key, matching the names shown in Organization settings. Field names, header names and values are unchanged: send the value from the login response verbatim, and treat it as an opaque string.


Authentication Changes

The X-API-Key and X-Organization-ID header contract is unchanged.

The public session endpoints carry no API key. Opening a session requires the renderer public key of the event’s organisation; every later call is authenticated by the session id alone. Confirming a session is a private endpoint and needs the organisation’s Secret API key, so the confirm belongs on the integrator’s server, after payment.


Removed and Deprecated Endpoints

Endpoint Status Replacement
GET /api/export/{id}/pdf/{eventId}/ Removed GET /api/export/{id}/ (SVG)
POST /convert_pdf (converter service) Removed none; it answered 501