---
title: "Performance Groups API"
description: "Create fixed multi-performance runs, mint browser access, inspect an atomic group hold, and commit it from a trusted backend."
---

The Performance Groups API coordinates assigned-seat inventory for a fixed run
of two to eight compatible Events. Your existing checkout stays in control: you
decide the package and price, take payment, create the order, and issue tickets.
Use Performance Groups for one fixed multi-date purchase, not a recurring
subscription or flexible season membership.

Every route on this page uses a matching `sk_test_…` or `sk_live_…` secret key.
Keep that key on your server. Browser access uses the short-lived group token
minted below and is consumed only by `PerformanceGroupPicker`.

## Choose REST or a server SDK

The REST examples on this page are the canonical wire contract. Each official
server SDK exposes the same trusted workflow through one resource; choose the
package that matches your backend. Before adopting a package release, check the
[operation support matrix](/server-api/operation-support/): **Live** means the
published package was verified, while **RC** means the matching source has
passed its release checks but a public package has not yet been verified.

| Backend | Resource |
|---|---|
| Node.js | `seatlayer.performanceGroups` |
| Python | `seatlayer.performance_groups` |
| PHP | `$seatlayer->performanceGroups` |
| Java | `seatlayer.performanceGroups()` |
| Go | `client.PerformanceGroups` |
| Ruby | `client.performance_groups` |
| .NET | `client.PerformanceGroups` |

The resource creates and manages the group, mints the buyer token, reads the
trusted hold, and confirms the booking. It never gives a server SDK access to
the browser-only `/pub/performance-groups/*` routes.

## API map

| Task | Method and route |
|---|---|
| List or filter groups | `GET /v1/performance-groups` |
| Create a fixed run | `POST /v1/performance-groups` |
| Read one run | `GET /v1/performance-groups/:groupKey` |
| Discard an unused draft | `DELETE /v1/performance-groups/:groupKey` |
| Activate / close it | `POST /:groupKey/activate` · `POST /:groupKey/close` |
| Recover lifecycle progress | `GET /:groupKey/lifecycle/:operationId` |
| Mint / list / revoke browser sessions | `/buyer-access-sessions` |
| Inspect the opaque group hold | `GET /:groupKey/holds/:operationId` |
| Commit inventory and recover booking | `POST /holds/:operationId/book` · `GET /bookings/:bookActionId` |

The browser-only descriptor, chart, availability, hold, release, extension, and
operation routes are SDK implementation details. Use `PerformanceGroupPicker`;
do not call `/pub/performance-groups/*` from your application.

To list groups, use `workspaceId`, `externalRef`, `state`, `limit`, and
`cursor` as needed. `DELETE` succeeds only for a `draft`; once a group is active,
close it and let existing operations drain instead of trying to replace its dates.

## Create a fixed run

```http title="HTTP request"
POST /v1/performance-groups HTTP/1.1
Authorization: Bearer sk_test_••••••••
Idempotency-Key: production-opening-weekend
Content-Type: application/json

{
  "name": "Opening weekend",
  "externalRef": "run_2026_opening_weekend",
  "eventKeys": ["ev_friday", "ev_saturday", "ev_sunday"]
}
```

`name` is required and limited to 120 characters. `externalRef` is optional,
stable within the organization, and limited to 128 characters. `eventKeys` must
contain two to eight distinct compatible Events.

The server validates organization, workspace, mode, chart snapshot, inventory
model, venue, timezone, currency, sale ownership, start times, and assigned-seat
compatibility before it creates a `draft` group. `201` returns
`{ performanceGroup }`, including chronological `performances` and `revision`.

Use the same idempotency key when retrying a lost create response. `409` can
mean an Event already belongs to another group or the external reference is
already in use.

Every compatibility refusal answers `error: "incompatible_performances"` and
names the specific reason in `code` — `currency_mismatch`, `chart_mismatch`,
`invalid_event_count`, and so on. It is the one create failure in this API
where the two fields differ, so branch on `error` to recognise the umbrella and
read `code` for what to correct. The status follows the reason: `404` when a
performance is unknown, `422` for a malformed set, `409` for a field the
performances cannot safely share. Refusals raised by the route itself
(`invalid_name`, `module_not_enabled`, `external_ref_exists`) keep the usual
`error === code` shape.

## Activate and close

```http title="activate"
POST /v1/performance-groups/pg_opening_weekend/activate
Authorization: Bearer sk_test_••••••••
Content-Type: application/json

{ "expectedRevision": 1 }
```

