Booking Client (Renderer) API Changes - v1.73.0

Release: v1.73.0 Package: @seatmap.pro/renderer Date: 2026-09-04


Summary

Backward Compatible: YES — everything in this release is a new export New Features: 1 Deprecations: 0 Bug Fixes: 0

Migration Required: NO. No existing method, callback, setting or type changes meaning.


New Features

Booking session client (SEAT-1226)

The package exports a client for the public booking session API, so a booking page can hold, release and check out a buyer’s selection against a server-held cart that survives a reload and cannot be double-booked. The API itself is described in the Backend API Changes.

import {
  BookingSessionClient,
  BookingSessionError,
  selectionFromCart,
  selectionFromSession,
} from '@seatmap.pro/renderer';

Constructor

new BookingSessionClient({ baseUrl: string; publicKey: string });

baseUrl is the booking-service origin; publicKey is the organisation’s Public API key, the same value the renderer is initialised with.

Methods

create(eventId: string, idempotencyKey?: string): Promise<ICreatedBookingSession>;
get(sessionId: string): Promise<IBookingSession>;
lock(sessionId: string, selection: ISessionSelection): Promise<IBookingSession>;
unlock(sessionId: string, selection: ISessionSelection): Promise<IBookingSession>;
checkout(sessionId: string, selection?: ISessionSelection): Promise<IBookingSession>;
cancel(sessionId: string, startOver?: boolean): Promise<IBookingSession>;

create generates an idempotency key when none is passed; pass your own to make a retried page load return the same session. The session id it returns is the credential for every later call, so the page is responsible for storing it across reloads. checkout locks anything in the selection that is not held yet before freezing the cart; call it with no selection to freeze what is already held. cancel(sessionId, true) releases a session that is already frozen for payment.

Types

type BookingSessionState = 'ACTIVE' | 'PENDING_PAYMENT' | 'CONFIRMED' | 'CANCELLED' | 'EXPIRED';

interface ISessionSelection {
  seats?: number[];
  groupOfSeats?: { id: number; capacity: number }[];
}

interface ICreatedBookingSession {
  sessionId: string;
  expiresAt: string;
  serverTime: string;
  maxSeats: number;
  ttlSeconds: number;
}

interface IBookingSession {
  sessionId: string;
  eventId: string;
  state: BookingSessionState;
  cart: ISessionCart;
  total: number;
  expiresAt: string;
  expiresInSeconds: number;
  serverTime: string;
}

interface ISessionCart {
  seats: ISessionCartSeat[];
  groupOfSeats: ISessionCartGa[];
}

interface ISessionCartSeat {
  id: number;
  priceId?: number;
  priceName?: string;
}

interface ISessionCartGa {
  id: number;
  capacity: number;
  priceId?: number;
  priceName?: string;
}

interface ISessionConflicts {
  seats: number[];
  groupOfSeats: number[];
}

A capacity in ISessionSelection is the desired total for that area in this session, not a delta, so repeating a lock is safe.

Errors

Every method rejects with BookingSessionError when the service answers with an error:

class BookingSessionError extends Error {
  status: number;
  code?: string;
  conflicts?: ISessionConflicts;
}

code carries the service’s errorCode: SEAT_CONFLICT, SESSION_FROZEN, SESSION_DEAD, START_OVER_REQUIRED, NOT_ENABLED, CAP_SEATS (status 429, the selection would take the session past its seat cap, counted as seats plus general-admission capacity) and EMPTY_CART (status 422, checkout on a session that holds nothing). On a refused hold, conflicts lists the seats and areas that could not be acquired; the whole call was rolled back, so the session still holds exactly what it held before. get never rejects a session that has ended: it resolves with the stored state, so check it before reusing a stored id. A hold that has lapsed still reads ACTIVE until the sweep drains it, with expiresInSeconds at 0, and a session already frozen for payment refuses lock with SESSION_FROZEN, so a page that reuses a stored id and then locks needs state === 'ACTIVE' and expiresInSeconds > 0.

Helpers

selectionFromCart(cart: ICart): ISessionSelection;
selectionFromSession(session: IBookingSession | null): ISessionSelection;

selectionFromCart turns the cart the renderer reports into the selection lock takes, so the seats and areas a buyer picked on the map can be held in one call. selectionFromSession builds the selection that releases everything a session still holds, for passing to unlock; it returns an empty selection for null. To restore the map after a reload, read the session back and mark the seats in session.cart on the renderer.

Example

const sessions = new BookingSessionClient({
  baseUrl: 'https://booking.seatmap.pro',
  publicKey: '{publicKey}',
});

const stored = sessionStorage.getItem('seatmap-session');
const existing = stored ? await sessions.get(stored).catch(() => null) : null;
const reusable = existing !== null && existing.state === 'ACTIVE' && existing.expiresInSeconds > 0;
const sessionId = reusable ? existing.sessionId : (await sessions.create(eventId)).sessionId;
sessionStorage.setItem('seatmap-session', sessionId);

try {
  await sessions.lock(sessionId, { seats: [1001, 1002], groupOfSeats: [{ id: 77, capacity: 2 }] });
} catch (e) {
  if (e instanceof BookingSessionError && e.code === 'SEAT_CONFLICT') {
    showTaken(e.conflicts?.seats ?? []);
  }
}

const frozen = await sessions.checkout(sessionId);
startCountdown(frozen.expiresInSeconds);

Confirming the session after payment is a private call that needs the organisation’s Secret API key, so it belongs on your server: POST /api/private/v2.0/session/{id}/confirm.


Behaviour Changes

None. Existing renderer behaviour, callbacks and settings are unchanged.


Deprecations

None.