Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,96 @@
# Changelog

## Unreleased

- A date its season cancelled never opens again: `transitionEventSales` to `testable` or `live`
and `reopen` answer `409 season_date_cancelled`, and `updateEventSeries` with `sales: 'on'`
leaves it closed as a failed item of the job (`season_date_cancelled`; not in `failedDates`).
- `createSeasonAmendment` refuses to move (`reschedule`), postpone or replace a cancelled date,
and `markSeasonPerformancePast` refuses to mark one as taken place: `409 performance_cancelled`.
- `updateEventSeries` sends only the prices that change: each date keeps its own price for every
category (or tier) left out, so weekend and weekday prices keep their difference.
- A season's `transferCutoffHours` / `returnCutoffHours` of `null` means the club default
(Settings › Ticket site), or until the date starts when the club has none.
- `createHolderTransfer` and `createHolderReturn` are not stopped by the club's customer switches
in Settings › Ticket site; cut-offs still apply.
- Cancelling a date voids its tickets as a `date_cancel_void` job (`SeasonBulkJobKind`), named
by `seasonDateCancelVoidJobId(amendmentId)` (`sbj_void_…`); `season.ticket.voided` says
`date_cancelled` for each season ticket it voids.
- A Season package holds 2 to 50 dates. `seasonHolderImportRowsPerCall(dates)` gives the most
rows one `createSeasonHolderImport` call takes (100 up to 20 dates, 43 at 50); a call with
more answers `422 invalid_rows` with `maxRows`.
- `SeasonSingleTicketDates.jobRefused` says why a `season_singles` job could not start; the
change stands, and sending `singleTickets: true` again opens any date still not open. An
`updateSeason` retried with its first key and `expectedRevision` is not applied twice.
- A series create given up for its key answers `409 create_abandoned` with a message to send
it again with a new idempotency key.

