Booking Service API Changes - v1.65.0
Release: v1.65.0 Date: 2026-04-29
Summary
| Aspect | Status |
|---|---|
| Breaking changes | NO |
| New endpoints | 1 booking endpoint (/directsale), 2 admin endpoint groups (federation instances, library audit) |
| Modified endpoints | 0 (V1 endpoints gain a booking_disabled enforcement check, but the contract is unchanged for enabled orgs) |
| Removed endpoints | 0 |
| Auth changes | NO |
New Endpoints
POST /api/private/v2.0/booking/directsale?eventId={uuid}
Issue: SEAT-957
Atomic ACTIVE → SOLD transition for seats and general-admission capacity. Bypasses the LOCKED intermediate state that /sale requires. Use for non-interactive flows (back-office sales, comp tickets, imports).
Auth: same as /sale — @securityServiceV2.hasAccessToEvent(eventId). Caller’s organization must have access to the event; the V2 enforcement of booking_disabled is honored.
Request
POST /api/private/v2.0/booking/directsale?eventId=8d4f0f3a-2b8e-4d2a-9c8e-1d3a2b8e4d2a
Content-Type: application/json
Authorization: Bearer {token}
{
"sessionId": "abc123",
"seats": [
{ "id": 42 },
{ "id": 43 }
],
"groupOfSeats": [
{ "id": 7, "capacity": 4 }
],
"holdType": "DIRECT"
}
Body: same StateSelection shape as /sale. holdType is optional and informational on the audit row only — direct sale does not transition through a hold.
Response
200 true— all seats and GA capacity transitioned successfully.200 false— at least one seat is not inACTIVEstate, or at least one GAactiveCountis below the requested capacity. Operation is atomic per request — partial application is rejected.402 ORGANIZATION_DISABLED— caller’s organization hasbooking_disabled=true.403 Forbidden— caller does not have access to the event.
curl example
curl -X POST \
"https://api.seatmap.dev/api/private/v2.0/booking/directsale?eventId=8d4f0f3a-2b8e-4d2a-9c8e-1d3a2b8e4d2a" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "abc123",
"seats": [{"id": 42}, {"id": 43}]
}'
# 200
# true
Behaviour
- Required source state is
ACTIVE.LOCKED,SOLD, andBLOCKEDseats are rejected (returnsfalse, no state change). - GA capacity is deducted from
activeCountdirectly — no intermediate locked bucket. - Each successful transition writes a row to
seat_state_eventswithold_state=ACTIVE, new_state=SOLDso the outbox and downstream analytics observe the source state correctly. - The existing
/saleendpoint and its semantics are unchanged./directsaleis opt-in.
When to use which
/sale— interactive checkout flows where a customer holds seats during payment. Continue with/lockthen/sale./directsale— non-interactive flows (back-office sales, comp tickets, imports). One call, no holding state.
GET / POST / DELETE /api/admin/federation/instances
Issue: SEAT-952
Global-admin endpoints to manage federation API keys. Used by source-instance operators to issue keys to consumer instances. Plaintext keys are returned once on creation and never readable again.
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/federation/instances |
Paginated list (default 50, sort by createdAt DESC). Returns label, hash prefix, status. |
POST |
/api/admin/federation/instances |
Issue a new key. Returns the plaintext key in the response — store it immediately. |
POST |
/api/admin/federation/instances/{id}/revoke |
Revoke. Sets revoked_at. Existing requests under the key fail thereafter. |
Auth: ROLE_GLOBAL_ADMIN.
Create key — request
{
"label": "Timepad on-prem",
"rateLimitRpm": 60
}
Create key — response
{
"id": 7,
"label": "Timepad on-prem",
"key": "fed_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"createdAt": "2026-04-29T12:34:56Z",
"rateLimitRpm": 60
}
The plaintext key is shown in this response only. Subsequent reads return only the hash prefix and metadata.
GET /api/admin/library/audit/...
Issue: SEAT-948
Global-admin audit viewer for library copy operations. Backed by the library_pull_log table introduced in V104.
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/library/audit |
Paginated audit log. Filters: venueId, organizationId, instanceKeyHash. |
GET |
/api/admin/library/audit/{id} |
Single audit row. |
Auth: ROLE_GLOBAL_ADMIN.
The audit captures both in-instance copies and federation pulls; rows triggered by federation carry instance_api_key_hash, in-instance copies carry user_id and organization_id.
Behavioral Changes
V1 booking endpoints — booking_disabled parity
Issue: SEAT-959
The legacy V1 booking API now honors organization.booking_disabled (introduced in 1.63.0 by SEAT-928). V1 paths previously bypassed the flag, so a disabled organization could still POST to /api/private/v1.0/booking/{lock,unlock,sale}.
Affected endpoints (all return 402 when the org is disabled):
POST /api/private/v1.0/booking/lockPOST /api/private/v1.0/booking/unlockPOST /api/private/v1.0/booking/saleGET /api/private/v1.0/venues/...GET /api/private/v1.0/events/...GET /api/private/v1.0/prices/...
Response shape (matches V2):
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"error": "ORGANIZATION_DISABLED",
"message": "Organization is disabled for booking"
}
Migration: integrators must surface 402 responses on the V1 path the same way they handle 4xx errors. Active organizations are unaffected — the change is transparent for them.
Removed Endpoints
No removed endpoints in this release.
Auth / Token Changes
No public auth contract changes.
The editor-service introduces an internal JWT shape change for impersonation (impersonator and impersonation_session_id claims), but this is editor-only and does not surface in booking-service.
OpenAPI / Swagger
The booking-service Swagger surface (/swagger-ui/index.html) reflects the new /directsale endpoint with full schemas. The admin federation and library audit endpoints surface in the editor-service Swagger under the Federation instances and Library audit tags.
Migration Guide
- Existing
/salecallers: no changes required./directsaleis a separate endpoint; existing flows continue to use/salewith/lockfirst. - V1 booking integrators: ensure 402 handling is in place. If the integration’s response handler treated all 4xx as transient, check that 402 routes to an operator-visible alert (the org has been disabled).
- Self-hosted operators consuming federation: see
deployment.mdfor the Helm + Secret setup. The source-instance admin issues your key once viaPOST /api/admin/federation/instances.