Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
72 changes: 72 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,77 @@
# 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, named by
`Seasons.dateCancelVoidJobId(amendmentId)` (`sbj_void_…`); follow it with
`retrieveSeasonBulkJob`. `season.ticket.voided` says `date_cancelled` for each season ticket it
voids.
- A Season package holds 2 to 50 dates. `Seasons.holderImportRowsPerCall(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` in the error's `body()`.
- `updateSeason`'s `singleTicketDates` can carry `jobRefused`, 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.
- `eventSeries().createEventSeries`: a series create given up for its key answers
`409 create_abandoned` with a message to send it again with a new idempotency key.
- Added 39 Season operations to `seasons()`, for 87 in all: performances, package prices, plan
edits and deletion, one-call publish, checkout tests, occurrence returns and reclaims, holders,
Season order refunds and cancellations, hosted renewals, renewal links and notices, renewal
quotes, intents and checkouts, sales and seat reports, seat counts, holder and order CSV exports, and bulk refunds and the release of lapsed renewal offers with their jobs.
- Added `holders()` (customers, holder reads and updates, sign-out, seasons, tickets, and ticket
transfers), `orders()` (orders across events and seasons, one event's orders, CSV export,
refunds, cancellations, and ticket emails), and `eventSeries()` (create, list, retrieve, add
dates, and update).
- Added `holders().createHolderReturn`: give back a holder's seat on one date, one per ticket.
- Added event sales operations to `events()`: buyer checkout, payment gateway, sales readiness,
preflight and transition, booking fee, and discount codes.
- The three CSV exports return the CSV text as a `String`. `deleteSeasonPlan` and
`deleteEventDiscountCode` return nothing (the API answers 204).
- 27 of the new operations use header replay, with one `Idempotency-Key` reused on every retry.
The other 54 are single-attempt and send no key.
- Added `seasons().refundSeasonPerformance` (refund every order of a cancelled date as a job, with
header replay), `seasons().listSeasonBulkJobs`, and `seasons().retrieveSeasonBulkJob`.
- Documented the new request fields the `Map` bodies carry: `priceTypeKey` on each
`replaceSeasonPackagePrices` price; `checkoutMode`, `transferCutoffHours` and
`returnCutoffHours` on `updateSeason`; `holderEmail` and `holderName` on each
`createSeasonHolderImport` row; and `kind` `postpone` or `cancel`, with `refund` and `reason`, on
`createSeasonAmendment`.
- `EventListOptions` takes `seriesKey` (builder `seriesKey(...)`) to list one event series' dates;
the five-argument constructor stays.
- Added `seasons().retrieveSeasonRenewalQuote`: what renewing an offer costs, and whether SeatLayer checkout can take it now.
- Added `seasons().createSeasonRenewalIntent`: record a holder's intent for a renewal taken by phone or at the box office, so your own checkout can commit.
- Added `seasons().createSeasonRenewalCheckout`: a SeatLayer checkout that renews an offer with no hosted page, with header replay.
- Added `holders().createHolderRenewalCheckout`: the same checkout for one of a holder's offers, with header replay.
- Added `holders().declineHolderRenewalOffer`: decline one of a holder's offers, with an optional `reason`.
- Added a `declineSeasonRenewalOffer` overload that takes a `reason`, kept on the offer as `declineReason`.
- `updateSeason` takes `audience` (`embed_only` or `public`).
- `openSeasonSales` answers `warnings` (`checkout_test_missing` until a checkout test or rehearsal has passed) and can refuse with `409 plan_not_published` or `409 checkout_mode_not_chosen`.
- A cancelled date refunds per date only: an amendment's `refund` is `none` or `per_date`, and `refundSeasonPerformance` takes `amount: per_date` (`full` answers `422 date_refund_per_date_only`; refund a whole order with the order refund).
- `addEventSeriesDates` takes `{"retryFailed": true}` to make the failed dates again (`409 series_busy`, `422 nothing_to_retry`).
- Added a `listOrders` overload with `holderRef`; orders carry `holderRef` and `customerId`, and `buyer.email` can be null (a box office sale with no email).
- Season holds, give-backs and date cancels can refuse with `performance_cancelled`, `refund_not_started` (503), `missing_manage_capability` (403) or `invalid_refund`; a holder's status can be `reserved`; hosted renewal readiness carries `missingPrices`.
- `eventSeries().createEventSeries` takes `fromEventKey` in place of `name` and `chartKey`: an event you already set up 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}`.
- Added `seasons().releaseSeasonLapsedOffers`: release the seats of every lapsed renewal offer as a job (`202` with the `job`), with header replay. An offer extended before its turn is skipped and keeps its seat.
- Added `retrieveSeasonHostedRenewals` and `listSeasonRenewalOffers` overloads that take `cursor` and `limit` (1-500 and 1-100); both answers carry `nextCursor`, null on the last page, and a bad cursor or limit answers `400 invalid_cursor` or `invalid_limit`. The one-argument forms read the first page.
- `createSeasonRenewalOffers` answers `201` with the `offers` for 100 contracts or fewer, and `202` with a `renewal_run` `job` and its `batchId` above that: follow the job with `retrieveSeasonBulkJob`, then page the offers with `listSeasonRenewalOffers`. It can refuse with `409 job_mismatch` or `503 season_not_configured`.
- `createSeasonHolderImportFromSeason` answers `202` with a `holder_import` `job` when a commit brings across more than 100 holders; the job's `summary` has the same counts when it completes. It can refuse with `409 job_mismatch`, or `503 job_busy` or `season_not_configured`. A retrieved bulk job carries `summary`, and its `kind` can be `holder_import`.
- `createSeasonRenewalNotices` takes up to 20,000 `offerIds`; 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` in their params (default `false`, Season only; anything else answers `422 invalid_single_tickets`). Every season read carries `singleTickets`, and a season on sale may carry `singleTicketsNotOpen`. `updateSeason`, `openSeasonSales` and `resumeSeasonSales` answer `singleTicketDates` (`direction`, `opened`, `closed`, `notOpened`, `jobId`) when they open or stop the dates' own sales; a `season_singles` job does it for more than 12 upcoming dates.