- `createSeason` takes `mode` (`live` or `test`), as `POST /v1/seasons` already allows.
- Adds 39 Season operations (87 in all): performances and package prices,
`publishSeason`, checkout tests, import from a previous season, occurrence
inventory returns and reclaims, the holder list, season order refunds and
cancellations, hosted renewal links, contacts and notices, renewal quotes,
intents and checkouts, sales and seat reports, holder and order CSV exports,
bulk refunds with their jobs, and the release of lapsed renewal offers.
- Adds `holders` (customers, holders, holder tickets, ticket transfers),
`orders` (orders across events and seasons, and one event's orders, refunds,
cancellations and resends), and `eventSeries` resources.
- Adds `holders.createHolderReturn`: give back a holder's seat on one date, one
per ticket (`HolderReturn`, `HolderReturnTicket`, `CreateHolderReturnParams`).
- Adds 14 event sales operations to `events`: buyer checkout choice, payment
gateway, sales readiness, preflight and transitions, booking fee, and
discount codes.
- 27 of the new mutations use exact header replay (41 in all) and reuse one
`Idempotency-Key` across retries; the others are sent once. CSV exports
resolve with the CSV text; `deleteSeasonPlan` and `deleteEventDiscountCode`
resolve with nothing.
- Adds 10 webhook event names to `WebhookEventName`: `series.created`,
`series.dates_added`, `order.paid`, `order.refunded`, `order.cancelled`,
`customer.created`, `customer.email_changed`, `season.ticket.transfer_sent`,
`season.ticket.transferred`, and `season.ticket.transfer_cancelled`.
- Adds `seasons.refundSeasonPerformance` (refund every order of a cancelled date as a job, with
header replay), `seasons.listSeasonBulkJobs`, and `seasons.retrieveSeasonBulkJob`
(`SeasonBulkJob`, `SeasonBulkJobDetail`, `RefundSeasonPerformanceParams`).
- Adds price types: `priceTypeKey` on `replaceSeasonPackagePrices` rows (`SeasonPackagePriceInput`,
`SeasonPriceTypeKey`), `priceTypeKey` and `priceTypeLabel` on every `SeasonPackagePrice`,
`priceTypeKey` on each `SeasonHoldItem`, and `seats` with `priceTypeKey` on a retrieved or
inspected `SeasonRenewalOffer`.
- `createSeasonAmendment` takes `kind: 'postpone' | 'cancel'` with `refund` and `reason`
(`CreateSeasonAmendmentParams`) and resolves with the `refundJob` a refunding cancel starts.
- `SeasonAmendment.kind` adds `postpone`, `cancel`, `performance_added`, `performance_removed`, and
`performance_past` (`SeasonAmendmentKind`).
- `updateSeason` takes `checkoutMode`, `transferCutoffHours`, and `returnCutoffHours`
(`UpdateSeasonParams`).
- `SeasonHolderImportRow` takes `holderEmail` and `holderName`.
- `WebhookEventName` lists all 56 public event names, adding `object.resale.*`,
`season.occurrence.*`, `season.performance.*` (including `postponed`, `cancelled`, and
`rescheduled`), `season.bulk_action.completed`, `season.order.*`, `season.ticket.issued`, and
`season.ticket.voided`.
- `events.list` and `events.listAll` take `seriesKey` to list one event series' dates.
- Adds `seasons.retrieveSeasonRenewalQuote`: what renewing an offer costs, and whether SeatLayer checkout can take it now (`SeasonRenewalQuote`, `SeasonRenewalQuoteProblem`).
- Adds `seasons.createSeasonRenewalIntent`: record a holder's intent for a renewal taken by phone or at the box office, so your own checkout can commit.
- Adds `seasons.createSeasonRenewalCheckout`: a SeatLayer checkout that renews an offer with no hosted page, with header replay (`SeasonRenewalCheckoutParams`, `SeasonRenewalCheckoutStarted`).
- Adds `holders.createHolderRenewalCheckout`: the same checkout for one of a holder's offers, with header replay.
- Adds `holders.declineHolderRenewalOffer`: decline one of a holder's offers, with an optional `reason`.
- `declineSeasonRenewalOffer` takes an optional `{ reason }` (`DeclineSeasonRenewalOfferParams`), and `SeasonRenewalOffer` has `declineReason`.
- `Season` carries `checkoutMode`, `transferCutoffHours`, and `returnCutoffHours`, and `audience` is `'embed_only' | 'public'`; `updateSeason` takes `audience`.
- `openSeasonSales` resolves with `warnings` (`SeasonSalesOpenResult`): `checkout_test_missing` until a checkout test or rehearsal has passed. It can also refuse with `409 plan_not_published` or `409 checkout_mode_not_chosen`.
- A cancelled date refunds per date only: `createSeasonAmendment`'s `refund` is `'none' | 'per_date'`, and `refundSeasonPerformance` takes `amount: 'per_date'` (`full` answers `422 date_refund_per_date_only`; refund a whole order with the order refund).
- `SeasonHolder.status` adds `reserved` (an imported holder with seats and no offer yet).
- `retrieveSeasonHostedRenewals` readiness carries `missingPrices` with `prices_missing`.
- `addEventSeriesDates` takes `{ retryFailed: true }` to make the failed dates again (`409 series_busy`, `422 nothing_to_retry`).
- `Order` has `holderRef` and `customerId`, and `buyer.email` is nullable (a box office sale with no email); `listOrders` takes `holderRef`.
- Season holds, give-backs, and date cancels can refuse with `performance_cancelled`, `refund_not_started` (503), `missing_manage_capability` (403), or `invalid_refund`.
- `createEventSeries` takes `{ fromEventKey, schedule }` (`CreateEventSeriesFromEventParams`): the event becomes the first date under its own key, and every other date is a copy of it. It refuses with `404 event_not_found`, or `422 event_has_no_date`, `event_in_season`, `event_in_performance_group`, `event_in_series` or `invalid_request`.
- Every date of an event series carries the series' plain name (`Hamlet`, not `Hamlet · 3 Nov`); tell dates apart by `startsAt`. `updateEventSeries` renames each date that carries the series name, and a series name cannot hold `{n}`, `{date}` or `{time}`.
- Adds `seasons.releaseSeasonLapsedOffers`: release the seats of every lapsed renewal offer as a job (202 `{ job }`), with header replay. An offer extended before its turn is skipped and keeps its seat. It can refuse with `503 job_busy` or `season_not_configured`; send it again.
- `retrieveSeasonHostedRenewals` and `listSeasonRenewalOffers` page, newest first: pass `{ cursor, limit }` (`SeasonRenewalOfferPageOptions`; `limit` 1-500 and 1-100), and each page carries `nextCursor`, null on the last. A bad cursor or limit answers `400 invalid_cursor` or `invalid_limit`.
- `createSeasonRenewalOffers` resolves with `{ offers }` for 100 contracts or fewer, and with `{ job, batchId }` (`SeasonRenewalOfferJob`, a 202) above that: a `renewal_run` job makes the offers. Follow it with `retrieveSeasonBulkJob`, then page the offers with `listSeasonRenewalOffers`.
- `createSeasonHolderImportFromSeason` resolves with `{ job }` (a 202) when a commit brings across more than 100 holders: a `holder_import` job, whose `summary` has the same counts when it completes. `SeasonBulkJobKind` adds `holder_import`, and `SeasonBulkJobDetail` has `summary`. Both creates can refuse with `409 job_mismatch` or `503 season_not_configured`, and the bring-across with `503 job_busy`.
- `createSeasonRenewalNotices` takes up to 20,000 offer ids; more answers `422 too_many_offer_ids`.
- `createSeasonRenewalNotices` takes `batchId` instead of `offerIds`: email every open offer one `createSeasonRenewalOffers` batch made, such as the batch a `renewal_run` job made.
- Single tickets on a season: `createSeason` and `updateSeason` take `singleTickets` (default `false`, Season only; anything else answers `422 invalid_single_tickets`), and every `Season` carries it, with `singleTicketsNotOpen` on a season on sale. `updateSeason`, `openSeasonSales` and `resumeSeasonSales` resolve with `singleTicketDates` (`SeasonSingleTicketDates`, on `SeasonResult` and `SeasonSalesOpenResult`) when they open or stop the dates' own sales. `SeasonBulkJobKind` adds `season_singles`.