Activation revalidates compatibility and locks the member Events against unsafe
chart or lifecycle changes. A final response contains the active group. A
recoverable `202` includes `Location`, `Retry-After`, the current group, and a
`lifecycleOperation`; poll that location until it is terminal.

Close uses the same body and recovery behavior:

```http title="close"
POST /v1/performance-groups/pg_opening_weekend/close
Authorization: Bearer sk_test_••••••••
Content-Type: application/json

{ "expectedRevision": 2 }
```

Closing stops new group holds, lets accepted holds finish or expire, then
releases the member locks only after drain proof. There is no reopen. Do not
use close as a shortcut to change the dates in a buyer's existing offer.

## Mint browser access

```http title="mint group buyer access"
POST /v1/performance-groups/pg_opening_weekend/buyer-access-sessions
Authorization: Bearer sk_test_••••••••
Content-Type: application/json

{
  "allowedOrigin": "https://tickets.example.com",
  "includePublic": true,
  "expiresInSeconds": 1800,
  "maxQuantity": 4,
  "buyerRef": "buyer_8372"
}
```

`allowedOrigin` and `includePublic` are required. The token applies to the
whole active group and is checked on every browser request. `expiresInSeconds`
is 60 to 43,200; `maxQuantity` is one to 100 or omitted; buyer and partner
references are opaque values up to 120 characters.

For private sales, pass `channelIdsByEvent` with an array for each member Event:

```json title="private-allocation-scope.json"
{
  "allowedOrigin": "https://members.example.com",
  "includePublic": false,
  "channelIdsByEvent": {
    "ev_friday": ["chn_members"],
    "ev_saturday": ["chn_members"],
    "ev_sunday": ["chn_members"]
  }
}
```

The `201` response reveals `token` only once, alongside `sessionId`, `expiresAt`,
and `performanceGroupKey`. Store the session ID for audit/revocation, return
only token and expiry to the browser, and never automatically retry a response
whose token delivery is uncertain.

Use `GET /v1/performance-groups/:groupKey/buyer-access-sessions?limit=50` to
list non-secret session metadata and
`DELETE /v1/performance-groups/:groupKey/buyer-access-sessions/:sessionId` to
revoke a session immediately.

## Inspect the group hold

The browser handoff supplies `operationId` and `holdId`. The server-inspection
route is addressed by the operation identity:

```http title="inspect group hold"
GET /v1/performance-groups/pg_opening_weekend/holds/pghop_01J8…
Authorization: Bearer sk_test_••••••••
```

The response contains `{ hold }` with one group hold identity, its common expiry,
selection mode, group revision, currency, and chronological `allocations`. Each
allocation includes its Event key/name/time and authoritative inventory line
items. Child hold IDs never appear.

Inspect immediately before your payment decision. `404` means the group,
operation, or hold is not available to this tenant/mode; `409
performance_group_integrity_error` is a support escalation, not a reason to
charge or retry with altered IDs.

## Book one group hold

```http title="commit inventory"
POST /v1/performance-groups/pg_opening_weekend/holds/pghop_01J8…/book
Authorization: Bearer sk_test_••••••••
Content-Type: application/json

{
  "bookActionId": "pg-book:order_1842",
  "bookingRef": "order_1842"
}
```

`bookActionId` and `bookingRef` are both required stable identifiers. Reuse the
same pair for every retry of this commercial order. `200` with `booking.state:
"booked"` is final. `202` with `booking.state: "book_pending"` includes a
`Location` and `Retry-After`; poll
`GET /v1/performance-groups/:groupKey/bookings/:bookActionId` until terminal.

`409 book_failed` is terminal and may require payment compensation or support
because a child performance can already have settled. Do not create a new action
ID to force another booking attempt.

## Error and recovery rules

| Situation | Correct response |
|---|---|
| `403 module_not_enabled` | Performance Groups is not enabled for this account. Do not fall back to separate Event holds. |
| `403 buyer_access_mode_mismatch` | Use a test key for a test group and a live key for a live group. |
| `404 not_found` | Treat as unknown/cross-tenant/mode-mismatched; never reveal more detail to a buyer. |
| `409` lifecycle pending or revision conflict | Reload the group and follow the existing lifecycle operation; do not overwrite a revision. |
| `incompatible_performances` (404/409/422) | Read `code` for the reason and correct the Event setup before creating a new group. |
| `202` lifecycle or booking | Poll the supplied Location using `Retry-After`. |
| Lost browser hold response | Let `PerformanceGroupPicker` recover the same operation. |
| Lost server booking response | Retry only with the same `bookActionId` and `bookingRef`. |

Next: [end-to-end integration](/integrations/performance-groups) and
[Performance Group picker](/buyer-sdk/performance-groups).