## 0.8.0 (2026-09-10)

- Added Event hosting regions: the `EventHostingRegion` enum, a `region` overload on event create, `defaultRegion` on workspace create, and `updateDefaultRegion`. Events created without a region run in Western Europe.
Expand Down
102 changes: 89 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,14 @@ The hint is best effort, not a data-residency guarantee. See the

## Fixed Renewable Seasons

Version `0.7.0` exposes all 48 trusted organizer operations through
`seatlayer.seasons()`.
`seatlayer.seasons()` exposes 87 trusted organizer operations: the 48 from
version `0.7.0`, plus performances, package prices (each price may name a
`priceTypeKey`: `adult`, `child`, `concession`, `senior` or `student`), checkout
tests, holders, Season orders, hosted renewals, renewal quotes, intents and
checkouts, reports, CSV exports, and the
bulk refund of a cancelled date (`refundSeasonPerformance`) and the release of
lapsed renewal offers (`releaseSeasonLapsedOffers`) with their jobs
(`listSeasonBulkJobs`, `retrieveSeasonBulkJob`).

After the test hold/book/cancel journey and matching webhook deliveries,
`validateSeasonBuyerRehearsal(seasonKey)` sends no evidence body; SeatLayer
Expand All @@ -116,6 +122,56 @@ returned operation identity. Buyer-session minting and domain-exact booking,
cancellation, and renewal actions remain single-attempt; only declared
header-replay catalogue mutations retry automatically.

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 `payment.url`. For a
renewal taken by phone or at the box office, `createSeasonRenewalIntent` records
the holder's intent before you commit the offer. A decline can carry the
holder's reason.

