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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Unreleased

## 0.5.0 — 2026-08-21

- Added `performanceGroups()`, the trusted server resource for fixed two-to-eight
performance runs. It creates and activates groups, mints one-time browser
access, retrieves authoritative group holds, and confirms bookings with
stable action and order references. Browser-only group routes remain outside
this secret-key SDK.

- **Security/reliability:** Mutations now default to a single attempt. Automatic header-replay
retries are limited to chart create/copy, template instantiation, event create, and workspace
create, preventing transient failures from duplicating holds or best-available results and from
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ Official Java server SDK for the [SeatLayer](https://seatlayer.io) reserved-seat
<dependency>
<groupId>io.seatlayer</groupId>
<artifactId>seatlayer-java</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>
</dependency>
```

```groovy
implementation 'io.seatlayer:seatlayer-java:0.4.0'
implementation 'io.seatlayer:seatlayer-java:0.5.0'
```

Requires Java 17 or newer. **Zero runtime dependencies** — the SDK uses
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.seatlayer</groupId>
<artifactId>seatlayer-java</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>
<packaging>jar</packaging>

<name>SeatLayer Java SDK</name>
Expand Down
136 changes: 136 additions & 0 deletions src/main/java/io/seatlayer/PerformanceGroups.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package io.seatlayer;

import static io.seatlayer.SeatLayerHttpClient.body;
import static io.seatlayer.SeatLayerHttpClient.encode;

import java.util.List;
import java.util.Map;

/**
* Fixed multi-performance runs, trusted hold inspection, and host-authorized booking.
*
* <p>This is a secret-key resource. Pass only the one-time token returned from
* {@link #createBuyerAccessSession(String, String, boolean)} to PerformanceGroupPicker in a browser.
*/
public final class PerformanceGroups {

private final SeatLayerHttpClient http;

PerformanceGroups(SeatLayerHttpClient http) {
this.http = http;
}

private static String path(String performanceGroupKey, String suffix) {
return "/v1/performance-groups/" + encode(performanceGroupKey) + suffix;
}

public Map<String, Object> list() {
return http.get("/v1/performance-groups");
}

public Map<String, Object> list(
String workspaceId, String externalRef, String state, Integer limit, String cursor) {
return http.get(
"/v1/performance-groups",
body(
"workspaceId", workspaceId,
"externalRef", externalRef,
"state", state,
"limit", limit,
"cursor", cursor));
}

/** Create a draft run with exact idempotency replay. */
public Map<String, Object> create(String name, List<String> eventKeys) {
return create(name, eventKeys, null, null);
}

public Map<String, Object> create(
String name, List<String> eventKeys, String externalRef, String idempotencyKey) {
return http.postWithHeaderReplay(
"/v1/performance-groups",
body("name", name, "eventKeys", eventKeys, "externalRef", externalRef),
idempotencyKey);
}

public Map<String, Object> retrieve(String performanceGroupKey) {
return http.get(path(performanceGroupKey, ""));
}

/** Delete a draft only; activated runs remain available for audit. */
public void delete(String performanceGroupKey) {
http.delete(path(performanceGroupKey, ""));
}

/** Start activation. Poll retrieveLifecycle when the returned operation is not terminal. */
public Map<String, Object> activate(String performanceGroupKey, int expectedRevision) {
return http.post(path(performanceGroupKey, "/activate"), body("expectedRevision", expectedRevision));
}

/** Stop new group sales. Poll retrieveLifecycle until the close becomes terminal. */
public Map<String, Object> close(String performanceGroupKey, int expectedRevision) {
return http.post(path(performanceGroupKey, "/close"), body("expectedRevision", expectedRevision));
}

public Map<String, Object> retrieveLifecycle(String performanceGroupKey, String operationId) {
return http.get(path(performanceGroupKey, "/lifecycle/" + encode(operationId)));
}

/**
* Reveal one origin-bound browser bearer. This call is deliberately single-attempt.
*/
public Map<String, Object> createBuyerAccessSession(
String performanceGroupKey, String allowedOrigin, boolean includePublic) {
return createBuyerAccessSession(
performanceGroupKey, allowedOrigin, includePublic, null, null, null, null, null);
}

public Map<String, Object> createBuyerAccessSession(
String performanceGroupKey,
String allowedOrigin,
boolean includePublic,
Map<String, List<String>> channelIdsByEvent,
Integer expiresInSeconds,
Integer maxQuantity,
String buyerRef,
String partnerRef) {
return http.post(
path(performanceGroupKey, "/buyer-access-sessions"),
body(
"allowedOrigin", allowedOrigin,
"includePublic", includePublic,
"channelIdsByEvent", channelIdsByEvent,
"expiresInSeconds", expiresInSeconds,
"maxQuantity", maxQuantity,
"buyerRef", buyerRef,
"partnerRef", partnerRef));
}

public Map<String, Object> listBuyerAccessSessions(String performanceGroupKey) {
return listBuyerAccessSessions(performanceGroupKey, null);
}

public Map<String, Object> listBuyerAccessSessions(String performanceGroupKey, Integer limit) {
return http.get(path(performanceGroupKey, "/buyer-access-sessions"), body("limit", limit));
}

public Map<String, Object> revokeBuyerAccessSession(String performanceGroupKey, String sessionId) {
return http.delete(path(performanceGroupKey, "/buyer-access-sessions/" + encode(sessionId)));
}

public Map<String, Object> retrieveHold(String performanceGroupKey, String operationId) {
return http.get(path(performanceGroupKey, "/holds/" + encode(operationId)));
}

/** Reuse both stable IDs and poll retrieveBooking while the returned booking is pending. */
public Map<String, Object> bookHold(
String performanceGroupKey, String operationId, String bookActionId, String bookingRef) {
return http.post(
path(performanceGroupKey, "/holds/" + encode(operationId) + "/book"),
body("bookActionId", bookActionId, "bookingRef", bookingRef));
}

public Map<String, Object> retrieveBooking(String performanceGroupKey, String actionId) {
return http.get(path(performanceGroupKey, "/bookings/" + encode(actionId)));
}
}
7 changes: 7 additions & 0 deletions src/main/java/io/seatlayer/SeatLayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public final class SeatLayer {
private final Channels channels;
private final Events events;
private final Inventory inventory;
private final PerformanceGroups performanceGroups;
private final Sessions sessions;
private final Templates templates;
private final Webhooks webhooks;
Expand All @@ -37,6 +38,7 @@ private SeatLayer(Builder builder) {
this.channels = new Channels(http);
this.events = new Events(http);
this.inventory = new Inventory(http);
this.performanceGroups = new PerformanceGroups(http);
this.sessions = new Sessions(http);
this.templates = new Templates(http);
this.webhooks = new Webhooks(http);
Expand Down Expand Up @@ -64,6 +66,11 @@ public Inventory inventory() {
return inventory;
}

/** Fixed multi-performance lifecycle and host-side booking coordination. */
public PerformanceGroups performanceGroups() {
return performanceGroups;
}

public Sessions sessions() {
return sessions;
}
Expand Down
51 changes: 51 additions & 0 deletions src/test/java/io/seatlayer/ClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,57 @@ void dropsNullQueryParameters() {
sdk.charts().list(ChartListOptions.builder().workspaceId("ws_1").build());
assertEquals("https://api.seatlayer.io/v1/charts?workspaceId=ws_1", call(0).url());
}

@Test
@DisplayName("maps the full Performance Groups lifecycle")
void performanceGroupLifecycle() {
SeatLayer sdk = client(List.of(
Stub.of(200, "{\"performanceGroups\":[]}"),
Stub.of(201, "{\"performanceGroup\":{}}"),
Stub.of(200, "{\"performanceGroup\":{}}"),
Stub.of(204, ""),
Stub.of(202, "{\"lifecycleOperation\":{\"terminal\":false}}"),
Stub.of(200, "{\"lifecycleOperation\":{\"terminal\":true}}"),
Stub.of(200, "{\"lifecycleOperation\":{}}"),
Stub.of(201, "{\"token\":\"bsg_secret\"}"),
Stub.of(200, "{\"sessions\":[]}"),
Stub.of(200, "{\"ok\":true}"),
Stub.of(200, "{\"hold\":{}}"),
Stub.of(202, "{\"booking\":{\"state\":\"book_pending\"}}"),
Stub.of(200, "{\"booking\":{\"state\":\"booked\"}}")));
String groupKey = "pg_a/b";

sdk.performanceGroups().list("ws_1", null, "draft", null, null);
sdk.performanceGroups().create("Weekend run", List.of("ev_1", "ev_2"), null, "weekend-run-1");
sdk.performanceGroups().retrieve(groupKey);
sdk.performanceGroups().delete(groupKey);
sdk.performanceGroups().activate(groupKey, 1);
sdk.performanceGroups().close(groupKey, 2);
sdk.performanceGroups().retrieveLifecycle(groupKey, "pga_1");
sdk.performanceGroups().createBuyerAccessSession(groupKey, "https://tickets.example.test", true);
sdk.performanceGroups().listBuyerAccessSessions(groupKey, 25);
sdk.performanceGroups().revokeBuyerAccessSession(groupKey, "pgbs_1");
sdk.performanceGroups().retrieveHold(groupKey, "pgh_1");
sdk.performanceGroups().bookHold(groupKey, "pgh_1", "book_1", "order_1");
sdk.performanceGroups().retrieveBooking(groupKey, "book_1");

String base = "https://api.seatlayer.io/v1/performance-groups/pg_a%2Fb";
assertEquals("https://api.seatlayer.io/v1/performance-groups?workspaceId=ws_1&state=draft", call(0).url());
assertEquals("weekend-run-1", call(1).headers().get("Idempotency-Key"));
assertEquals(base, call(2).url());
assertEquals("DELETE", call(3).method());
assertEquals(base + "/activate", call(4).url());
assertEquals(base + "/close", call(5).url());
assertEquals(base + "/lifecycle/pga_1", call(6).url());
assertEquals(base + "/buyer-access-sessions", call(7).url());
assertNull(call(7).headers().get("Idempotency-Key"));
assertEquals(base + "/buyer-access-sessions?limit=25", call(8).url());
assertEquals(base + "/buyer-access-sessions/pgbs_1", call(9).url());
assertEquals(base + "/holds/pgh_1", call(10).url());
assertEquals(base + "/holds/pgh_1/book", call(11).url());
assertNull(call(11).headers().get("Idempotency-Key"));
assertEquals(base + "/bookings/book_1", call(12).url());
}
}

@Nested
Expand Down
Loading