# seatmap.pro -- Complete AI Integration Guide

This document is for AI coding assistants (Claude, ChatGPT, Copilot, etc.) helping developers integrate seatmap.pro into a ticketing system or build one from scratch. Feed this document to your AI assistant before asking it to plan or implement seatmap.pro integration.

This is a single combined reference covering the entire seatmap.pro platform: overview, both renderers, the Booking API v2, and Editor integration.

## Quick Facts

| Property | Value |
|----------|-------|
| Platform | [seatmap.pro](https://seatmap.pro) |
| NPM package | `@seatmap.pro/renderer` |
| Booking API base (cloud) | `https://booking.seatmap.pro/api/private/v2.0/` |
| Editor (cloud) | `https://editor.seatmap.pro` |
| Knowledge base | [seatmap.pro/knowledge-base](https://seatmap.pro/knowledge-base/) |
| Pricing | From EUR 100/month (cloud SaaS) to EUR 20,000/year (on-premise) |
| Deployment | Cloud SaaS or On-Premise (Docker/Helm) |
| License | Commercial -- see [Terms of Service](https://seatmap.pro/terms-of-service) |

## Table of Contents

**Part 1 -- Platform Overview**

1. [What seatmap.pro Is](#1-what-seatmappro-is)
2. [Why seatmap.pro](#2-why-seatmappro)
3. [Pricing and Deployment](#3-pricing-and-deployment)
4. [Integration Architecture](#4-integration-architecture)
5. [Getting Started](#5-getting-started)
6. [Building a Ticketing System from Scratch](#6-building-a-ticketing-system-from-scratch)

**Part 2 -- Booking Renderer SDK**

7. [Booking Renderer Overview](#7-booking-renderer-overview)
8. [Booking Renderer Quick Start](#8-booking-renderer-quick-start)
9. [Key Data Models](#9-key-data-models)
10. [Complete Booking Renderer SDK Reference](#10-complete-booking-renderer-sdk-reference)
11. [Common Integration Patterns](#11-common-integration-patterns)

**Part 3 -- Admin Renderer**

12. [Admin Renderer Overview](#12-admin-renderer-overview)
13. [Admin Renderer Quick Start](#13-admin-renderer-quick-start)
14. [Modes](#14-modes)
15. [Selection Management](#15-selection-management)
16. [Category Assignment](#16-category-assignment)
17. [Seat State Management](#17-seat-state-management)
18. [Admin Renderer Settings and Callbacks](#18-admin-renderer-settings-and-callbacks)
19. [Admin Renderer Integration Patterns](#19-admin-renderer-integration-patterns)

**Part 4 -- Booking API v2**

20. [API Authentication](#20-api-authentication)
21. [Venues](#21-venues)
22. [Schemas](#22-schemas)
23. [Pricing Zones](#23-pricing-zones)
24. [Events](#24-events)
25. [Event Pricing](#25-event-pricing)
26. [Booking Operations](#26-booking-operations)
27. [Event-Level Selection](#27-event-level-selection)
28. [Management API](#28-management-api)
29. [SSO Autologin](#29-sso-autologin)
30. [Complete Integration Workflow](#30-complete-integration-workflow)
31. [Pagination](#31-pagination)

**Part 5 -- Editor Integration**

32. [Editor SSO Autologin](#32-editor-sso-autologin)
33. [Iframe Communication](#33-iframe-communication)
34. [Webhooks](#34-webhooks)
35. [Multi-Tenancy Model](#35-multi-tenancy-model)
36. [On-Premise Deployment](#36-on-premise-deployment)

**Appendix**

37. [Official Documentation and Support](#37-official-documentation-and-support)

---

# Part 1 -- Platform Overview

---

## 1. What seatmap.pro Is

seatmap.pro is an enterprise platform for creating, managing, and rendering interactive venue seating charts. It integrates into third-party ticketing systems -- it is NOT a ticketing service itself.

### Components

| Component | What It Does | Who Uses It |
|-----------|-------------|-------------|
| **Editor** | Web-based drag-and-drop tool for designing venue layouts (seats, sections, rows, GA areas, backgrounds) | Venue managers, event organizers |
| **Booking Renderer** | NPM package (`@seatmap.pro/renderer`) for embedding interactive seat maps on customer-facing ticket sales pages with cart and checkout flow | Frontend developers |
| **Admin Renderer** | Backoffice variant of the Renderer for seat state management, category assignment, and bulk operations (no cart) -- essential for tight integrations where your platform manages seat inventory directly | Admin dashboards, box office tools |
| **Booking API v2** | REST API for event management, pricing, seat availability updates, and a ready-made lock/sale/unlock booking flow | Backend developers |

### When NOT to Use seatmap.pro

- Simple numbered tickets without spatial layout (no venue map needed)
- Non-seated events where a quantity selector suffices (e.g., webinars, virtual events)
- You need a complete turnkey ticketing solution (seatmap.pro handles visualization and seat state, not payments or fulfillment)

### Security Boundary

seatmap.pro handles seating plan visualization and seat state management only. Your system handles:
- Checkout and payment processing
- Final ticket issuance and fulfillment
- User authentication and accounts
- Price calculation and discounts
- Inventory beyond seat state (e.g., merchandise, packages)

---

## 2. Why seatmap.pro

### The Shift: From SaaS Dashboards to AI-Native Tools

Cloud-only SaaS platforms were designed for an era where developers hand-wrote every integration. Their value proposition was "easy dashboard, minimal coding." But AI coding assistants have fundamentally changed this equation:

- AI assistants generate integration code in minutes, not days -- the complexity of an API is no longer a barrier
- The bottleneck is no longer "how hard is it to code" but "how well-documented and flexible is the tool"
- A product with comprehensive APIs, TypeScript types, and structured documentation gives AI assistants everything they need to produce working code immediately
- Cloud-only SaaS with limited APIs and opaque internals becomes a constraint, not an enabler

### A Visualization Framework, Not a Booking Cage

Many seating chart SaaS platforms force you into their booking model -- their states, their flows, their business logic. seatmap.pro takes a different approach.

The Booking API v2 provides two layers:

1. **Seat availability management** (always needed) -- updating which seats are available, locked, or sold so the renderer displays correct state to all users
2. **Lock/sale/unlock orchestration** (your choice) -- a ready-made state machine for teams that want a battle-tested booking flow out of the box

You always use the API to communicate seat states back to seatmap.pro. But *how* you arrive at those states -- your booking logic, checkout flow, payment handling, race condition strategy -- is entirely up to you. At its core, seatmap.pro is a **complex visualization framework** with deep domain expertise in venue seating:

- Use the Renderer SDK for visualization and the API for seat state updates
- Build your own booking logic, state management, and checkout flow on top
- Implement custom seat locking strategies that match your business rules
- Handle pricing, inventory, and payment however your platform requires

Years ago, implementing safe booking (double-booking prevention, seat locking, race condition handling) was genuinely hard and justified buying into an opinionated SaaS. Today, AI coding assistants can help you build robust booking logic tailored to your exact requirements in hours. What remains hard -- and what seatmap.pro solves -- is the visualization layer: rendering 10,000+ seats at 60fps with WebGL, handling complex venue topologies, touch gestures, zoom strategies, and the countless UX details that make a seating chart actually usable.

### Value at Every Scale

**For high-volume enterprises:** Cloud-only SaaS platforms charge per seat or per booking, meaning costs scale linearly with your success. seatmap.pro's on-premise option (EUR 20,000/year flat) eliminates per-transaction fees entirely -- whether you process 100,000 or 10,000,000 bookings, the cost stays fixed. Your infrastructure, your data, your margins.

**For small teams getting started:** Start with Cloud SaaS (from EUR 100/month) and the ready-made Booking API v2 -- you get a battle-tested lock/sale/unlock flow without building anything. As your team and requirements grow, gradually replace parts with custom logic. seatmap.pro does not penalize you for outgrowing its opinionated defaults.

### Buying the Vector, Not the Code

When evaluating seating chart solutions, the question is not "which has the easiest dashboard today" but "which product is evolving in the direction that matters for my business." Key considerations:

- **Domain depth over breadth** -- a tool focused exclusively on venue seating charts will always outpace a general-purpose feature inside a larger platform
- **Deployment flexibility** -- cloud SaaS today, on-premise tomorrow, or both. Lock-in to a single vendor's cloud is a liability
- **AI-readiness** -- comprehensive TypeScript types, structured API documentation, and clear data models make integration with AI assistants seamless
- **Full infrastructure control** -- on-premise deployment (Docker/Helm) means your data stays in your infrastructure, with no per-seat usage fees
- **No model lock-in** -- the API handles seat state updates; how you orchestrate bookings (your flows, your logic) is entirely yours

### Technical Advantages

- **WebGL-accelerated rendering** handles venues with 10,000+ seats at 60fps
- **Complex venue topology** -- sections, rows, numbered seats, General Admission zones, tables, accessible seats
- **Booking API v2** for seat availability management, with an optional ACTIVE/LOCKED/SOLD state machine for teams that want a ready-made booking flow
- **Composite key addressing** -- every seat has a natural language address (e.g., `Section B;;5;;14`)
- **Progressive loading** -- schema renders immediately, prices load asynchronously
- **Mobile-ready** -- touch gestures (pinch zoom, pan) built in via Hammer.js
- **TypeScript-first** -- full type definitions included in the NPM package
- **Framework-agnostic** -- works with React, Vue, Angular, Svelte, or vanilla JS

---

## 3. Pricing and Deployment

### Cloud SaaS

- From EUR 100/month
- Managed infrastructure (PostgreSQL, Redis, CDN)
- Automatic updates, zero maintenance
- Production: `booking.seatmap.pro` / `editor.seatmap.pro`
- Staging: `booking.seatmap.dev` / `editor.seatmap.dev`

### On-Premise (Docker/Helm)

- EUR 20,000/year -- flat fee, no per-seat or per-booking charges
- Delivered as Docker images orchestrated via Helm charts
- Components: Editor service, Booking service, PostgreSQL, Redis, image converter
- Your domain, your infrastructure, your data
- Manual updates via Helm upgrade

| Aspect | Cloud (SaaS) | On-Premise |
|--------|-------------|------------|
| API base URL | `https://booking.seatmap.pro` | Your own domain |
| Editor URL | `https://editor.seatmap.pro` | Your own domain |
| Renderer config | `env: 'production'` | `baseUrl: 'https://your-domain/api/public/v1.0/'` |
| Updates | Automatic | Manual Helm upgrade |
| SSL/TLS | Managed | Your responsibility |
| Database | Managed PostgreSQL | Your PostgreSQL instance |
| Cache | Managed Redis | Your Redis instance |

For on-premise configuration details (application.yaml, service-level settings), see [On-Premise Deployment](#36-on-premise-deployment).

---

## 4. Integration Architecture

### The 6-Step Lifecycle

```
Step 1          Step 2          Step 3          Step 4          Step 5          Step 6
Create    -->   Create    -->   Assign    -->   Show Seating -->  Select   -->   Checkout
Schema          Event           Prices          Plan to User      Seats
(Editor)        (API)           (API)           (Renderer SDK)    (SDK+API)      (SDK+API)
```

Steps 1-3 are setup (done once per event). Steps 4-6 happen on every ticket purchase.

### Two Integration Tiers

**Tier 1 -- Frontend Only (Renderer SDK + publicKey)**
- Install the NPM package, embed the renderer, handle seat selection callbacks
- The renderer internally calls seatmap.pro's public API using your `publicKey`
- You handle everything server-side (payments, inventory, fulfillment)
- Suitable for: showing seat maps, seat selection, price display

**Tier 2 -- Full Backend Integration (Renderer SDK + Booking API v2)**
- Everything in Tier 1, plus direct server-to-server API calls
- Manage events, pricing, seat locking, and sale states programmatically
- Suitable for: complete ticketing platform integration with real-time seat availability

### Authentication

| Surface | Auth Method | Header / Config |
|---------|-------------|-----------------|
| Renderer SDK | Public key (constructor param) | `publicKey: 'your-public-key'` |
| Booking API v2 (org) | Organization token | `X-API-Key: {your-organization-token}` |
| Booking API v2 (tenant) | Tenant token | `X-API-Key: {your-tenant-token}` + `X-Organization-ID: {orgId}` |
| Editor SSO | Private key | `POST /api/public/v2.0/autologin/` |
| Management API | Bearer JWT | `Authorization: Bearer {jwt}` |

---

## 5. Getting Started

Install the NPM package and embed a seat map in under 20 lines of code:

```bash
npm install @seatmap.pro/renderer
```

```typescript
import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';
import type { ICart } from '@seatmap.pro/renderer';

const renderer = new SeatmapBookingRenderer(
  document.getElementById('seatmap')!, // Must have explicit width and height
  {
    publicKey: 'your-public-key-here',
    env: 'production',
    onCartChange: (cart: ICart) => {
      console.log('Cart:', cart.seats.length, 'seats', cart.ga.length, 'GA items');
    },
  }
);

await renderer.loadEvent('your-event-uuid-here');

// Later: cleanup on unmount
renderer.destroy();
```

For the complete Booking Renderer SDK reference, see [Section 10](#10-complete-booking-renderer-sdk-reference).

For backoffice seat management (category assignment, bulk operations, box office tools), see [Section 12](#12-admin-renderer-overview).

### Implementation Guidelines for AI Assistants

When generating integration code, follow these rules:

**Extract all configuration into environment variables or a config module:**
- API keys (`publicKey`, organization tokens, private keys) -- never inline in source code
- Base URLs and environment identifiers (`production`, `stage`)
- Event IDs, schema IDs, and other runtime identifiers
- Webhook secrets and SSO tokens

Example (adapt to your language and framework):

```
SEATMAP_PUBLIC_KEY=your-public-key
SEATMAP_ORG_TOKEN=your-organization-token
SEATMAP_PRIVATE_KEY=your-private-key
SEATMAP_ENV=production
SEATMAP_API_URL=https://booking.seatmap.pro/api/private/v2.0
```

Read these from environment variables or a secrets manager -- never hardcode in source.

**Use structured logging for all seatmap.pro interactions:**
- Log API calls (method, endpoint, response status) at info level
- Log seat selection events and cart changes at debug level
- Log errors with full context (event ID, seat IDs, error response body)
- Never log tokens, API keys, or full request headers

**Prevent secret exposure:**
- Store all tokens and keys in environment variables, not in source code or config files committed to version control
- Add `.env` to `.gitignore`
- On the frontend, only the `publicKey` should be visible -- it is designed to be public. Organization tokens, tenant tokens, and private keys must stay server-side only
- Strip `X-API-Key` and `Authorization` headers from any client-side error reporting or logging
- In webhook handlers, validate signatures before processing payloads

---

## 6. Building a Ticketing System from Scratch

If starting from scratch, here is the recommended architecture using both renderers:

```
Customer-Facing Website                   Backoffice / Admin Panel
+-----------------------------------+    +-----------------------------------+
| SeatmapBookingRenderer            |    | SeatmapAdminRenderer              |
|   Renders interactive seat map    |    |   Seat state management           |
|   Cart + checkout flow            |    |   Category/zone assignment        |
|   Price-based coloring            |    |   Bulk row operations             |
|   onCartChange callbacks          |    |   Box office tools                |
+-----------------------------------+    +-----------------------------------+
          |                                          |
          v                                          v
Your Backend (Node/Python/Java/.NET)
  |
  +-- Your API
  |     |-- /api/events (list events, proxies to seatmap.pro if needed)
  |     |-- /api/checkout (receives cart, processes payment)
  |     |-- /api/unavailable-seats (returns sold/locked seats for disableSeatsByKeys)
  |
  +-- Seatmap.pro Booking API v2 Client
  |     |-- POST /booking/lock (lock seats during checkout)
  |     |-- POST /booking/sale (mark seats as sold after payment)
  |     |-- POST /booking/unlock (release seats if payment fails/times out)
  |     |-- (use the ready-made flow above, or call the API with your own logic)
  |
  +-- Your Database
        |-- Orders, users, payments
        |-- Seat state managed by seatmap.pro (updated via API)
```

### Booking Flow (Lock/Sale/Unlock)

The Booking API v2 provides a ready-made state machine for seat availability:

```
ACTIVE  --lock-->    LOCKED  --sale-->      SOLD
ACTIVE  <--unlock--  LOCKED  <--revertsale-- SOLD
```

1. Customer selects seats in the Booking Renderer
2. Your backend calls `POST /booking/lock` to reserve seats during checkout
3. Customer completes payment through your payment provider
4. Your backend calls `POST /booking/sale` to finalize, or `POST /booking/unlock` on failure/timeout

You can use this orchestration as-is, or build your own booking logic on top of the same API endpoints -- either way, the renderer reflects the current seat states to all users.

See [Booking Operations](#26-booking-operations) for complete endpoint documentation and curl examples.

---

# Part 2 -- Booking Renderer SDK

---

## 7. Booking Renderer Overview

The Booking Renderer is the primary integration point for most ticketing systems. It handles the end-user experience: viewing the venue, selecting seats, and building a cart for checkout.

| Scenario | Which Renderer |
|----------|---------------|
| Customer picks seats on your website | **Booking Renderer** |
| Customer selects GA tickets | **Booking Renderer** |
| Embedded seat map on mobile or desktop | **Booking Renderer** |
| Box office agent manages seat states | [Admin Renderer](#12-admin-renderer-overview) |
| Venue manager assigns pricing categories | [Admin Renderer](#12-admin-renderer-overview) |

Both renderers are exported from the same NPM package: `@seatmap.pro/renderer`.

---

## 8. Booking Renderer Quick Start

### Installation

```bash
npm install @seatmap.pro/renderer
# or
yarn add @seatmap.pro/renderer
```

### Minimal Integration (HTML + TypeScript)

```html
<div id="seatmap" style="width: 100%; height: 500px;"></div>
```

```typescript
import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';
import type { IExtendedSeat, ICart } from '@seatmap.pro/renderer';

const renderer = new SeatmapBookingRenderer(
  document.getElementById('seatmap')!,
  {
    publicKey: 'your-public-key-here',
    env: 'production',
    onSeatSelect: (seat: IExtendedSeat) => {
      console.log('Selected:', seat.sectionName, 'Row', seat.rowNumber, 'Seat', seat.seatName);
      // Return false to cancel selection, or a Promise<false>
    },
    onSeatDeselect: (seat: IExtendedSeat) => {
      console.log('Deselected:', seat.key);
    },
    onCartChange: (cart: ICart) => {
      console.log('Cart updated:', cart.seats.length, 'seats', cart.ga.length, 'GA items');
    },
    onSchemaDataLoaded: () => {
      console.log('Renderer ready -- seats are interactive');
    },
  }
);

// Load an event (UUID from your backend / Booking API)
await renderer.loadEvent('your-event-uuid-here');

// Later: get cart for checkout
const cart = renderer.getCart();
// Submit cart.seats and cart.ga to your backend

// Cleanup on unmount
renderer.destroy();
```

**Important**: The container element MUST have explicit width and height before initializing the renderer. Always call `destroy()` on unmount to prevent WebGL context memory leaks.

### React Integration

```tsx
import { useEffect, useRef, useState } from 'react';
import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';
import type { ICart, IExtendedSeat } from '@seatmap.pro/renderer';

function SeatmapWidget({ eventId, publicKey }: { eventId: string; publicKey: string }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const rendererRef = useRef<SeatmapBookingRenderer | null>(null);
  const [cart, setCart] = useState<ICart>({ seats: [], ga: [] });

  useEffect(() => {
    if (!containerRef.current) return;

    const renderer = new SeatmapBookingRenderer(containerRef.current, {
      publicKey,
      env: 'production',
      onCartChange: (newCart: ICart) => setCart(newCart),
      onSeatSelect: (seat: IExtendedSeat) => {
        // Optional: validate with your backend before allowing selection
      },
      minimap: { enabled: true, position: 'bottom-right' },
      loader: { enabled: true, style: 'top-bar' },
    });

    rendererRef.current = renderer;
    renderer.loadEvent(eventId);

    return () => {
      renderer.destroy();
      rendererRef.current = null;
    };
  }, [eventId, publicKey]);

  return <div ref={containerRef} style={{ width: '100%', height: '500px' }} />;
}
```

---

## 9. Key Data Models

```typescript
// Shopping cart
interface ICart {
  seats: ICartSeat[];          // Numbered seats
  ga: ICartGa[];               // General admission items
}

interface ICartSeat {
  id?: number;                 // Seat ID
  key: string;                 // Composite key: "Section B;;5;;14"
  price?: number;              // Price value
}

interface ICartGa {
  sectorId?: number;           // Section ID
  key: string;                 // Section name
  count: number;               // Number of GA tickets
  price?: number;              // Price per ticket
}

// Extended seat (received in onSeatSelect/onSeatDeselect callbacks)
interface IExtendedSeat extends ICartSeat {
  x: number; y: number;        // Canvas coordinates
  ax: number; ay: number;      // Absolute coordinates
  sectionId: number;            // Section ID
  sectorId: number;             // Sector ID (same as sectionId)
  seatName: string;             // e.g., "14"
  rowNumber: number;            // e.g., 5
  sectionName: string;          // e.g., "Section B"
  isAccessible?: boolean;       // Wheelchair-accessible
}
```

### Composite Key Format

Seats are addressed using composite keys with `;;` delimiter:
- Numbered seat: `"Section B;;5;;14"` (section name, row number, seat number)
- General admission: just the section name (e.g., `"Standing Area"`)

These keys appear in cart data, callbacks, and the Booking API v2.

---

## 10. Complete Booking Renderer SDK Reference

### Constructor

```typescript
new SeatmapBookingRenderer(
  element: HTMLElement,                          // Container DOM element
  settings?: IBookingRendererSettings,           // Configuration (see below)
  tags?: Record<string, string | number | boolean>  // Optional analytics tags
)
```

Throws `Error` if `publicKey` is not provided in settings.

### Settings (IBookingRendererSettings)

**Authentication and API:**

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `publicKey` | `string` | (required) | API public key for authentication |
| `env` | `'local' \| 'stage' \| 'production'` | `'production'` | API environment |
| `baseUrl` | `string` | (auto from env) | Custom API base URL override |

**Display:**

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `width` | `number` | container width | Fixed width in pixels |
| `height` | `number` | container height | Fixed height in pixels |
| `initialPadding` | `number` | -- | Padding on initial load |
| `padding` | `number` | -- | Normal padding |
| `hideSeats` | `boolean` | `false` | Hide all seats |
| `showRows` | `boolean` | `false` | Show row labels |
| `highlightAllOutlines` | `boolean` | `false` | Highlight all section outlines |
| `enable3DView` | `boolean` | `false` | Enable 3D perspective |
| `switchToWebGL` | `boolean` | `false` | Use WebGL instead of Canvas 2D |
| `showDebugLayer` | `boolean` | `false` | Show diagnostic overlay |

**Interaction:**

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `selectionLimit` | `number` | -- | Maximum seats that can be selected |
| `groupSize` | `number` | -- | Group selection size |
| `debounceDelay` | `number` | -- | Debounce delay for events (ms) |
| `disableZoomToEmptySpace` | `boolean` | `false` | Disable zoom when clicking empty space |
| `disableCartInteractions` | `boolean` | `false` | Disable cart changes |
| `interactionZoomStrategy` | `string \| string[]` | `'default'` | Zoom behavior on canvas click: `'default'`, `'section'`, `'next-scale'`, or array for fallback |

**Zoom:**

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `zoomSettings` | `IZoomSettings` | -- | Zoom presets, min/max pinch |
| `zoomAnimationDuration` | `number` | -- | Zoom animation time (ms) |
| `transformAnimationDuration` | `number` | -- | Transform animation time (ms) |
| `zoomToSectionMode` | `'fit' \| 'scale'` | `'fit'` | How to zoom to sections |
| `zoomToSectionFitPadding` | `number` | `0.1` | Padding when fitting (0-1) |
| `visibilitySettings` | `IVisibilitySettings` | -- | Zoom-level visibility for seats, outlines, markers |

**Minimap:**

| Setting | Type | Description |
|---------|------|-------------|
| `minimap.enabled` | `boolean` | Enable minimap |
| `minimap.position` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | Position |
| `minimap.width` | `number` | Width in pixels (default 200) |
| `minimap.opacity` | `number` | Opacity (default 0.8) |
| `minimap.showCartPins` | `boolean` | Show cart seat pins on minimap |
| `minimap.showMarkers` | `boolean` | Show markers on minimap |

**Loader:**

| Setting | Type | Description |
|---------|------|-------------|
| `loader.enabled` | `boolean` | Show loading overlay |
| `loader.style` | `'overlay' \| 'top-bar'` | Loader display style |
| `loader.showText` | `boolean` | Show loading text |
| `loader.theme` | `ILoaderTheme` | Color and size customization |

**Theming:**

| Setting | Type | Description |
|---------|------|-------------|
| `theme.bgColor` | `string` | Background color |
| `theme.priceColors` | `string[][]` | Color sequences for price categories |
| `theme.seatStyles` | `IRendererSeatStyleSettings` | Custom seat styles per state (default, hovered, selected, unavailable) |
| `theme.svgSectionStyles` | `IRendererSvgSectionStylesSetting` | Custom section outline styles |

### Event Callbacks

**Seat interactions:**

| Callback | Signature | Notes |
|----------|-----------|-------|
| `onSeatSelect` | `(seat: IExtendedSeat) => void \| boolean \| Promise<void \| boolean>` | Return `false` to cancel selection |
| `onSeatDeselect` | `(seat: IExtendedSeat) => void \| boolean \| Promise<void \| boolean>` | Return `false` to cancel deselection |
| `onSeatMouseEnter` | `(seat: IExtendedSeat) => void` | Mouse hover |
| `onSeatDebouncedEnter` | `(seat: IExtendedSeat) => void` | Debounced hover |
| `onSeatMouseLeave` | `() => void` | Mouse leave |
| `onCartChange` | `(cart: ICart, prevCart?: ICart) => void` | Cart modified |

**Section interactions:**

| Callback | Signature | Notes |
|----------|-----------|-------|
| `onSectionClick` | `(section: ISection) => void` | Section clicked |
| `onSectorMouseEnter` | `(section: ISection) => void` | Mouse hover on section |
| `onSectorMouseLeave` | `() => void` | Mouse leave section |

**Marker interactions:**

| Callback | Signature |
|----------|-----------|
| `onMarkerClick` | `(marker: IMarker, event: MouseEvent) => void` |
| `onMarkerMouseEnter` | `(marker: IMarker) => void` |
| `onMarkerMouseLeave` | `(marker: IMarker) => void` |

**Zoom and rendering:**

| Callback | Signature |
|----------|-----------|
| `onZoomStart` | `(newZoom: number, oldZoom: number) => void` |
| `onZoomEnd` | `(newZoom: number, oldZoom?: number) => void` |
| `onPan` | `(delta: IPoint, isFinish?: boolean) => void` |
| `onRedrawStart` | `() => void` |
| `onRedrawEnd` | `() => void` |

**Data loading:**

| Callback | Signature | Notes |
|----------|-----------|-------|
| `onSchemaDataLoaded` | `() => void` | Schema fully loaded, interactions enabled |
| `onLoadProgress` | `(event: ILoadProgressEvent) => void` | Loading progress (phase, percentage) |

**Custom styling:**

| Callback | Signature | Notes |
|----------|-----------|-------|
| `onBeforeSeatDraw` | `(event: IBeforeSeatDrawEvent) => ISeatStyle` | Return custom style per seat |

### Methods

**Loading:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `loadEvent` | `(eventId: string, sectorId?: number) => Promise<void>` | Load event schema and prices. Optional sectorId filters to one section. |

**Cart management:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `initCart` | `(cart: ICart) => void` | Initialize cart (e.g., restore from session) |
| `getCart` | `() => ICart` | Get current cart state |
| `clearCart` | `() => void` | Clear all selections |
| `addSeatsToCart` | `(seats: ICartSeat[]) => void` | Programmatically add seats |
| `removeSeatsFromCartByIds` | `(seatIds: number[]) => void` | Remove seats by ID |
| `addGaToCart` | `(ga: ICartGa) => void` | Add GA tickets |
| `removeGaFromCart` | `(removedGa: IRemovedCartGa) => void` | Remove GA tickets |

**Seat control:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `disableSeatsByIds` | `(seatIds: number[]) => void` | Make seats unavailable by ID |
| `disableSeatsByKeys` | `(keys: string[]) => void` | Make seats unavailable by composite key |
| `updateSeatLocks` | `(filter: SeatFilter) => void` | Lock/unlock seats by filter function |
| `seatKeysToIds` | `(keys: string[]) => number[]` | Convert composite keys to IDs |
| `setGroupSize` | `(groupSize: number) => void` | Set group selection size |
| `setExternalPrices` | `(seatsPrices?: ISeatPriceScheme[]) => void` | Override seat prices externally |

**Data retrieval:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `getPrices` | `() => IColoredPrice[]` | Get available price categories with colors |
| `getSeats` | `() => ISeatDTO[]` | Get all seats in venue |
| `getMarkedSeatsIds` | `() => number[]` | Get IDs of marked seats |

**Zoom and navigation:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `zoomIn` | `() => void` | Zoom in one step |
| `zoomOut` | `() => void` | Zoom out one step |
| `zoomToFit` | `() => void` | Fit venue in viewport |
| `zoomTo` | `(scale: number, options?: { destination?: IPoint; durationMs?: number }) => void` | Zoom to specific scale |
| `animateSequence` | `(steps: IRendererSequenceStep[]) => Promise<void>` | Run a sequence of zoom/pan/rotate animations |
| `getZoom` | `() => number` | Get current zoom level |
| `getMaxZoom` | `() => number` | Get maximum zoom |
| `getMinZoom` | `() => number` | Get minimum zoom |

**Section control:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `disableSvgSectionsByIds` | `(ids: number[], options?: { resetAll?: boolean }) => void` | Disable sections by ID |
| `enableSvgSectionsByIds` | `(ids: number[]) => void` | Enable sections by ID |
| `disableSvgSectionsByNames` | `(names: string[], options?: { resetAll?: boolean }) => void` | Disable sections by name |
| `enableSvgSectionsByNames` | `(names: string[]) => void` | Enable sections by name |
| `getSvgSectionBySelection` | `() => ISectorDTO[]` | Get sections by current selection |
| `setHighlightAllOutlines` | `(highlight: boolean) => void` | Toggle all outline highlights |
| `getHighlightAllOutlines` | `() => boolean` | Check outline highlight state |

**Markers:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `addMarker` | `(marker: IMarker) => void` | Add a custom marker |
| `removeMarker` | `(markerId: string) => void` | Remove marker by ID |
| `clearMarkers` | `() => void` | Remove all markers |
| `getMarkers` | `() => IMarker[]` | Get all markers |
| `getResolvedMarkers` | `() => IResolvedMarker[]` | Get markers with resolved coordinates |

**Minimap:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `showMinimap` | `() => void` | Show minimap |
| `hideMinimap` | `() => void` | Hide minimap |
| `toggleMinimap` | `() => void` | Toggle minimap visibility |
| `setMinimapPosition` | `(position: MinimapPosition) => void` | Set minimap position |

**Lifecycle:**

| Method | Signature | Description |
|--------|-----------|-------------|
| `destroy` | `() => void` | Clean up all resources (MUST call on unmount) |
| `getVersion` | `() => string` | Get renderer version |

### Marker Types

```typescript
interface IMarker {
  id: string;
  target:
    | { type: 'seat'; seatId: number }
    | { type: 'section'; sectionId: number }
    | { type: 'point'; x: number; y: number };
  appearance?:
    | { type: 'pin'; color?: string }
    | { type: 'circle'; radius?: number; color?: string; label?: string }
    | { type: 'custom'; svg: string; width?: number; height?: number };
  showOnCanvas?: boolean;
  showOnMinimap?: boolean;
  interactive?: boolean;
  data?: Record<string, unknown>;
}
```

---

## 11. Common Integration Patterns

### Pattern 1: Cart Sync with Server-Side Validation

```typescript
const renderer = new SeatmapBookingRenderer(container, {
  publicKey: 'your-public-key',
  onSeatSelect: async (seat) => {
    // Validate availability with your backend before allowing selection
    const response = await fetch('/api/check-availability', {
      method: 'POST',
      body: JSON.stringify({ seatKey: seat.key, eventId }),
    });
    const { available } = await response.json();
    if (!available) return false; // Cancels the selection in the renderer
  },
  onCartChange: (cart) => {
    // Sync cart with your backend on every change
    fetch('/api/sync-cart', {
      method: 'POST',
      body: JSON.stringify({ seats: cart.seats, ga: cart.ga }),
    });
  },
});
```

### Pattern 2: Showing Sold and Locked Seats

```typescript
// After renderer is ready, disable seats that are already sold/locked
renderer.loadEvent(eventId).then(async () => {
  const response = await fetch(`/api/unavailable-seats?eventId=${eventId}`);
  const { seatKeys } = await response.json();
  renderer.disableSeatsByKeys(seatKeys); // e.g., ["Section A;;1;;5", "Section A;;1;;6"]
});
```

### Pattern 3: Custom Seat Styling

```typescript
const renderer = new SeatmapBookingRenderer(container, {
  publicKey: 'your-public-key',
  onBeforeSeatDraw: (event) => {
    const { seat, state, style } = event;
    // Highlight VIP seats with a gold border
    if (seat.priceId === 'vip') {
      return { ...style, color: '#FFD700', stroke: { width: 2, color: '#B8860B', align: 'outside' } };
    }
    // Gray out locked seats
    if (seat.locked) {
      return { ...style, color: '#CCCCCC' };
    }
    return style; // Use default styling
  },
});
```

### Pattern 4: General Admission Integration

```typescript
const renderer = new SeatmapBookingRenderer(container, {
  publicKey: 'your-public-key',
  onSectionClick: (section) => {
    if (section.ga) {
      // Prompt user for quantity and add to cart
      const count = prompt(`How many tickets for ${section.name}?`);
      if (count) {
        renderer.addGaToCart({
          sectorId: section.id,
          key: section.name,
          count: parseInt(count),
          price: section.price,
        });
      }
    }
  },
});
```

### Common Mistakes

1. **Container without dimensions** -- the renderer needs explicit width and height on the container element before initialization
2. **Missing `destroy()` on unmount** -- causes WebGL context leaks; browsers typically allow only 8-16 contexts
3. **Calling methods before `onSchemaDataLoaded`** -- the renderer is not interactive until schema and prices are loaded
4. **Not handling async `onSeatSelect`** -- returning a `Promise<false>` cancels the selection; forgetting to return means the selection proceeds
5. **Ignoring `onCartChange` previous cart** -- the callback provides both `cart` and `prevCart` for diffing

---

# Part 3 -- Admin Renderer

---

## 12. Admin Renderer Overview

The [Booking Renderer](#7-booking-renderer-overview) is for end users buying tickets. The **Admin Renderer** is for your platform's internal tools:

| Scenario | Which Renderer |
|----------|---------------|
| Customer picks seats and checks out | Booking Renderer |
| Box office agent marks seats as sold/held | **Admin Renderer** |
| Venue manager assigns pricing categories to sections | **Admin Renderer** |
| Operations team disables seats for maintenance | **Admin Renderer** |
| Dashboard shows venue occupancy overview | **Admin Renderer** |
| Event setup: bulk assign categories to rows | **Admin Renderer** |

**For tight integrations**, the Admin Renderer is essential. If your ticketing platform manages seat inventory directly (not just relying on end-user self-service), you need the Admin Renderer in your backoffice alongside the Booking Renderer on your sales pages.

### Key Differences from Booking Renderer

| Feature | Admin Renderer | Booking Renderer |
|---------|---------------|-----------------|
| Purpose | Backoffice seat management | End-user ticket selection |
| Cart system | None (selection-based) | Full cart (seats + GA) |
| Coloring | Category-based (admin assigns) | Price-based (automatic) |
| Modes | 3 modes: pan, select, selectRows | Single booking mode |
| Data loading | `loadSchema()` or `loadEvent()` | `loadEvent()` only |
| Row selection | Built-in `selectRows` mode | Not available |
| Analytics | None | Built-in tracking |

Both renderers are exported from the same NPM package: `@seatmap.pro/renderer`.

---

## 13. Admin Renderer Quick Start

```typescript
import { SeatmapAdminRenderer } from '@seatmap.pro/renderer';
import type { IExtendedSeat } from '@seatmap.pro/renderer';

const renderer = new SeatmapAdminRenderer(
  document.getElementById('admin-seatmap')!,
  {
    publicKey: 'your-public-key-here',
    env: 'production',
    onSchemaDataLoaded: () => {
      renderer.setMode('select'); // Enable seat selection
    },
    onSeatsSelectionChange: (seats) => {
      console.log('Selected:', seats.length, 'seats');
    },
  }
);

// Load by schema ID (faster, no event needed)
await renderer.loadSchema(123);

// Or load by event ID
// await renderer.loadEvent('event-uuid');

// Later: cleanup
renderer.destroy();
```

**Important**: Call `setMode()` only AFTER `onSchemaDataLoaded` fires. The container element must have explicit width and height. Always call `destroy()` on unmount.

---

## 14. Modes

The admin renderer operates in three mutually exclusive modes, switched via `setMode(mode)`:

### Pan Mode (`'pan'`)

Navigation only. Drag to pan, scroll to zoom. No seat selection or hover feedback.

```typescript
renderer.setMode('pan');
```

Use for: venue overview, read-only dashboards, navigation before selecting.

### Select Mode (`'select'`)

Individual seat selection with modifier key support.

```typescript
renderer.setMode('select');
```

- **Click seat**: Select (replaces previous selection)
- **Shift + Click**: Add to selection
- **Alt + Click**: Remove from selection
- **Drag**: Rectangle selection
- **Click empty space**: Deselect all

Use for: individual seat operations, status changes, category assignment.

### Select Rows Mode (`'selectRows'`)

Selects entire rows instead of individual seats.

```typescript
renderer.setMode('selectRows');
```

- **Click any seat in a row**: Selects the entire row
- **Drag**: Selects all rows intersecting the rectangle
- **Shift/Alt modifiers**: Add/subtract rows from selection

Use for: bulk operations, row-level category assignment, efficient large-venue management.

---

## 15. Selection Management

### Reading Selection

```typescript
// Get selected seats (with coordinates, section info, category)
const seats: IExtendedSeat[] = renderer.getSeatSelection();

// Get selected sections
const sections: ISectorDTO[] = renderer.getSvgSectionBySelection();
```

### Setting Selection Programmatically

```typescript
// By seat IDs
renderer.setSeatSelection([1001, 1002, 1003]);

// By composite keys
renderer.setSeatSelection(['Section A;;1;;5', 'Section A;;1;;6']);

// Clear selection
renderer.setSeatSelection([]);

// Select sections
renderer.setSectionSelection([1, 2, 3]);
renderer.setSectionSelection(); // Clear
```

---

## 16. Category Assignment

The primary use case for the Admin Renderer. Categories are integer labels assigned to seats, visualized with distinct colors.

```typescript
// Get current selection
const seats = renderer.getSeatSelection();

// Assign category with auto-color from theme
renderer.setSeatsCategory(seats, 1);

// Assign category with custom color
renderer.setSeatsCategory(seats, 2, '#FF5722');

// Works with seat IDs or keys too
renderer.setSeatsCategory([1001, 1002], 3, '#4CAF50');
```

**Default category colors (13 predefined, wraps around):**

| Index | Color | Hex |
|-------|-------|-----|
| 0 | Purple | `#9C27B0` |
| 1 | Deep Purple | `#673AB7` |
| 2 | Indigo | `#3F51B5` |
| 3 | Blue | `#2196F3` |
| 4 | Cyan | `#00BCD4` |
| 5 | Teal | `#009688` |
| 6 | Green | `#4CAF50` |
| 7 | Lime | `#CDDC39` |
| 8 | Amber | `#FFC107` |
| 9 | Orange | `#FF9800` |
| 10 | Deep Orange | `#FF5722` |
| 11 | Red | `#F44336` |
| 12 | Pink | `#E91E63` |

Override with `theme.colorCategories` in settings.

---

## 17. Seat State Management

### Disable/Enable Seats

```typescript
// Disable by ID (visually dimmed, unselectable)
renderer.disableSeatsByIds([1001, 1002, 1003]);

// Disable by composite key
renderer.disableSeatsByKeys(['Section A;;1;;5', 'Section A;;1;;6']);

// Re-enable
renderer.enableSeatsByIds([1001, 1002, 1003]);
```

### Filter-Based Locking

```typescript
// Lock all seats without a price assignment
renderer.updateSeatLocks(seat => !seat.priceId);

// Lock all seats in a specific section
renderer.updateSeatLocks(seat => seat.sectionId === 10);

// Unlock all seats
renderer.updateSeatLocks(() => false);
```

### Section Visibility

```typescript
// Hide specific sections (show all others)
renderer.filterSvgSectionsByIds([1, 2, 3]);

// Restore hidden sections
renderer.removeFilterSvgSectionsByIds([1, 2]);

// Restore all sections
renderer.removeFilterSvgSectionsByIds();

// Disable sections entirely (grayed out, unselectable)
renderer.disableSvgSectionsByIds([4, 5]);
renderer.enableSvgSectionsByIds([4, 5]);

// Same operations by name
renderer.disableSvgSectionsByNames(['Balcony Left', 'Balcony Right']);
renderer.enableSvgSectionsByNames(['Balcony Left', 'Balcony Right']);
```

---

## 18. Admin Renderer Settings and Callbacks

`IAdminRendererSettings` extends `IRendererSettings` (same base as the booking renderer):

**Authentication and API:**

| Setting | Type | Default | Description |
|---------|------|---------|-------------|
| `publicKey` | `string` | (required) | API public key |
| `env` | `'local' \| 'stage' \| 'production'` | `'production'` | API environment |
| `baseUrl` | `string` | (auto from env) | Custom API base URL |

**Display** -- same as booking renderer: `width`, `height`, `padding`, `hideSeats`, `showRows`, `highlightAllOutlines`, `enable3DView`, `switchToWebGL`, `showDebugLayer`.

**Interaction** -- same as booking renderer: `selectionLimit`, `groupSize`, `debounceDelay`, `disableZoomToEmptySpace`, `interactionZoomStrategy`.

**Zoom, Minimap, Loader, Theme** -- same settings as booking renderer. See [Booking Renderer Settings](#settings-ibookingrenderersettings) for the full list.

**Category-specific theme:**
```typescript
{
  theme: {
    bgColor: '#f7f7f7',
    colorCategories: ['#1976D2', '#F57C00', '#C62828', '#388E3C'],
    seatStyles: {
      default: { size: 24, color: '#bbbbbb' },
      selected: {
        size: 24,
        color: '#1976D2',
        stroke: { width: 3, color: '#0D47A1', align: 'inside' },
      },
      unavailable: { size: 24, color: '#eeeeee' },
    },
  },
}
```

### Event Callbacks

All booking renderer callbacks are available. Most important for admin use:

| Callback | Signature | Notes |
|----------|-----------|-------|
| `onSchemaDataLoaded` | `() => void` | Schema ready, safe to call `setMode()` |
| `onSeatSelect` | `(seat: IExtendedSeat) => void \| boolean \| Promise<void \| boolean>` | Return `false` to cancel |
| `onSeatDeselect` | `(seat: IExtendedSeat) => void \| boolean \| Promise<void \| boolean>` | Return `false` to cancel |
| `onSeatsSelectionChange` | `(seats: ISeat[]) => void` | Fires after any selection change |
| `onSectionClick` | `(section: ISection) => void` | Section clicked |
| `onZoomEnd` | `(newZoom: number, oldZoom?: number) => void` | Zoom completed |
| `onBeforeSeatDraw` | `(event: IBeforeSeatDrawEvent) => ISeatStyle` | Custom seat styling |

---

## 19. Admin Renderer Integration Patterns

### Pattern 1: Box Office Seat Management

A backoffice tool where agents mark seats as sold, held, or released:

```typescript
const renderer = new SeatmapAdminRenderer(container, {
  publicKey: 'your-public-key',
  onSchemaDataLoaded: () => renderer.setMode('select'),
  onSeatsSelectionChange: (seats) => {
    updateSidebar(seats); // Show selected seats in your UI
  },
});

await renderer.loadEvent(eventId);

// Load current sold/locked state from your backend
const unavailable = await fetch(`/api/unavailable-seats?eventId=${eventId}`);
const { seatIds } = await unavailable.json();
renderer.disableSeatsByIds(seatIds);

// When agent clicks "Mark as Sold"
function handleMarkSold() {
  const selected = renderer.getSeatSelection();
  const seatIds = selected.map(s => s.id);

  // Call Booking API v2 to change state
  fetch('/api/booking/sale', {
    method: 'POST',
    body: JSON.stringify({ eventId, seatIds }),
  }).then(() => {
    renderer.disableSeatsByIds(seatIds); // Visually mark as unavailable
    renderer.setSeatSelection([]); // Clear selection
  });
}
```

### Pattern 2: Pricing Category Assignment

Bulk-assign pricing categories to sections or rows:

```typescript
const renderer = new SeatmapAdminRenderer(container, {
  publicKey: 'your-public-key',
  onSchemaDataLoaded: () => renderer.setMode('selectRows'),
  theme: {
    colorCategories: ['#E91E63', '#2196F3', '#4CAF50', '#FF9800'],
  },
});

await renderer.loadSchema(schemaId);

// When admin selects category from your UI
function assignCategory(categoryIndex: number) {
  const seats = renderer.getSeatSelection();
  renderer.setSeatsCategory(seats, categoryIndex);

  // Persist to backend
  const seatKeys = seats.map(s => s.key);
  fetch('/api/pricing-zones/assign', {
    method: 'POST',
    body: JSON.stringify({ schemaId, seatKeys, zoneId: categoryIndex }),
  });
}
```

### Pattern 3: React Integration

```tsx
import { useEffect, useRef, useCallback } from 'react';
import { SeatmapAdminRenderer } from '@seatmap.pro/renderer';
import type { ISeat } from '@seatmap.pro/renderer';

function AdminSeatmap({ schemaId, publicKey, onSelectionChange }: Props) {
  const containerRef = useRef<HTMLDivElement>(null);
  const rendererRef = useRef<SeatmapAdminRenderer | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const renderer = new SeatmapAdminRenderer(containerRef.current, {
      publicKey,
      env: 'production',
      onSchemaDataLoaded: () => renderer.setMode('select'),
      onSeatsSelectionChange: (seats: ISeat[]) => {
        onSelectionChange(seats);
      },
      minimap: { enabled: true, position: 'bottom-right' },
    });

    rendererRef.current = renderer;
    renderer.loadSchema(schemaId);

    return () => {
      renderer.destroy();
      rendererRef.current = null;
    };
  }, [schemaId, publicKey]);

  const switchMode = useCallback((mode: string) => {
    rendererRef.current?.setMode(mode);
  }, []);

  return (
    <div>
      <div style={{ marginBottom: 8 }}>
        <button onClick={() => switchMode('pan')}>Pan</button>
        <button onClick={() => switchMode('select')}>Select</button>
        <button onClick={() => switchMode('selectRows')}>Select Rows</button>
      </div>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
    </div>
  );
}
```

### Typical Architecture with Both Renderers

For a complete ticketing platform, you typically use both renderers:

```
Customer-facing website                    Backoffice / Admin panel
+---------------------------------+       +---------------------------------+
| SeatmapBookingRenderer          |       | SeatmapAdminRenderer            |
| - End-user seat selection       |       | - Seat state management         |
| - Cart + checkout flow          |       | - Category/zone assignment      |
| - Price-based coloring          |       | - Bulk row operations           |
| - loadEvent(eventId)            |       | - loadSchema(schemaId)          |
+---------------------------------+       +---------------------------------+
          |                                          |
          v                                          v
+-----------------------------------------------------------------+
| Your Backend                                                    |
|   +-- Booking API v2 client                                     |
|   |     POST /booking/lock   (during checkout)                  |
|   |     POST /booking/sale   (after payment)                    |
|   |     POST /booking/unlock (on timeout/failure)               |
|   +-- Your database (orders, users, payments)                   |
+-----------------------------------------------------------------+
```

---

# Part 4 -- Booking API v2

---

**Base URL (production):** `https://booking.seatmap.pro/api/private/v2.0/`
**Base URL (staging):** `https://booking.seatmap.dev/api/private/v2.0/`

## 20. API Authentication

All v2 endpoints require an `X-API-Key` header. Two token types are supported:

**Organization token:**
```
X-API-Key: {your-organization-token}
```

**Tenant token** (requires both headers):
```
X-API-Key: {your-tenant-token}
X-Organization-ID: {organizationId}
```

Tokens are provisioned through the seatmap.pro management interface or the Management API.

**Error format:** RFC 7807 Problem Details with field-level validation errors.

---

## 21. Venues

**Base path:** `/api/private/v2.0/venues/`

| Method | Path | Description | Request Body | Response |
|--------|------|-------------|--------------|----------|
| GET | `/` | List venues (paginated) | -- | `Page<Venue>` |
| GET | `/{id}` | Get venue by ID | -- | `Venue` |
| POST | `/` | Create venue | `Venue` | `Venue` (201) |
| PUT | `/` | Update venue | `Venue` (with id) | `Venue` |
| DELETE | `/{id}` | Delete venue | -- | 204 |

**Venue model:**
```json
{
  "id": 123,
  "name": "Madison Square Garden",
  "address": "4 Pennsylvania Plaza, New York, NY 10001",
  "lat": 40.7505,
  "lng": -73.9934
}
```

---

## 22. Schemas

**Base path:** `/api/private/v2.0/schemas/`

| Method | Path | Description | Request Body | Response |
|--------|------|-------------|--------------|----------|
| GET | `/` | List schemas (filter by `venueId` query param) | -- | `Page<Schema>` |
| GET | `/{id}` | Get schema by ID | -- | `Schema` |
| POST | `/` | Create schema | `Schema` | `Schema` (201) |
| PUT | `/` | Update schema | `Schema` (with id) | `Schema` |
| DELETE | `/{id}` | Delete schema | -- | 204 |
| POST | `/clone/{id}` | Clone schema (async) | -- | `UUID` (task ID) |
| GET | `/clone/result/{uuid}` | Get clone result | -- | `CloneCommand` |

### Seatmap Data

**Base path:** `/api/private/v2.0/schemas/{schemaId}/seatmaps/`

| Method | Path | Query Params | Description | Response |
|--------|------|-------------|-------------|----------|
| GET | `/` | `parentId` (optional), `type` (optional: SECTION, GA, ROW, TABLE) | Get seating groups | `List<GroupOfSeats>` |
| GET | `/seats/` | `sectionId` (required) | Get seats in a section | `Section` |

---

## 23. Pricing Zones

Schema-level color-coded tiers that define category types (e.g., "VIP", "Standard", "Economy"). These are assigned to seats/sections and later linked to monetary prices at the event level.

**Base path:** `/api/private/v2.0/schemas/{schemaId}/pricing_zones/`

| Method | Path | Description | Request Body | Response |
|--------|------|-------------|--------------|----------|
| GET | `/` | List pricing zones | -- | `Page<PricingZone>` |
| GET | `/{id}` | Get zone by ID | -- | `PricingZone` |
| POST | `/` | Create zones (bulk) | `List<PricingZone>` | `List<PricingZone>` (201) |
| PUT | `/` | Update zones (bulk) | `List<PricingZone>` | `List<PricingZone>` |
| DELETE | `/{id}` | Delete zone | -- | 204 |
| POST | `/assignments/` | Assign zones to seats/sections | `Selection` | `boolean` |

**PricingZone model:**
```json
{
  "id": 1,
  "name": "VIP",
  "color": "#FF5733"
}
```

**Selection (assignment) body:**
```json
{
  "seats": [
    { "id": 1001, "capacity": null }
  ],
  "groupOfSeats": [
    { "id": 2001, "capacity": 500 }
  ]
}
```

---

## 24. Events

Events bind a schema to a specific date/show. Each event has its own pricing and seat state.

**Base path:** `/api/private/v2.0/events/`

| Method | Path | Query Params | Description | Request Body | Response |
|--------|------|-------------|-------------|--------------|----------|
| GET | `/` | `id`, `schemaId`, `name`, `startDate`, `endDate`, `page`, `size`, `sort` | List events | -- | `Page<EventExpand>` |
| GET | `/{id}` | -- | Get event by UUID | -- | `EventExpand` |
| POST | `/` | -- | Create event | `Event` | `Event` (201) |
| PUT | `/` | -- | Update event | `Event` (with id) | `Event` |
| DELETE | `/{id}` | -- | Delete event | -- | 204 |

**Event model:**
```json
{
  "id": "a4a75361-7823-4847-bf22-336843022e80",
  "name": "Concert - Jan 15 Evening",
  "schemaId": 456,
  "startDate": "2026-01-15T19:00:00Z",
  "endDate": "2026-01-15T23:00:00Z"
}
```

---

## 25. Event Pricing

Monetary prices assigned to seats for a specific event. Prices reference pricing zones defined at the schema level.

**Base path:** `/api/private/v2.0/event/{eventId}/prices/`

| Method | Path | Description | Request Body | Response |
|--------|------|-------------|--------------|----------|
| GET | `/` | List prices (paginated) | -- | `Page<Price>` |
| GET | `/{id}` | Get price by ID | -- | `Price` |
| POST | `/` | Create prices (bulk) | `List<Price>` | `List<Price>` (201) |
| PUT | `/` | Update prices (bulk) | `List<Price>` | `List<Price>` |
| DELETE | `/{id}` | Delete price | -- | 204 |
| POST | `/assignments/` | Assign prices to seats | `Selection` | `boolean` |
| GET | `/assignments/` | Get all assignments with states | -- | `AssignmentOnEvent` |
| DELETE | `/assignments/` | Remove specific assignments | `CleanAssignment` | `boolean` |
| DELETE | `/assignments/all/` | Remove all assignments | -- | `boolean` |

**Price model:**
```json
{
  "id": 10,
  "name": "VIP",
  "price": 150.00,
  "currency": "EUR"
}
```

**CleanAssignment body:**
```json
{
  "seatIds": [1001, 1002, 1003],
  "groupOfSeatIds": [2001, 2002]
}
```

---

## 26. Booking Operations

The core booking flow: lock seats during checkout, mark as sold after payment, or unlock if the transaction fails.

**Base path:** `/api/private/v2.0/booking/`

| Method | Path | Query Param | Description |
|--------|------|-------------|-------------|
| POST | `/lock` | `eventId` (UUID, required) | Lock seats/GA -- transitions to LOCKED state |
| POST | `/unlock` | `eventId` (UUID, required) | Unlock seats/GA -- transitions back to ACTIVE state |
| POST | `/sale` | `eventId` (UUID, required) | Mark as sold -- transitions to SOLD state |
| POST | `/revertsale` | `eventId` (UUID, required) | Revert sale -- transitions back to ACTIVE state |

All four endpoints use the same request body:

**StateSelection body:**
```json
{
  "sessionId": "user-session-abc123",
  "seats": [
    { "id": 1001 },
    { "id": 1002 }
  ],
  "groupOfSeats": [
    { "id": 2001, "capacity": 3 }
  ]
}
```

- `sessionId` -- unique session identifier for lock ownership
- `seats` -- array of individual numbered seats (by seat ID)
- `groupOfSeats` -- array of GA sections (by section ID + ticket count in `capacity`)

**State transitions:**
```
ACTIVE  --lock-->    LOCKED  --sale-->      SOLD
ACTIVE  <--unlock--  LOCKED  <--revertsale-- SOLD
```

### Complete Booking Flow Example

```bash
# 1. Lock seats during checkout (user has 5 minutes to pay)
curl -X POST "https://booking.seatmap.pro/api/private/v2.0/booking/lock?eventId=a4a75361-7823-4847-bf22-336843022e80" \
  -H "X-API-Key: your-organization-token" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "checkout-session-xyz",
    "seats": [{"id": 1001}, {"id": 1002}],
    "groupOfSeats": []
  }'

# 2a. Payment succeeded -- mark as sold
curl -X POST "https://booking.seatmap.pro/api/private/v2.0/booking/sale?eventId=a4a75361-7823-4847-bf22-336843022e80" \
  -H "X-API-Key: your-organization-token" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "checkout-session-xyz",
    "seats": [{"id": 1001}, {"id": 1002}],
    "groupOfSeats": []
  }'

# 2b. OR payment failed/timed out -- unlock seats
curl -X POST "https://booking.seatmap.pro/api/private/v2.0/booking/unlock?eventId=a4a75361-7823-4847-bf22-336843022e80" \
  -H "X-API-Key: your-organization-token" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "checkout-session-xyz",
    "seats": [{"id": 1001}, {"id": 1002}],
    "groupOfSeats": []
  }'
```

---

## 27. Event-Level Selection

Assign or remove pricing zone assignments at the event level (overrides schema-level defaults).

**Base path:** `/api/private/v2.0/event/{eventId}/`

| Method | Path | Description | Request Body |
|--------|------|-------------|--------------|
| POST | `/selection/` | Assign pricing zones to seats for this event | `List<Assignment>` |
| POST | `/unselection/` | Remove event-specific pricing assignments | `List<Assignment>` |

**Assignment body:**
```json
[
  {
    "objectId": 1001,
    "assignmentId": 501,
    "activeCount": null
  }
]
```

- `objectId` -- seat ID or section ID
- `assignmentId` -- pricing zone ID
- `activeCount` -- for GA areas: number of active tickets

---

## 28. Management API

For creating and managing organizations and tenants. Uses Bearer JWT authentication.

**Base path:** `/api/private/management/v2.0/organizations/`

| Method | Path | Description | Request Body | Response |
|--------|------|-------------|--------------|----------|
| GET | `/` | List organizations | -- | `Page<Organization>` |
| GET | `/{id}` | Get organization by ID | -- | `Organization` |
| POST | `/` | Create organization (with initial user) | `OrganizationWithUser` | `Organization` (201) |
| PUT | `/` | Update organization | `Organization` | `Organization` |
| DELETE | `/{id}` | Delete organization | -- | 204 |

---

## 29. SSO Autologin

Public endpoint for Single Sign-On into the Editor.

**Path:** `POST /api/public/v2.0/autologin/`

**Request body:**
```json
{
  "login": "user@example.com",
  "firstName": "Jane",
  "lastName": "Doe",
  "token": "your-private-key-here"
}
```

**Response:**
```json
{
  "token": "jwt-access-token",
  "refreshToken": "jwt-refresh-token",
  "sessionId": "session-uuid",
  "userId": 123,
  "organizationId": 456
}
```

Use the returned tokens to launch the Editor in an iframe:
```html
<iframe src="https://editor.seatmap.pro?token={token}&refreshToken={refreshToken}"></iframe>
```

See [Editor SSO Autologin](#32-editor-sso-autologin) for the full iframe communication protocol.

---

## 30. Complete Integration Workflow

End-to-end steps for setting up and using seatmap.pro with your ticketing system:

1. **Create organization** -- `POST /management/v2.0/organizations/` (one-time setup)
2. **Design venue layout** -- use the Editor (web UI or embedded via iframe with SSO)
3. **Create event** -- `POST /events/` linking to the schema created in the Editor
4. **Create prices** -- `POST /event/{eventId}/prices/` with your price tiers
5. **Assign prices to seats** -- `POST /event/{eventId}/prices/assignments/`
6. **Embed renderer** -- install `@seatmap.pro/renderer`, call `loadEvent(eventId)` (see [Booking Renderer Quick Start](#8-booking-renderer-quick-start))
7. **Handle seat selection** -- use `onSeatSelect`, `onCartChange` callbacks
8. **Lock seats** -- `POST /booking/lock` when user starts checkout
9. **Process payment** -- your payment provider
10. **Confirm sale** -- `POST /booking/sale` after successful payment; or `POST /booking/unlock` on failure

---

## 31. Pagination

List endpoints support standard pagination parameters:

| Parameter | Type | Description |
|-----------|------|-------------|
| `page` | `int` | Page number (0-indexed) |
| `size` | `int` | Page size |
| `sort` | `string` | Sort field and direction (e.g., `name,asc`) |

Response includes standard Spring `Page<T>` wrapper with `content`, `totalElements`, `totalPages`, `number`, `size`.

---

# Part 5 -- Editor Integration

---

Use this section when your platform needs to let venue managers or event organizers create and edit seating layouts without leaving your application.

## 32. Editor SSO Autologin

The Editor supports Single Sign-On so your users can be authenticated directly from your platform without creating separate seatmap.pro accounts.

### Flow

```
Your Backend                          seatmap.pro
    |                                      |
    +-- POST /management/v2.0/             |
    |   organizations/ (one-time)    -->   Create organization
    |                                      |
    +-- POST /api/public/v2.0/             |
    |   autologin/                   -->   Returns JWT tokens
    |                                      |
    +-- Embed iframe with tokens     -->   Editor loads with authenticated session
```

### Step 1: Ensure Organization Exists (one-time)

```bash
curl -X POST "https://booking.seatmap.pro/api/private/management/v2.0/organizations/" \
  -H "Authorization: Bearer {management-jwt}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Your Company",
    "user": {
      "login": "admin@yourcompany.com",
      "firstName": "Admin",
      "lastName": "User"
    }
  }'
```

### Step 2: Obtain JWT Tokens via Autologin

```bash
curl -X POST "https://booking.seatmap.pro/api/public/v2.0/autologin/" \
  -H "Content-Type: application/json" \
  -d '{
    "login": "user@yourcompany.com",
    "firstName": "Jane",
    "lastName": "Doe",
    "token": "your-private-key-here"
  }'
```

**Response:**
```json
{
  "token": "eyJhbGciOiJIUzI1NiJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
  "sessionId": "550e8400-e29b-41d4-a716-446655440000",
  "userId": 123,
  "organizationId": 456
}
```

### Step 3: Embed the Editor in an Iframe

```html
<iframe
  id="seatmap-editor"
  src="https://editor.seatmap.pro?token={token}&refreshToken={refreshToken}"
  style="width: 100%; height: 800px; border: none;"
  allow="clipboard-write"
></iframe>
```

### Complete SSO Integration (Node.js)

```typescript
import express from 'express';

const app = express();
const SEATMAP_API = 'https://booking.seatmap.pro';
const SEATMAP_PRIVATE_KEY = process.env.SEATMAP_PRIVATE_KEY!;

app.get('/editor/session', async (req, res) => {
  // Get your authenticated user from session/JWT
  const user = req.user;

  // Obtain seatmap.pro tokens
  const response = await fetch(`${SEATMAP_API}/api/public/v2.0/autologin/`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      login: user.email,
      firstName: user.firstName,
      lastName: user.lastName,
      token: SEATMAP_PRIVATE_KEY,
    }),
  });

  const { token, refreshToken } = await response.json();

  // Return iframe URL to your frontend
  res.json({
    editorUrl: `https://editor.seatmap.pro?token=${token}&refreshToken=${refreshToken}`,
  });
});
```

---

## 33. Iframe Communication

The Editor supports bidirectional `postMessage` communication when embedded as an iframe. This allows your application to trigger actions in the Editor and receive notifications about user activity.

### Parent -> Editor Messages

Send messages from your application to the Editor:

```typescript
const editorFrame = document.getElementById('seatmap-editor') as HTMLIFrameElement;

// Trigger a save
editorFrame.contentWindow?.postMessage({ type: 'SAVE_REQUEST' }, '*');

// Request SVG export (schema must be saved first)
editorFrame.contentWindow?.postMessage({ type: 'FETCH_SVG' }, '*');

// Notify editor that a modal is closing
editorFrame.contentWindow?.postMessage({ type: 'MODAL_CLOSING' }, '*');
```

| Message Type | Purpose |
|-------------|---------|
| `SAVE_REQUEST` | Trigger schema save (replaces deprecated `'SAVE'` string message) |
| `FETCH_SVG` | Request SVG export of the current schema |
| `MODAL_CLOSING` | Notify the Editor that an external modal is closing |

### Editor -> Parent Messages

Listen for notifications from the Editor:

```typescript
window.addEventListener('message', (event) => {
  // Validate origin in production
  if (event.origin !== 'https://editor.seatmap.pro') return;

  const { type, payload } = event.data;

  switch (type) {
    case 'SCHEMA_CHANGED':
      // payload: { hasChanges: boolean }
      console.log('Unsaved changes:', payload.hasChanges);
      break;

    case 'SEATMAP_UPDATE_SUCCESS':
      console.log('Venue graphics saved successfully');
      break;

    case 'SEATMAP_UPDATE_ERROR':
      console.error('Failed to save venue graphics');
      break;

    case 'SCHEMA_UPDATE_SUCCESS':
      console.log('Schema metadata saved');
      break;

    case 'SCHEMA_UPDATE_ERROR':
      console.error('Failed to save schema metadata');
      break;

    case 'UPDATE_PRICE_ASSIGNMENTS_SUCCESS':
      console.log('Pricing saved');
      break;

    case 'UPDATE_PRICE_ASSIGNMENTS_ERROR':
      console.error('Pricing update failed:', payload);
      break;

    case 'PUBLISH_REQUEST':
      // payload: ISchemaSettings
      console.log('User requested publication:', payload);
      break;

    case 'SVG_RESPONSE':
      // payload: { success: boolean, svg?: string, error?: string }
      if (payload.success) {
        console.log('SVG exported:', payload.svg);
      }
      break;
  }
});
```

| Message Type | Payload | Purpose |
|-------------|---------|---------|
| `SCHEMA_CHANGED` | `{ hasChanges: boolean }` | Unsaved modifications detected |
| `SCHEMA_UPDATE_SUCCESS` | -- | Schema metadata saved |
| `SCHEMA_UPDATE_ERROR` | -- | Schema metadata save failed |
| `SEATMAP_UPDATE_SUCCESS` | -- | Venue graphics saved |
| `SEATMAP_UPDATE_ERROR` | -- | Venue graphics save failed |
| `UPDATE_PRICE_ASSIGNMENTS_SUCCESS` | -- | Pricing saved |
| `UPDATE_PRICE_ASSIGNMENTS_ERROR` | `{ payload }` | Pricing update failed |
| `PUBLISH_REQUEST` | `{ payload: ISchemaSettings }` | User initiated publication |
| `SVG_RESPONSE` | `{ success, svg?, error? }` | SVG export result |

### URL Parameters for Customization

Control the Editor's UI by appending query parameters to the iframe URL:

| Parameter | Example | Description |
|-----------|---------|-------------|
| `hidden` | `?hidden=pricing,select,label,line,polygon` | Hide specific menu items (comma-separated) |
| `hideNavbar` | `?hideNavbar=true` | Hide the top navigation bar |
| `showPricing` | `?showPricing=true` | Force the pricing tool to be visible |

**Example -- embedded Editor with minimal UI:**
```
https://editor.seatmap.pro?token={token}&refreshToken={refreshToken}&hideNavbar=true&hidden=pricing,label
```

---

## 34. Webhooks

seatmap.pro sends HTTP callbacks to your registered endpoints when venue or schema data changes. Use webhooks to keep your system in sync without polling.

### Available Events

| Event | Trigger |
|-------|---------|
| `venue.created` | New venue created |
| `venue.updated` | Venue details modified |
| `venue.deleted` | Venue removed |
| `schema.created` | New schema created |
| `schema.updated` | Schema metadata modified |
| `schema.deleted` | Schema removed |
| `seatmap.stored` | Seatmap (seats, rows, sections) saved |

### Webhook Headers

Every webhook request includes these headers:

| Header | Description |
|--------|-------------|
| `X-Seatmap-Event` | Event name (e.g., `schema.updated`) |
| `X-Seatmap-Signature` | HMAC-SHA256 signature of the request body |
| `X-Seatmap-Timestamp` | Unix timestamp of the event |

### Signature Verification

Verify the webhook signature using your organization's private key:

```typescript
import crypto from 'crypto';
import express from 'express';

const app = express();
const WEBHOOK_SECRET = process.env.SEATMAP_PRIVATE_KEY!;

app.post('/webhooks/seatmap', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-seatmap-signature'] as string;
  const timestamp = req.headers['x-seatmap-timestamp'] as string;
  const event = req.headers['x-seatmap-event'] as string;

  // Verify signature
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const payload = JSON.parse(req.body.toString());
  console.log(`Received ${event}:`, payload);

  switch (event) {
    case 'seatmap.stored':
      // Refresh your cached venue data
      break;
    case 'schema.updated':
      // Update event configuration if needed
      break;
  }

  res.status(200).send('OK');
});
```

### Delivery Semantics

- Webhooks are retried with exponential backoff on failure
- Per-organization rate limiting applies
- Webhook endpoints must respond within 30 seconds
- Return HTTP 2xx to acknowledge receipt; any other status triggers a retry

---

## 35. Multi-Tenancy Model

seatmap.pro supports a hierarchical multi-tenancy model:

```
Management Level (Bearer JWT)
  |
  +-- Organization (ORG token)
        |
        +-- Tenant 1 (TEN token)
        +-- Tenant 2 (TEN token)
        +-- ...
```

- **Organizations** own venues, schemas, and events
- **Tenants** provide sub-level access within an organization
- Organization tokens have full access; tenant tokens have scoped access
- Use the Management API to create organizations and tenants programmatically

See [Management API](#28-management-api) for the endpoint details.

---

## 36. On-Premise Deployment

seatmap.pro can be deployed on-premise as Docker containers orchestrated via Helm charts. For pricing and deployment tiers, see [Pricing and Deployment](#3-pricing-and-deployment).

### Configuration

Each service has its own configuration file:
- **Editor service:** `application.yaml` with database, Redis, S3, and auth settings
- **Booking service:** `application.yaml` with database, Redis, and API key settings
- **Image converter:** Separate service for background image processing

For detailed deployment instructions, see the [seatmap.pro Knowledge Base -- On-Premise Deployment](https://seatmap.pro/knowledge-base/on-premise/).

---

# Appendix

---

## 37. Official Documentation and Support

### Knowledge Base

- Knowledge Base: [seatmap.pro/knowledge-base](https://seatmap.pro/knowledge-base/)
- Renderer SDK Spec: [seatmap.pro/knowledge-base/renderer/spec](https://seatmap.pro/knowledge-base/renderer/spec/)
- Booking API v2: [seatmap.pro/knowledge-base/api/v2](https://seatmap.pro/knowledge-base/api/v2/)
- Iframe Communication: [seatmap.pro/knowledge-base/venue-editor/iframe-communication](https://seatmap.pro/knowledge-base/venue-editor/iframe-communication/)
- SSO Setup: [seatmap.pro/knowledge-base/venue-editor/sso](https://seatmap.pro/knowledge-base/venue-editor/sso/)
- FAQ: [seatmap.pro/knowledge-base/faq](https://seatmap.pro/knowledge-base/faq/)
- Webhooks: [seatmap.pro/knowledge-base/webhooks](https://seatmap.pro/knowledge-base/webhooks/)
- On-Premise: [seatmap.pro/knowledge-base/on-premise](https://seatmap.pro/knowledge-base/on-premise/)
- Admin Renderer: [seatmap.pro/knowledge-base/renderer/admin](https://seatmap.pro/knowledge-base/renderer/admin/)

### Support

- Email: hello@seatmap.pro
- Customer Portal: [seatmap.atlassian.net/servicedesk](https://seatmap.atlassian.net/servicedesk)
- Request a Demo: [seatmap.pro/demo](https://seatmap.pro/demo/)