```java
Map<String, Object> quote = seatlayer.seasons().retrieveSeasonRenewalQuote(seasonKey, offerId);
if (quote.get("problem") == null) {
Map<String, Object> started = seatlayer.seasons().createSeasonRenewalCheckout(
seasonKey, offerId,
Map.of("returnUrl", "https://tickets.yourclub.example/renewed"),
"renew-" + offerId);
// Send the holder to the "url" under started.get("payment").
}
seatlayer.holders().declineHolderRenewalOffer(
"holder_123", offerId, Map.of("reason", "Moving away"));
```

## Orders, holders, event sales, and event series

`orders()` reads orders across events and seasons and handles one event's
refunds, cancellations, and ticket emails. `holders()` covers Season ticket
holders, their tickets and transfers, and the customer list. Event checkout,
payment gateway, sales readiness, booking fee, and discount codes are on
`events()`, and `eventSeries()` plans many dated events from one schedule.

```java
Map<String, Object> readiness = seatlayer.events().retrieveEventSalesReadiness("summer-gala");
seatlayer.events().transitionEventSales("summer-gala", Map.of(
"to", "live",
"expectedRevision", 4));

Map<String, Object> orders = seatlayer.orders().listEventOrders("summer-gala");
String csv = seatlayer.orders().exportEventOrdersCsv("summer-gala");
seatlayer.orders().refundEventOrder(
"summer-gala", "ord_1042", Map.of("amountMinor", 500), "refund-ord_1042");

seatlayer.holders().createHolderTransfer("holder-1042", Map.of(
"ticketIds", List.of("tkt_1"),
"toEmail", "friend@example.com"), null);
```

Request bodies are maps in the API's own field names. To send an explicit
`null`, such as clearing a booking fee, use a map that allows null values
(`LinkedHashMap`). The three CSV exports (`seasons().exportSeasonHoldersCsv`,
`seasons().exportSeasonOrdersCsv`, `orders().exportEventOrdersCsv`) return the
CSV text as a `String`. `seasons().deleteSeasonPlan` and
`events().deleteEventDiscountCode` return nothing: the API answers 204.

## Test vs live