## 0.8.0 (2026-09-10)

- Added Event hosting regions: `region` on `events.create`, the `EventHostingRegion` type, `defaultRegion` on workspace create and update, and `region` on every Event read back (null on older Events). Events created without a region run in Western Europe.
Expand Down
73 changes: 62 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,16 @@ await seatlayer.seasons.createSeasonHolderImport(
{ idempotencyKey: 'incumbents-2027-commit' },
);
const deadlineAt = Date.parse('2027-06-30T23:59:59Z');
const { offers } = await seatlayer.seasons.createSeasonRenewalOffers(
const created = await seatlayer.seasons.createSeasonRenewalOffers(
seasonKey,
{ successorPlanActivationId: importInput.successorPlanActivationId, deadlineAt },
{ idempotencyKey: 'renewal-offers-2027' },
);
// Above 100 contracts the answer is { job, batchId }: a renewal_run job makes the
// offers. Follow it with retrieveSeasonBulkJob, then page them with
// listSeasonRenewalOffers ({ cursor: page.nextCursor }).
if ('job' in created) return followRenewalRun(created.job, created.batchId);
const { offers } = created;

// Browser intent is not payment. Inspect and commit only after your trusted
// host has made its commercial decision with the exact immutable Plan.
Expand All @@ -304,10 +309,35 @@ const terminal = await seatlayer.seasons.waitForSeasonRenewal(seasonKey, accepte
if (terminal.offer.state === 'partial_terminal') reconcile(terminal.offer.commitOutcomes);
```

Renewal intent requires a browser session bound to the exact `buyerRef`/holder.
Renewal intent normally comes from a browser session bound to the exact `buyerRef`/holder. For
a renewal taken by phone or at the box office, record it with your secret key first:
`createSeasonRenewalIntent(seasonKey, offerId)`, then take the payment and commit. A decline
can carry the holder's reason: `declineSeasonRenewalOffer(seasonKey, offerId, { reason })`.
Extension is explicit and pre-lapse; no default extension is invented while
organizer bounds and permission policy remain an owner-open rollout decision.

### Renewal with SeatLayer checkout

When SeatLayer takes the season's payments, renew a holder with no hosted page: read the price,
start a checkout, and send the holder to pay. Paying renews the offer on every date.

```ts
const quote = await seatlayer.seasons.retrieveSeasonRenewalQuote(seasonKey, offerId);
if (quote.problem === null) {
const started = await seatlayer.seasons.createSeasonRenewalCheckout(
seasonKey,
offerId,
{ returnUrl: 'https://tickets.yourclub.example/renewed' },
{ idempotencyKey: `renew-${offerId}` },
);
redirect(started.payment.url!);
}
```

For one holder, `holders.createHolderRenewalCheckout(holderRef, offerId, params)` and
`holders.declineHolderRenewalOffer(holderRef, offerId, { reason })` do the same for an offer of
theirs.

## Private allocations

`seatlayer.channels` manages event allocations and mints short-lived buyer access for a browser.
Expand Down Expand Up @@ -356,6 +386,12 @@ exactly when you don't want that, and you can control it explicitly:
await seatlayer.events.list({ limit: 50, counts: false });
```

To list only the dates of one event series, pass its key:

```ts
await seatlayer.events.list({ seriesKey: 'ser_1' });
```

## Keeping a hold alive

When an order takes longer than the checkout window (an invoice, a phone sale), extend rather than
Expand Down Expand Up @@ -463,12 +499,21 @@ requests.

## Reliability

**Retries and idempotency.** Reads retry 408, 429 and 5xx responses with backoff. Fourteen
**Retries and idempotency.** Reads retry 408, 429 and 5xx responses with backoff. Forty-one
mutations use exact header replay: `charts.create`, `charts.copy`, `templates.instantiateTemplate`,
`events.create`, `workspaces.create`, `performanceGroups.createPerformanceGroup`,
`seasons.createSeason`, `seasons.updateSeason`, `seasons.deleteSeason`, `seasons.createSeasonPlan`,
`seasons.duplicateSeasonToLive`, `seasons.createSeasonHolderImport`,
`seasons.createSeasonRenewalOffers`, and `seasons.createSeasonAmendment`. The SDK generates one
`events.create`, `workspaces.create`, `performanceGroups.createPerformanceGroup`; 26 Season
operations (`createSeason`, `updateSeason`, `deleteSeason`, `publishSeason`, `createSeasonPlan`,
`updateSeasonPlan`, `deleteSeasonPlan`, `duplicateSeasonToLive`, `createSeasonHolderImport`,
`createSeasonHolderImportFromSeason`, `createSeasonRenewalOffers`, `createSeasonAmendment`,
`createSeasonPerformances`, `attachSeasonPerformances`, `deleteSeasonPerformance`,
`markSeasonPerformancePast`, `attachSeasonPlanPerformance`, `deleteSeasonPlanPerformance`,
`replaceSeasonPackagePrices`, `createSeasonCheckoutTest`, `refundSeasonOrder`, `cancelSeasonOrder`,
`cancelSeasonOrderDate`, `refundSeasonPerformance`, `releaseSeasonLapsedOffers`, and
`createSeasonRenewalCheckout`);
`holders.updateHolder`, `holders.createHolderTransfer`, `holders.createHolderReturn`, and
`holders.createHolderRenewalCheckout`; `orders.refundEventOrder` and `orders.cancelEventOrder`; and
`eventSeries.createEventSeries`, `eventSeries.addEventSeriesDates`, and
`eventSeries.updateEventSeries`. The SDK generates one
`Idempotency-Key` and reuses it for every attempt. All remaining SDK mutations are sent once; some
have a server-side domain idempotency contract, but the SDK does not retry them automatically.

Expand All @@ -480,7 +525,7 @@ before trying again.
```ts
new SeatLayer({
secretKey: process.env.SEATLAYER_SECRET_KEY!,
maxRetries: 3, // retries for reads and the 14 exact-header-replay mutations
maxRetries: 3, // retries for reads and the 41 exact-header-replay mutations
timeoutMs: 30_000, // per attempt
});
```
Expand All @@ -498,20 +543,26 @@ await seatlayer.request('POST', '/v1/events/ev_1/some-new-route', { body: { …

The client exposes these resources. Performance Groups cover runs, sessions, holds, and bookings;
Seasons cover catalogue, plan, sales, buyer-session, booking, renewal, occurrence, reporting,
outbox, and support operations.
outbox, and support operations, plus performances, package prices, holders, season orders, hosted
renewals, and seat reports. The CSV exports (`seasons.exportSeasonHoldersCsv`,
`seasons.exportSeasonOrdersCsv`, `orders.exportEventOrdersCsv`) resolve with the CSV text as a
string.

| Resource | Methods |
| --- | --- |
| `charts` | `list` `listAll` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` |
| `templates` | `instantiateTemplate` |
| `events` | `list` `listAll` `create` `retrieve` `retrieveConfigurationBinding` `updateConfigurationBinding` `update` `delete` `updatePoster` `deletePoster` `updateChart` `close` `reopen` `archive` `retrieveHoldTtl` `updateHoldTtl` `listTicketReleases` `updateTicketReleases` `closeTicketRelease` `retrieveReport` `retrieveLog` |
| `events` | `list` `listAll` `create` `retrieve` `retrieveConfigurationBinding` `updateConfigurationBinding` `update` `delete` `updatePoster` `deletePoster` `updateChart` `close` `reopen` `archive` `retrieveHoldTtl` `updateHoldTtl` `listTicketReleases` `updateTicketReleases` `closeTicketRelease` `retrieveReport` `retrieveLog` `retrieveEventBuyerCheckout` `replaceEventBuyerCheckout` `retrieveEventPaymentGateway` `replaceEventPaymentGateway` `retrieveEventSalesReadiness` `preflightEventSales` `transitionEventSales` `retrieveEventBookingFee` `replaceEventBookingFee` `listEventDiscountCodes` `createEventDiscountCode` `updateEventDiscountCode` `endEventDiscountCode` `deleteEventDiscountCode` |
| `eventSeries` | `createEventSeries` `listEventSeries` `retrieveEventSeries` `addEventSeriesDates` `updateEventSeries` |
| `orders` | `listOrders` `retrieveOrder` `listOrderTickets` `listEventOrders` `retrieveEventOrder` `exportEventOrdersCsv` `refundEventOrder` `cancelEventOrder` `resendEventOrderTickets` |
| `holders` | `listCustomers` `retrieveHolder` `updateHolder` `revokeHolderSessions` `listHolderSeasons` `listHolderTickets` `retrieveHolderTicket` `createHolderTransfer` `listHolderTransfers` `cancelHolderTransfer` `acceptHolderTransfer` `createHolderReturn` `createHolderRenewalCheckout` `declineHolderRenewalOffer` |
| `inventory` | `hold` `holdBestAvailable` `bookBestAvailable` `extendHold` `retrieveHold` `release` `book` `boxOfficeBook` `unbook` `block` `unblock` `unblockAll` `retrieveAvailability` `updateAvailability` `listBookings` `retrieveBooking` `listInventoryBookings` `retrieveInventoryBooking` |
| `channels` | `listChannels` `createChannel` `updateChannel` `updateChannelAssignments` `listChannelAllocation` `retrieveChannelAccessPreview` `pauseChannel` `unpauseChannel` `archiveChannel` `retrieveChannelReport` `createBuyerAccessSession` `listBuyerAccessSessions` `revokeBuyerAccessSession` `createAccessLink` `listAccessLinks` `rotateAccessLink` `revokeAccessLink` |
| `sessions` | `createManageSession` `revokeManageSession` `createDesignerSession` `revokeDesignerSession` |
| `webhooks` | `list` `create` `update` `delete` `listDeliveries` |
| `workspaces` | `list` `create` `retrieve` `update` |
| `performanceGroups` | `listPerformanceGroups` `createPerformanceGroup` `retrievePerformanceGroup` `deletePerformanceGroup` `activatePerformanceGroup` `closePerformanceGroup` `retrievePerformanceGroupLifecycle` `createPerformanceGroupBuyerAccessSession` `listPerformanceGroupBuyerAccessSessions` `revokePerformanceGroupBuyerAccessSession` `retrievePerformanceGroupHold` `bookPerformanceGroupHold` `retrievePerformanceGroupBooking` |
| `seasons` | 48 organizer operations for catalogue and Plan lifecycle, sales windows, buyer access and booking, holder imports, renewals, occurrence amendments, reports, audit, outbox, and support export, plus polling helpers |
| `seasons` | 87 organizer operations for catalogue and Plan lifecycle, performances and package prices (with price types), sales windows, buyer access and booking, checkout tests, holder imports and the holder list, season orders, renewals and hosted renewal links, renewal quotes and checkouts, occurrence amendments (postpone, cancel) and inventory returns, bulk refunds of a cancelled date and the release of lapsed renewal offers with the jobs that run them, sales and seat reports, CSV exports, audit, outbox, and support export, plus polling helpers |

Full reference: [SeatLayer Server API](https://docs.seatlayer.io/server-api/events/)

Expand Down
Loading
Loading