Keys carry their own mode. `sk_test_…` keys can only touch test-mode events and `sk_live_…` only
Expand Down Expand Up @@ -224,6 +280,8 @@ exactly when you don't want that, and you can control it explicitly:
seatlayer.events().list(EventListOptions.builder().limit(50).counts(false).build());
```

Add `.seriesKey("ser_1")` to list only the dates of one event series.

## 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 @@ -315,17 +373,32 @@ support requests. All are unchecked, so they do not force `throws` clauses throu
## Reliability

**Retries and idempotency.** Reads (`GET`/`HEAD`) retry connection failures, 408, 429 and 5xx with
exponential backoff and full jitter; `Retry-After` wins when the server sends it. Fourteen mutations
use exact header replay: `charts().create`, `charts().copy`,
`templates().instantiateTemplate`, `events().create`, `workspaces().create`,
`performanceGroups().create`, `seasons().createSeason`, `seasons().updateSeason`,
`seasons().deleteSeason`, `seasons().createSeasonPlan`, `seasons().duplicateSeasonToLive`,
`seasons().createSeasonHolderImport`, `seasons().createSeasonRenewalOffers`, and
`seasons().createSeasonAmendment`. They generate an `Idempotency-Key` when absent and reuse that key
across every attempt. Overloads that accept a key let you provide a stable provisioning key instead.
exponential backoff and full jitter; `Retry-After` wins when the server sends it. Forty-one
mutations use exact header replay:

- `charts().create`, `charts().copy`, `templates().instantiateTemplate`, `events().create`,
`workspaces().create`, and `performanceGroups().create`
- 26 on `seasons()`: `createSeason`, `updateSeason`, `deleteSeason`, `createSeasonPlan`,
`updateSeasonPlan`, `deleteSeasonPlan`, `publishSeason`, `duplicateSeasonToLive`,
`createSeasonHolderImport`, `createSeasonHolderImportFromSeason`, `createSeasonRenewalOffers`,
`createSeasonAmendment`, `createSeasonPerformances`, `attachSeasonPerformances`,
`deleteSeasonPerformance`, `markSeasonPerformancePast`, `createSeasonCheckoutTest`,
`replaceSeasonPackagePrices`, `attachSeasonPlanPerformance`, `deleteSeasonPlanPerformance`,
`refundSeasonOrder`, `cancelSeasonOrder`, `cancelSeasonOrderDate`, `refundSeasonPerformance`,
`releaseSeasonLapsedOffers`, and `createSeasonRenewalCheckout`
- `holders().updateHolder`, `holders().createHolderTransfer`, `holders().createHolderReturn`,
`holders().createHolderRenewalCheckout`, `orders().refundEventOrder`, and
`orders().cancelEventOrder`
- `eventSeries().createEventSeries`, `eventSeries().addEventSeriesDates`, and
`eventSeries().updateEventSeries`

They generate an `Idempotency-Key` when absent and reuse that key across every attempt. Overloads
that accept a key let you provide a stable provisioning key instead; pass `null` to have one
generated.

All remaining SDK mutations are single-attempt: holds, bookings, lifecycle changes, channel
changes, show-once secret creation, and raw requests. Some have a server-side domain idempotency
changes, show-once secret creation, ticket and renewal emails, renewal links, discount codes,
event checkout, gateway, fee and sales changes, and raw requests. Some have a server-side domain idempotency
contract, but the SDK does not retry them automatically. Reconcile bookings with their required
`bookingRef`; never retry an unknown booking outcome as though the transport had made it safe.

Expand Down Expand Up @@ -356,14 +429,17 @@ outbox, and support operations.
| --- | --- |
| `charts()` | `list` `listAll` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` |
| `templates()` | `instantiateTemplate` |
| `events()` | `list` `listAll` `create` `retrieve` `retrieveConfigurationBinding` `updateConfigurationBinding` `update` `delete` `updateChart` `close` `reopen` `archive` `retrieveHoldTtl` `updateHoldTtl` `listTicketReleases` `updateTicketReleases` `closeTicketRelease` `retrieveReport` `retrieveLog` |
| `events()` | `list` `listAll` `create` `retrieve` `retrieveConfigurationBinding` `updateConfigurationBinding` `update` `delete` `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` |
| `channels()` | `list` `create` `update` `updateAssignments` `listAllocation` `retrieveAccessPreview` `retrieveReport` `pause` `unpause` `archive` `createBuyerAccessSession` `listBuyerAccessSessions` `revokeBuyerAccessSession` |
| `inventory()` | `hold` `holdBestAvailable` `bookBestAvailable` `extendHold` `retrieveHold` `release` `book` `bookLabels` `boxOfficeBook` `unbook` `block` `unblock` `unblockAll` `retrieveAvailability` `updateAvailability` `listBookings` `retrieveBooking` |
| `sessions()` | `createManageSession` `revokeManageSession` `createDesignerSession` `revokeDesignerSession` |
| `webhooks()` | `list` `create` `update` `delete` `listDeliveries` |
| `workspaces()` | `list` `create` `retrieve` `update` |
| `performanceGroups()` | `list` `create` `retrieve` `delete` `activate` `close` `retrieveLifecycle` `createBuyerAccessSession` `listBuyerAccessSessions` `revokeBuyerAccessSession` `retrieveHold` `bookHold` `retrieveBooking` |
| `seasons()` | 48 operations for catalogue and Plan lifecycle, sales windows, buyer access and booking, holder imports, renewals, occurrence amendments, reports, audit, outbox, and support export |
| `seasons()` | 87 operations for catalogue and Plan lifecycle, performances, package prices, sales windows, checkout tests, buyer access and booking, holders and holder imports, Season orders, renewals, hosted renewals and renewal checkouts, occurrence returns and amendments, bulk refunds, lapsed offer releases and jobs, reports and CSV exports, audit, outbox, and support export |

Full reference: [SeatLayer Java server SDK guide](https://docs.seatlayer.io/server-sdk/java/)

Expand Down
Loading
Loading