Partner API
Connect your booking experience to club availability. Reserve with confidence, keep inventory in sync, and let the club stay in control.
HTTPS · /api/partner/v1 · JSON · Bearer authenticationTechnical documentation is maintained in English. Access requires an approved connection.Adding booking to your club’s website? Start with the booking widget.
Start with an approved connection
For a club’s own website, start with the ready-made iframe booking widget at /partners#embedding. It uses BookingGood’s booking flow without a custom API backend. This API is for developers building custom booking interfaces, marketplaces or apps. BookingGood owns capacity, club configuration and reservation status. Your service owns checkout, customer communication and financial workflows.
Ask the club administrator for an approved sandbox connection, its API origin, facility grants and a securely delivered credential. Credentials belong to one club and environment. The API is disabled by default; documentation availability does not mean a deployment has enabled it. There is no self-service API key signup.
Call from your backend over HTTPS with Authorization: Bearer <apiCredential>. Keep credentials and the separate callback signing secret out of browser code, URLs and logs. A sales integration needs configuration:read, availability:read, bookings:read and bookings:write. Callbacks also need callbacks:manage and the read scope for each topic.
Independent primary facilities and explicitly granted coaches are supported. Shared resources, recurring creation, courses, accounts, passes and payments are outside this API. Sales pause permits reads and owned future-booking cancellation; full connection disablement also blocks reads, retries and cancellation.
GET /api/partner/v1/connection HTTP/1.1
Authorization: Bearer <apiCredential>1. Discover facilities and available times
Read /connection and durably save its original bootstrapCursor before any baseline reads. It advertises scopes, generations, timezone, sales status, horizon and limits. Read /configuration for approved facility IDs and rules.
Fetch /snapshot for explicit facilities and coverage. itemIds is one comma-separated query value. Offer only returned start/end options and quantities up to maxQuantity; empty options and zero capacity cannot be sold. Availability reserves no capacity. Only a committed booking reserves it.
Configuration marks resources as kind facility or coach. Coaches list their granted supplementaryTo facilities. Each facility end option includes availableCoaches for the full interval. A coach needs its own explicit resource grant, working hours and capacity one. Reserve with optional coachId and quantity 1; approval is required if either resource requires it.
Default snapshot limits are seven days and 20 facilities; use the limits advertised by your connection. Split larger coverage into complete windows. IDs and revisions are strings; quantities are positive integer JSON numbers. Timestamps have whole seconds and an explicit offset; responses use UTC Z. Display times in the club timezone. Intervals include the start and exclude the end. Unknown request fields are rejected.
GET /api/partner/v1/snapshot?fromDate=<encodedStart>&toDate=<encodedEnd>&itemIds=42,43 HTTP/1.1
Authorization: Bearer <apiCredential>2. Reserve before confirming
Before every mutation, durably save its exact method, path, JSON body, conditional headers, unique Idempotency-Key and BookingGood-Operation-Expires. Choose a future whole-second UTC expiry no more than five minutes ahead of the server clock. Placeholders below are illustrative; choose facility IDs and times from a current snapshot.
Create a reservation with a permanent opaque externalBookingId. This byte-exact, case-sensitive identity survives cancellation and credential rotation and cannot identify a different reservation later. Do not put personal data in it. Optional customer fields are private reservation contacts, omitted from API responses, feeds and callbacks; they do not create accounts or send messages.
Read the returned booking status and retain its ID and exact quoted ETag header. CONFIRMED can be confirmed to your customer. PENDING reserves capacity but awaits staff approval. CANCELED and REJECTED are terminal. Never infer confirmation from availability or an HTTP success alone.
POST /api/partner/v1/bookings HTTP/1.1
Authorization: Bearer <apiCredential>
Content-Type: application/json
Idempotency-Key: <savedUniqueKey>
BookingGood-Operation-Expires: <savedUTCExpiry>
{
"externalBookingId": "order-7f3a",
"itemId": "42",
"fromDate": "<offeredStart>",
"toDate": "<offeredEnd>",
"quantity": 1
}3. Read, change or cancel
Read /bookings/{id} for current state and ETag, or /bookings/by-external-id?externalBookingId=... for your permanent identity. Encode query values with a URL library: a literal + must be %2B. Only this connection’s owned reservations are visible.
PATCH /bookings/{id} atomically changes a future PENDING or CONFIRMED reservation. Supply only the facility, interval or quantity fields you intend to change. A refused move preserves the original reservation. An edit can require approval again; inspect the returned status.
Omit coachId in a patch to preserve it, supply a coach ID to add or replace it, or send null to remove it. Creation, changes and cancellation update facility and coach capacity together under one booking identity and ETag.
POST /bookings/{id}/cancel cancels a future owned reservation while preserving its identity. The body can be {} or include a reason. Patch and cancel require the current quoted ETag in If-Match plus their own saved idempotency key and expiry. Started reservations need staff reconciliation.
4. Resolve timeouts without duplicate bookings
Retry the exact saved request with its original key, expiry, payload and conditional headers until accepted or durably refused. A timeout, expired operation, 404 lookup, authorization failure, 429 or 503 cannot settle an earlier uncertain request. Do not create a new identity or key to escape uncertainty.
Errors use {code, message, requestId}. Only operationFinal: true marks a durably recorded refusal. After a final refusal, a revised attempt uses a new key and expiry. Response decisions remain for at least seven days; permanent external identities survive replay pruning.
For a final 412 revision_mismatch, refresh the ETag and reconsider the edit; for a final 412 capacity_unavailable, refresh availability. 428 means a required precondition is missing. Honor Retry-After on 429 and 503. Expiry fences new admission; it is not a commit deadline.
If checkout is abandoned, keep resolving the outstanding create and conditionally cancel any accepted future reservation with a new operation. A reservation that has started requires staff reconciliation.
5. Keep a complete, recoverable baseline
From the saved original bootstrapCursor: fetch configuration, snapshots for every sales facility/window, and every page of the unfiltered owned-booking inventory. Retain terminal identities and withdrawn-facility references. A missing known booking is a recovery incident, not cancellation.
Then replay /changes from that original cursor, using each returned continuation and hasMore. Apply all refreshes for a page durably before saving its cursor, even for an empty filtered page. Never substitute a newer snapshot cursor. Replace only a snapshot’s stated coverage; serialize overlapping refreshes or reject older revisions.
Poll periodically even with healthy callbacks. Callbacks are refresh hints and never advance the feed cursor. bookings.items means refresh those owned IDs; bookings.allOwned means enumerate every owned-booking page. Fetch newly exposed future windows as the horizon advances, even without a callback.
On 410 resync_required or a scope/recovery-generation change, repeat the complete baseline. Administrator sales authorization does not perform synchronization. Coordinate possible data-loss incidents with the operator before resuming sales.
6. Verify signed callbacks
Register a singleton subscription with PUT /subscription at an administrator-approved HTTPS hostname on port 443. Use If-None-Match: * for initial creation and the current subscription ETag in If-Match for replacement/resume, plus the saved mutation key and expiry. Redirects and private/reserved destinations are refused.
Verify HMAC-SHA256 over the exact raw body bytes before parsing JSON. Decode the separate 32-byte base64url onboarding signing secret. Join bookinggood.v1, timestamp, eventId, deliveryId, keyId and raw body with LF bytes, with no extra trailing LF. Signature and test vectors are included in the OpenAPI contract.
Headers are BookingGood-Event-Id, BookingGood-Delivery-Id, BookingGood-Signing-Key and BookingGood-Signature: t=<unixSeconds>,v1=<unpaddedBase64urlMac>. Reject malformed or duplicate parameters, unknown versions/keys, noncanonical encodings, payload-ID mismatches and timestamps outside five minutes. Compare MAC bytes in constant time.
For a verified subscription.challenge, echo {"nonce":"<received nonce>"} within ten seconds; the nonce expires after five minutes and binds connection/generation. For events, durably enqueue and deduplicate by stable eventId before returning 2xx within ten seconds. Check generations before applying hints. Retries use fresh delivery IDs and timestamps.
Normal signing-key rotation overlaps keys for 24 hours and switches after receipt confirmation. Compromised keys are revoked immediately. Delivery can suspend after 72 hours of failure; feed recovery remains necessary.
API reference
All operations require a backend Bearer credential. Scopes below are granted by the club administrator. Expand definitions for exact constraints and response headers; linked schemas include every field.
/connectionRead connection
Read enabled connection metadata and retain bootstrapCursor before baseline work.
Required scopes: Enabled connection; no additional scope.
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | Connection |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Connection"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/configurationRead configuration
Read complete permitted normalized configuration; unsupported required configuration fails closed.
Required scopes: configuration:read
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | Configuration |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Configuration"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/snapshotRead availability snapshot
All three scopes required. Half-open coverage at most seven days and 20 facilities, complete response at most 2 MiB.
Required scopes: configuration:read, availability:read, bookings:read
Parameters
| Name | Type | Description |
|---|---|---|
fromDatequery · Required | Instant | |
toDatequery · Required | Instant | |
itemIdsquery · Required | array of Id |
Parameter constraints
[
{
"name": "fromDate",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Instant"
},
"required": true
},
{
"name": "toDate",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Instant"
},
"required": true
},
{
"name": "itemIds",
"in": "query",
"required": true,
"style": "form",
"explode": false,
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Id"
},
"minItems": 1,
"maxItems": 20,
"uniqueItems": true
}
}
]Responses
| Status | Description | Body |
|---|---|---|
200 | Success | Snapshot |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Snapshot"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/bookingsList bookings
Complete owned inventory including withdrawn resources and terminal identities. Optional fromDate/toDate must be paired and select overlapping intervals.
Required scopes: bookings:read
Parameters
| Name | Type | Description |
|---|---|---|
cursorquery · Optional | string | |
limitquery · Optional | integer | |
fromDatequery · Optional | Instant | |
toDatequery · Optional | Instant |
Parameter constraints
[
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 250,
"default": 100
}
},
{
"name": "fromDate",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Instant"
}
},
{
"name": "toDate",
"in": "query",
"schema": {
"$ref": "#/components/schemas/Instant"
}
}
]Responses
| Status | Description | Body |
|---|---|---|
200 | Success | BookingPage |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingPage"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/bookingsCreate booking
Reserve before confirming to the customer. A successful response explicitly says CONFIRMED or PENDING. Every attempt must reuse exact original body/key/expiry until success or operationFinal:true. After replay expiry an identical permanent identity returns current booking with existing:true; a different original fingerprint is 409.
Required scopes: bookings:write
Parameters
| Name | Type | Description |
|---|---|---|
Idempotency-Keyheader · Required | OpaqueReference | Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409. |
BookingGood-Operation-Expiresheader · Required | UTCInstant | At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation. |
Parameter constraints
[
{
"name": "Idempotency-Key",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueReference"
},
"description": "Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409."
},
{
"name": "BookingGood-Operation-Expires",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/UTCInstant"
},
"description": "At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation."
}
]JSON request body
| Field | Type | Description |
|---|---|---|
externalBookingIdRequired | OpaqueReference | At most 200 UTF-8 bytes, enforced independently of JSON Schema character limit. Byte-exact case/spacing preservation; valid UTF-8 required. Never put personal data in permanent external IDs. |
itemIdRequired | Id | SQL identifiers are decimal strings, never floating-point JSON numbers. |
fromDateRequired | Instant | Whole-second RFC 3339 instant with explicit offset. No fractions or rounding; unsupported repeated/gap local hours fail closed. Responses normalize to UTC Z. |
toDateRequired | Instant | Whole-second RFC 3339 instant with explicit offset. No fractions or rounding; unsupported repeated/gap local hours fail closed. Responses normalize to UTC Z. |
quantityRequired | integer | |
customerReferenceOptional | OpaqueReference | At most 200 UTF-8 bytes, enforced independently of JSON Schema character limit. Byte-exact case/spacing preservation; valid UTF-8 required. Never put personal data in permanent external IDs. |
customerLabelOptional | string | |
customerPhoneOptional | string | Optional unverified booking contact in international E.164 format. Visible only to club staff; omitted from partner responses and public projections. |
customerPictureOptional | string | Optional absolute HTTPS customer image URL without credentials, whitespace or control characters. Displayed in staff booking details without a referrer; the server does not fetch or upload it. Private, creation-only and cleared with other customer fields after 90 days. Omitted from partner responses, public projections, feeds and callbacks. |
coachIdOptional | Id | Optional explicitly granted coach linked to itemId. Requires quantity 1. Both resources are reserved atomically. |
Full schema: BookingCreate
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | BookingResult |
201 | New reservation committed | BookingResult |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingResult"
}
}
}
},
"201": {
"description": "New reservation committed",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingResult"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/bookings/by-external-idFind by external ID
Permanent byte-exact identity lookup. URL encode literal plus as %2B. A 404 never closes an in-flight operation.
Required scopes: bookings:read
Parameters
| Name | Type | Description |
|---|---|---|
externalBookingIdquery · Required | OpaqueReference |
Parameter constraints
[
{
"name": "externalBookingId",
"in": "query",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueReference"
}
}
]Responses
| Status | Description | Body |
|---|---|---|
200 | Success | BookingResult |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingResult"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/bookings/{id}Read booking
Read current owned booking and ETag. Foreign connection/club and nonexistent objects return identical 404.
Required scopes: bookings:read
Parameters
| Name | Type | Description |
|---|---|---|
idpath · Required | Id |
Parameter constraints
[
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/Id"
}
}
]Responses
| Status | Description | Body |
|---|---|---|
200 | Success | BookingResult |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingResult"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/bookings/{id}Update booking
Atomically change a future pending/confirmed reservation. Failed moves preserve old occupancy. Pure reductions preserve confirmation; move/enlargement reevaluates it. No account/payment/status edits.
Required scopes: bookings:write
Parameters
| Name | Type | Description |
|---|---|---|
idpath · Required | Id | |
Idempotency-Keyheader · Required | OpaqueReference | Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409. |
BookingGood-Operation-Expiresheader · Required | UTCInstant | At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation. |
If-Matchheader · Required | string | Opaque ETag returned by GET/mutation; includes recovery generation. Missing: 428; stale: 412 revision_mismatch. Recorded replay bypasses only its own committed precondition. |
Parameter constraints
[
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/Id"
}
},
{
"name": "Idempotency-Key",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueReference"
},
"description": "Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409."
},
{
"name": "BookingGood-Operation-Expires",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/UTCInstant"
},
"description": "At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation."
},
{
"name": "If-Match",
"in": "header",
"required": true,
"schema": {
"type": "string"
},
"description": "Opaque ETag returned by GET/mutation; includes recovery generation. Missing: 428; stale: 412 revision_mismatch. Recorded replay bypasses only its own committed precondition."
}
]JSON request body
| Field | Type | Description |
|---|---|---|
itemIdOptional | Id | SQL identifiers are decimal strings, never floating-point JSON numbers. |
fromDateOptional | Instant | Whole-second RFC 3339 instant with explicit offset. No fractions or rounding; unsupported repeated/gap local hours fail closed. Responses normalize to UTC Z. |
toDateOptional | Instant | Whole-second RFC 3339 instant with explicit offset. No fractions or rounding; unsupported repeated/gap local hours fail closed. Responses normalize to UTC Z. |
quantityOptional | integer | |
coachIdOptional | Id | null | Omit to preserve the coach, supply an ID to select or replace, or null to remove. The resulting coach reservation requires quantity 1. |
Full schema: BookingPatch
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | BookingResult |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingResult"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/bookings/{id}/cancelCancel booking
Cancel future owned pending/confirmed reservation. Already canceled returns current state even with old ETag. A started booking requires staff reconciliation. Terminal identity persists.
Required scopes: bookings:write
Parameters
| Name | Type | Description |
|---|---|---|
idpath · Required | Id | |
Idempotency-Keyheader · Required | OpaqueReference | Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409. |
BookingGood-Operation-Expiresheader · Required | UTCInstant | At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation. |
If-Matchheader · Required | string | Opaque ETag returned by GET/mutation; includes recovery generation. Missing: 428; stale: 412 revision_mismatch. Recorded replay bypasses only its own committed precondition. |
Parameter constraints
[
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"$ref": "#/components/schemas/Id"
}
},
{
"name": "Idempotency-Key",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueReference"
},
"description": "Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409."
},
{
"name": "BookingGood-Operation-Expires",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/UTCInstant"
},
"description": "At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation."
},
{
"name": "If-Match",
"in": "header",
"required": true,
"schema": {
"type": "string"
},
"description": "Opaque ETag returned by GET/mutation; includes recovery generation. Missing: 428; stale: 412 revision_mismatch. Recorded replay bypasses only its own committed precondition."
}
]JSON request body
| Field | Type | Description |
|---|---|---|
reasonOptional | string |
Full schema: BookingCancel
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | BookingResult |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BookingResult"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/changesRead changes
Requires at least one data-read scope. Filter by current read grants independently of callback topics. Never infer missing events from revision gaps.
Required scopes: At least one of configuration:read, availability:read or bookings:read. Events are permission-filtered.
Parameters
| Name | Type | Description |
|---|---|---|
cursorquery · Required | string | |
limitquery · Optional | integer |
Parameter constraints
[
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 250,
"default": 100
}
}
]Responses
| Status | Description | Body |
|---|---|---|
200 | Success | ChangePage |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChangePage"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/subscriptionRead callback subscription
Read singleton callback and sanitized health. Returns 404 if absent. Never returns a signing secret.
Required scopes: callbacks:manage
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | Subscription |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Subscription"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}/subscriptionSet callback subscription
Each topic additionally requires its read scope. First create requires If-None-Match: *; replacements require If-Match. Approved HTTPS host only, port 443; no userinfo, fragments, query secrets, redirects or private/reserved DNS. Every subscription change/resumption increments generation, invalidates verification and requires fresh challenge. needsResync is always true; feed acknowledgment is separate. Explicit resume requires feed catch-up.
Required scopes: callbacks:manage
Parameters
| Name | Type | Description |
|---|---|---|
Idempotency-Keyheader · Required | OpaqueReference | Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409. |
BookingGood-Operation-Expiresheader · Required | UTCInstant | At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation. |
If-Matchheader · Optional | string | Opaque ETag returned by GET/mutation; includes recovery generation. Missing: 428; stale: 412 revision_mismatch. Recorded replay bypasses only its own committed precondition. |
If-None-Matchheader · Optional | "*" | Required for first creation; mutually exclusive with If-Match. |
Parameter constraints
[
{
"name": "Idempotency-Key",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/OpaqueReference"
},
"description": "Unique per connection. Reuse unchanged for retries; fingerprint includes method, route, body, conditional headers and expiry. Different request with same key: 409."
},
{
"name": "BookingGood-Operation-Expires",
"in": "header",
"required": true,
"schema": {
"$ref": "#/components/schemas/UTCInstant"
},
"description": "At most five minutes ahead of primary DB clock. Reuse unchanged on every retry. Replay/permanent identity resolution precedes expiry fence under lock; old expiry cannot admit a new mutation."
},
{
"name": "If-Match",
"in": "header",
"required": false,
"schema": {
"type": "string"
},
"description": "Opaque ETag returned by GET/mutation; includes recovery generation. Missing: 428; stale: 412 revision_mismatch. Recorded replay bypasses only its own committed precondition."
},
{
"name": "If-None-Match",
"in": "header",
"schema": {
"const": "*"
},
"description": "Required for first creation; mutually exclusive with If-Match."
}
]JSON request body
| Field | Type | Description |
|---|---|---|
urlRequired | string | |
topicsRequired | array of Topic | |
enabledRequired | boolean |
Full schema: SubscriptionWrite
Responses
| Status | Description | Body |
|---|---|---|
200 | Success | Subscription |
400 | Malformed/unknown input or missing idempotency/expiry header. | Error |
401 | Invalid credential; cannot settle an uncertain operation. | Error |
403 | Connection disabled or missing scope; cannot settle an uncertain operation. | Error |
404 | Absent or inaccessible owned object; negative lookup never proves final refusal. | Error |
409 | external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict. | Error |
410 | resync_required: discard invalid cursor and obtain a full baseline. | Error |
412 | capacity_unavailable or revision_mismatch. | Error |
422 | Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage. | Error |
428 | Conditional header required. | Error |
429 | Rate budget exceeded; retry after Retry-After. | Error |
503 | sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final. | Error |
Response headers and definitions
{
"200": {
"description": "Success",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"ETag": {
"schema": {
"type": "string"
},
"description": "Opaque conditional revision; retain exact quoted value."
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Subscription"
}
}
}
},
"400": {
"description": "Malformed/unknown input or missing idempotency/expiry header.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Invalid credential; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Connection disabled or missing scope; cannot settle an uncertain operation.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "Absent or inaccessible owned object; negative lookup never proves final refusal.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "external_identity_conflict, idempotency_mismatch, final_state, operation_expired or verification_conflict.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"410": {
"description": "resync_required: discard invalid cursor and obtain a full baseline.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"412": {
"description": "capacity_unavailable or revision_mismatch.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"422": {
"description": "Unsupported interval/resource/quantity/policy, snapshot_too_large or invalid coverage.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"428": {
"description": "Conditional header required.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Rate budget exceeded; retry after Retry-After.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "sales_paused, recovery_in_progress, write_contended or authoritative service unavailable; non-final.",
"headers": {
"Cache-Control": {
"schema": {
"const": "no-store"
}
},
"Retry-After": {
"schema": {
"type": "integer",
"minimum": 1
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}Callback contract
Receiver request headers, payloads and acknowledgments, followed by exact signing rules and public test vectors. These test keys are public examples.
Webhook request and response
{
"partnerCallback": {
"post": {
"summary": "Signed committed-change hint or subscription challenge",
"parameters": [
{
"name": "BookingGood-Event-Id",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "BookingGood-Delivery-Id",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "BookingGood-Signing-Key",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "BookingGood-Signature",
"in": "header",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"oneOf": [
{
"$ref": "#/components/schemas/ChangeEvent"
},
{
"$ref": "#/components/schemas/Challenge"
}
]
}
}
}
},
"responses": {
"200": {
"description": "Durably queued event, or verified nonce echo for challenge.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChallengeResponse"
}
}
}
}
}
}
}
}Signature verification
{
"scheme": "HMAC-SHA256",
"secret": "Decode unpadded base64url 32-byte onboarding secret. Separate from API credential.",
"bytes": "bookinggood.v1\\n<t>\\n<eventId>\\n<deliveryId>\\n<keyId>\\n<rawBody>",
"encoding": "UTF-8; LF separators; no extra trailing LF. Sign exact raw body before parsing.",
"signature": "t=<unixSeconds>,v1=<unpadded-base64url-mac>",
"verification": "Reject duplicated header parameters, unknown versions, malformed/noncanonical encodings, UUID/payload mismatches and timestamps outside five minutes. Constant-time MAC comparison. Fresh deliveryId/timestamp per retry, stable eventId for durable deduplication.",
"acknowledgment": "Verify, durably enqueue eventId and payload, then 2xx within ten seconds. For challenge echo {nonce} within ten seconds; nonce expires after five minutes and binds connection/generation.",
"rotation": "24-hour normal overlap; switch after onboarding confirms the new secret. Immediate revocation for compromised keys."
}Signature test vectors
[
{
"description": "Public test secret ONLY. Generated independently using Python standard-library hmac; verifier uses Node crypto.",
"secretBase64url": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8",
"unixSeconds": 1793923200,
"rawBody": "{\"eventId\":\"a2bfac64-2929-4e66-89e1-dc9c180a27af\",\"event\":\"club.changed\",\"siteId\":\"27\",\"scopeGeneration\":\"3\",\"recoveryGeneration\":\"19dcc072-7d27-4f0b-b349-71f973727359\",\"revision\":\"415\",\"changes\":{\"availability\":[{\"itemId\":\"42\",\"fromDate\":\"2026-11-06T16:00:00Z\",\"toDate\":\"2026-11-06T17:00:00Z\"}]}}",
"signedBytesHex": "626f6f6b696e67676f6f642e76310a313739333932333230300a61326266616336342d323932392d346536362d383965312d6463396331383061323761660a30313233343536372d383961622d346364652d383132332d3435363738396162636465660a6578616d706c652d6b65792d310a7b226576656e744964223a2261326266616336342d323932392d346536362d383965312d646339633138306132376166222c226576656e74223a22636c75622e6368616e676564222c22736974654964223a223237222c2273636f706547656e65726174696f6e223a2233222c227265636f7665727947656e65726174696f6e223a2231396463633037322d376432372d346630622d623334392d373166393733373237333539222c227265766973696f6e223a22343135222c226368616e676573223a7b22617661696c6162696c697479223a5b7b226974656d4964223a223432222c2266726f6d44617465223a22323032362d31312d30365431363a30303a30305a222c22746f44617465223a22323032362d31312d30365431373a30303a30305a227d5d7d7d",
"headers": {
"BookingGood-Event-Id": "a2bfac64-2929-4e66-89e1-dc9c180a27af",
"BookingGood-Delivery-Id": "01234567-89ab-4cde-8123-456789abcdef",
"BookingGood-Signing-Key": "example-key-1",
"BookingGood-Signature": "t=1793923200,v1=JK5bKtFLWh8WXXvzGHz4zHT45YOsO0FAPC6NoGgvM9s"
}
},
{
"description": "Public test secret ONLY. Generated independently using Python standard-library hmac; verifier uses Node crypto.",
"secretBase64url": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8",
"unixSeconds": 1793923200,
"rawBody": "{\"eventId\":\"ab686331-9b7d-46a0-b7f5-6baa0fce7b3c\",\"event\":\"subscription.challenge\",\"nonce\":\"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8\",\"connectionId\":\"7\",\"generation\":\"2\",\"recoveryGeneration\":\"19dcc072-7d27-4f0b-b349-71f973727359\"}",
"signedBytesHex": "626f6f6b696e67676f6f642e76310a313739333932333230300a61623638363333312d396237642d343661302d623766352d3662616130666365376233630a31313233343536372d383961622d346364652d383132332d3435363738396162636465660a6578616d706c652d6b65792d310a7b226576656e744964223a2261623638363333312d396237642d343661302d623766352d366261613066636537623363222c226576656e74223a22737562736372697074696f6e2e6368616c6c656e6765222c226e6f6e6365223a2241414543417751464267634943516f4c4441304f4478415245684d554652595847426b6147787764486838222c22636f6e6e656374696f6e4964223a2237222c2267656e65726174696f6e223a2232222c227265636f7665727947656e65726174696f6e223a2231396463633037322d376432372d346630622d623334392d373166393733373237333539227d",
"headers": {
"BookingGood-Event-Id": "ab686331-9b7d-46a0-b7f5-6baa0fce7b3c",
"BookingGood-Delivery-Id": "11234567-89ab-4cde-8123-456789abcdef",
"BookingGood-Signing-Key": "example-key-1",
"BookingGood-Signature": "t=1793923200,v1=JKRAC75k6Xjh5uj4l_EIdkEpxeMbcKEa0P4b_MO6JLw"
}
}
]Schemas
JSON Schema definitions from the published OpenAPI contract, including validation rules. References name other definitions in this section.
Id
{
"type": "string",
"pattern": "^[1-9][0-9]*$",
"maxLength": 20,
"description": "SQL identifiers are decimal strings, never floating-point JSON numbers."
}Revision
{
"type": "string",
"pattern": "^[0-9]+$"
}Instant
{
"type": "string",
"format": "date-time",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2})$",
"description": "Whole-second RFC 3339 instant with explicit offset. No fractions or rounding; unsupported repeated/gap local hours fail closed. Responses normalize to UTC Z."
}UTCInstant
{
"type": "string",
"format": "date-time",
"pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$"
}OpaqueReference
{
"type": "string",
"minLength": 1,
"maxLength": 200,
"pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]+$",
"description": "At most 200 UTF-8 bytes, enforced independently of JSON Schema character limit. Byte-exact case/spacing preservation; valid UTF-8 required. Never put personal data in permanent external IDs."
}Scope
{
"type": "string",
"enum": [
"configuration:read",
"availability:read",
"bookings:read",
"bookings:write",
"callbacks:manage"
]
}Topic
{
"type": "string",
"enum": [
"configuration",
"availability",
"bookings"
]
}Error
{
"type": "object",
"additionalProperties": false,
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"requestId": {
"type": "string"
},
"operationFinal": {
"const": true
},
"details": {
"type": "object",
"additionalProperties": false,
"properties": {
"fields": {
"type": "array",
"items": {
"type": "string"
}
},
"requestedQuantity": {
"type": "integer",
"minimum": 1
},
"maxQuantity": {
"type": "integer",
"minimum": 0
}
},
"required": []
}
},
"required": [
"code",
"message",
"requestId"
],
"description": "operationFinal:true only for a durably committed operation decision. All other errors, negative lookups and transport failures cannot settle a previous uncertain mutation. details never identifies another customer."
}Limits
{
"type": "object",
"additionalProperties": false,
"properties": {
"jsonBytes": {
"type": "integer",
"minimum": 1
},
"referenceBytes": {
"type": "integer",
"minimum": 1
},
"snapshotDays": {
"type": "integer",
"minimum": 1
},
"snapshotItems": {
"type": "integer",
"minimum": 1
},
"snapshotBytes": {
"type": "integer",
"minimum": 1
},
"pageSize": {
"type": "integer",
"minimum": 1
},
"maxPageSize": {
"type": "integer",
"minimum": 1
},
"requestsPerMinute": {
"type": "integer",
"minimum": 1
},
"bookingWritesPerMinute": {
"type": "integer",
"minimum": 1
},
"operationAdmissionSeconds": {
"type": "integer",
"minimum": 1
},
"replayRetentionDays": {
"type": "integer",
"minimum": 1
},
"journalRetentionDays": {
"type": "integer",
"minimum": 1
}
},
"required": [
"jsonBytes",
"referenceBytes",
"snapshotDays",
"snapshotItems",
"snapshotBytes",
"pageSize",
"maxPageSize",
"requestsPerMinute",
"bookingWritesPerMinute",
"operationAdmissionSeconds",
"replayRetentionDays",
"journalRetentionDays"
]
}Connection
{
"type": "object",
"additionalProperties": false,
"properties": {
"connectionId": {
"$ref": "#/components/schemas/Id"
},
"siteId": {
"$ref": "#/components/schemas/Id"
},
"name": {
"type": "string"
},
"timezone": {
"type": "string",
"description": "IANA timezone"
},
"enabled": {
"type": "boolean"
},
"salesEnabled": {
"type": "boolean"
},
"scopes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Scope"
},
"uniqueItems": true
},
"scopeGeneration": {
"$ref": "#/components/schemas/Revision"
},
"recoveryGeneration": {
"type": "string",
"format": "uuid"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
},
"limits": {
"$ref": "#/components/schemas/Limits"
},
"salesHorizon": {
"type": "object",
"additionalProperties": false,
"properties": {
"fromDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"toDate": {
"$ref": "#/components/schemas/UTCInstant"
}
},
"required": [
"fromDate",
"toDate"
]
},
"bootstrapCursor": {
"type": "string"
}
},
"required": [
"connectionId",
"siteId",
"name",
"timezone",
"enabled",
"salesEnabled",
"scopes",
"scopeGeneration",
"recoveryGeneration",
"features",
"limits",
"salesHorizon",
"bootstrapCursor"
],
"description": "Capture bootstrapCursor before any baseline reads. Sales activation is separately operator controlled; successful reads never enable sales."
}ScheduleSegment
{
"type": "object",
"additionalProperties": false,
"properties": {
"start": {
"type": "integer",
"minimum": 0,
"maximum": 1439
},
"end": {
"type": "integer",
"minimum": 1,
"maximum": 1440
},
"step": {
"type": "integer",
"minimum": 1,
"maximum": 1440
},
"capacity": {
"type": "integer",
"minimum": 0
}
},
"required": [
"start",
"end"
],
"description": "Minutes since local midnight; start < end. step overrides bookingStepMinutes; default is 60. capacity overrides facility capacity but effective capacity cannot exceed the facility limit. No prices or arbitrary metadata."
}Schedule
{
"type": "object",
"additionalProperties": false,
"properties": {
"mode": {
"type": "string",
"enum": [
"every_day",
"every_week"
]
},
"bookingStepMinutes": {
"type": "integer",
"minimum": 1,
"maximum": 1440
},
"data": {
"oneOf": [
{
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"0": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"1": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"2": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"3": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"4": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"5": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"6": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
}
},
"required": []
}
]
}
},
"required": [
"mode",
"data"
],
"description": "every_day uses one array. every_week uses weekday map (0 Sunday): omitted days inherit the preceding specified day in Monday-through-Sunday order. Empty array means closed.",
"allOf": [
{
"if": {
"properties": {
"mode": {
"const": "every_day"
}
}
},
"then": {
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
}
}
},
"else": {
"properties": {
"data": {
"type": "object",
"additionalProperties": false,
"properties": {
"0": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"1": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"2": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"3": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"4": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"5": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
},
"6": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleSegment"
}
}
},
"required": [
"1"
]
}
}
}
}
]
}ScheduleException
{
"type": "object",
"additionalProperties": false,
"properties": {
"fromDate": {
"type": "string",
"format": "date"
},
"toDate": {
"type": "string",
"format": "date"
},
"schedule": {
"$ref": "#/components/schemas/Schedule"
}
},
"required": [
"fromDate",
"toDate",
"schedule"
],
"description": "Inclusive club-local dates. First matching exception replaces the regular schedule."
}CapacityEvent
{
"type": "object",
"additionalProperties": false,
"properties": {
"fromDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"toDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"capacity": {
"type": "integer",
"minimum": 0
}
},
"required": [
"fromDate",
"toDate",
"capacity"
],
"description": "Half-open interval; overlapping event capacities sum. Existing schedule semantics may raise slot capacity, but effective capacity is capped by facility capacity."
}BookingRules
{
"type": "object",
"additionalProperties": false,
"properties": {
"quantity": {
"type": "object",
"additionalProperties": false,
"properties": {
"selector": {
"type": "boolean"
},
"maximum": {
"type": [
"integer",
"null"
],
"minimum": 1
}
},
"required": [
"selector",
"maximum"
]
},
"duration": {
"type": "object",
"additionalProperties": false,
"properties": {
"multipleSlots": {
"type": "boolean"
},
"minimumMinutes": {
"type": [
"integer",
"null"
],
"minimum": 1
},
"maximumMinutes": {
"type": [
"integer",
"null"
],
"minimum": 1
}
},
"required": [
"multipleSlots",
"minimumMinutes",
"maximumMinutes"
]
},
"confirmation": {
"type": "object",
"additionalProperties": false,
"properties": {
"required": {
"type": "boolean"
},
"ifLessThanMinutes": {
"type": [
"integer",
"null"
],
"minimum": 1
}
},
"required": [
"required",
"ifLessThanMinutes"
]
}
},
"required": [
"quantity",
"duration",
"confirmation"
],
"description": "Maximum null means no configured bound; for multiple slots the absent maximum uses the first slot step as the selection ceiling. Malformed legacy quantity maximum normalizes to one. A false quantity selector only permits one. Durations and confirmation notice are minutes; snapshot options are authoritative."
}Site
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/components/schemas/Id"
},
"name": {
"type": "string"
},
"active": {
"type": "boolean"
},
"timezone": {
"type": "string"
}
},
"required": [
"id",
"name",
"active",
"timezone"
]
}Item
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/components/schemas/Id"
},
"name": {
"type": "string"
},
"salesEnabled": {
"type": "boolean"
},
"retired": {
"type": "boolean"
},
"capacity": {
"type": "integer",
"minimum": 1
},
"schedule": {
"$ref": "#/components/schemas/Schedule"
},
"scheduleExceptions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ScheduleException"
}
},
"capacityEvents": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CapacityEvent"
}
},
"bookingRules": {
"$ref": "#/components/schemas/BookingRules"
},
"kind": {
"type": "string",
"enum": [
"facility",
"coach"
],
"description": "Only facilities can be the primary item of a reservation or a snapshot request."
},
"supplementaryTo": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Id"
},
"description": "Coach-only list of granted primary facilities this coach may accompany."
}
},
"required": [
"id",
"name",
"salesEnabled",
"retired",
"capacity",
"schedule",
"scheduleExceptions",
"capacityEvents",
"bookingRules",
"kind"
]
}RetiredItem
{
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/components/schemas/Id"
},
"name": {
"type": "string"
},
"salesEnabled": {
"const": false
},
"retired": {
"type": "boolean"
},
"kind": {
"type": "string",
"enum": [
"facility",
"coach"
]
}
},
"required": [
"id",
"name",
"salesEnabled",
"retired"
],
"description": "Retained ownership context only. Withdrawal never cancels a reservation."
}Configuration
{
"type": "object",
"additionalProperties": false,
"properties": {
"site": {
"$ref": "#/components/schemas/Site"
},
"items": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/components/schemas/Item"
},
{
"$ref": "#/components/schemas/RetiredItem"
}
]
}
},
"revision": {
"$ref": "#/components/schemas/Revision"
}
},
"required": [
"site",
"items",
"revision"
]
}Booking
{
"type": "object",
"additionalProperties": false,
"properties": {
"siteId": {
"$ref": "#/components/schemas/Id"
},
"id": {
"$ref": "#/components/schemas/Id"
},
"externalBookingId": {
"$ref": "#/components/schemas/OpaqueReference"
},
"itemId": {
"$ref": "#/components/schemas/Id"
},
"fromDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"toDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"quantity": {
"type": "integer",
"minimum": 1
},
"status": {
"type": "string",
"enum": [
"PENDING",
"CONFIRMED",
"CANCELED",
"REJECTED",
"MISSED"
]
},
"revision": {
"$ref": "#/components/schemas/Revision"
},
"coachId": {
"$ref": "#/components/schemas/Id",
"description": "Optional explicitly granted coach linked to itemId. Requires quantity 1. Both resources are reserved atomically."
}
},
"required": [
"siteId",
"id",
"externalBookingId",
"itemId",
"fromDate",
"toDate",
"quantity",
"status",
"revision"
],
"description": "Owned reservation or permanent terminal identity. PENDING and CONFIRMED consume capacity; pending is not confirmation. Stored UTC instants survive later timezone changes. No account, payment, staff note or third-party booking data."
}BookingResult
{
"type": "object",
"additionalProperties": false,
"properties": {
"booking": {
"$ref": "#/components/schemas/Booking"
},
"existing": {
"const": true
}
},
"required": [
"booking"
]
}BookingCreate
{
"type": "object",
"additionalProperties": false,
"properties": {
"externalBookingId": {
"$ref": "#/components/schemas/OpaqueReference"
},
"itemId": {
"$ref": "#/components/schemas/Id"
},
"fromDate": {
"$ref": "#/components/schemas/Instant"
},
"toDate": {
"$ref": "#/components/schemas/Instant"
},
"quantity": {
"type": "integer",
"minimum": 1
},
"customerReference": {
"$ref": "#/components/schemas/OpaqueReference"
},
"customerLabel": {
"type": "string",
"maxLength": 120
},
"customerPhone": {
"type": "string",
"pattern": "^\\+[1-9][0-9]{1,14}$",
"maxLength": 16,
"description": "Optional unverified booking contact in international E.164 format. Visible only to club staff; omitted from partner responses and public projections."
},
"customerPicture": {
"type": "string",
"format": "uri",
"maxLength": 2048,
"description": "Optional absolute HTTPS customer image URL without credentials, whitespace or control characters. Displayed in staff booking details without a referrer; the server does not fetch or upload it. Private, creation-only and cleared with other customer fields after 90 days. Omitted from partner responses, public projections, feeds and callbacks."
},
"coachId": {
"$ref": "#/components/schemas/Id",
"description": "Optional explicitly granted coach linked to itemId. Requires quantity 1. Both resources are reserved atomically."
}
},
"required": [
"externalBookingId",
"itemId",
"fromDate",
"toDate",
"quantity"
]
}BookingPatch
{
"type": "object",
"additionalProperties": false,
"properties": {
"itemId": {
"$ref": "#/components/schemas/Id"
},
"fromDate": {
"$ref": "#/components/schemas/Instant"
},
"toDate": {
"$ref": "#/components/schemas/Instant"
},
"quantity": {
"type": "integer",
"minimum": 1
},
"coachId": {
"oneOf": [
{
"$ref": "#/components/schemas/Id"
},
{
"type": "null"
}
],
"description": "Omit to preserve the coach, supply an ID to select or replace, or null to remove. The resulting coach reservation requires quantity 1."
}
},
"required": [],
"minProperties": 1
}BookingCancel
{
"type": "object",
"additionalProperties": false,
"properties": {
"reason": {
"type": "string",
"maxLength": 500
}
},
"required": []
}Coverage
{
"type": "object",
"additionalProperties": false,
"properties": {
"fromDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"toDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"itemIds": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Id"
},
"minItems": 1,
"maxItems": 20,
"uniqueItems": true
}
},
"required": [
"fromDate",
"toDate",
"itemIds"
]
}AvailabilityOption
{
"type": "object",
"additionalProperties": false,
"properties": {
"toDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"maxQuantity": {
"type": "integer",
"minimum": 0
},
"requiresConfirmation": {
"type": "boolean"
},
"availableCoaches": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/components/schemas/Id"
},
"requiresConfirmation": {
"type": "boolean"
}
},
"required": [
"id",
"requiresConfirmation"
]
},
"description": "Granted coaches available for the entire facility start/end option. A coach booking uses quantity 1; approval is required if either the facility or the chosen coach requires it."
}
},
"required": [
"toDate",
"maxQuantity",
"requiresConfirmation",
"availableCoaches"
]
}Snapshot
{
"type": "object",
"additionalProperties": false,
"properties": {
"asOf": {
"$ref": "#/components/schemas/UTCInstant"
},
"coverage": {
"$ref": "#/components/schemas/Coverage"
},
"revision": {
"$ref": "#/components/schemas/Revision"
},
"cursor": {
"type": "string"
},
"configuration": {
"$ref": "#/components/schemas/Configuration"
},
"availability": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"itemId": {
"$ref": "#/components/schemas/Id"
},
"slots": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"fromDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"options": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AvailabilityOption"
}
}
},
"required": [
"fromDate",
"options"
]
}
}
},
"required": [
"itemId",
"slots"
]
}
},
"bookings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Booking"
}
}
},
"required": [
"asOf",
"coverage",
"revision",
"cursor",
"configuration",
"availability",
"bookings"
],
"description": "Complete SQL-consistent snapshot; no truncated successful responses. Options are clipped to coverage and follow public selection semantics. Empty options/zero maxQuantity are unavailable. Reject oversized requests instead of partial success."
}BookingPage
{
"type": "object",
"additionalProperties": false,
"properties": {
"bookings": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Booking"
}
},
"cursor": {
"type": [
"string",
"null"
]
},
"hasMore": {
"type": "boolean"
}
},
"required": [
"bookings",
"cursor",
"hasMore"
],
"description": "Primary-ID keyset order with captured upper bound. Cursor binds filters and generations. Page through every terminal identity without date filters for reconciliation, then replay from original bootstrapCursor."
}AvailabilityHint
{
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"itemId": {
"$ref": "#/components/schemas/Id"
},
"fromDate": {
"$ref": "#/components/schemas/UTCInstant"
},
"toDate": {
"$ref": "#/components/schemas/UTCInstant"
}
},
"required": [
"itemId",
"fromDate",
"toDate"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"itemId": {
"$ref": "#/components/schemas/Id"
},
"fullHorizon": {
"const": true
}
},
"required": [
"itemId",
"fullHorizon"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"allItems": {
"const": true
},
"fullHorizon": {
"const": true
}
},
"required": [
"allItems",
"fullHorizon"
]
}
]
}Changes
{
"type": "object",
"additionalProperties": false,
"properties": {
"configuration": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"allItems": {
"const": true
}
},
"required": [
"allItems"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"itemIds": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Id"
}
}
},
"required": [
"itemIds"
]
}
]
},
"availability": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AvailabilityHint"
}
},
"bookings": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"properties": {
"allOwned": {
"const": true
}
},
"required": [
"allOwned"
]
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"$ref": "#/components/schemas/Id"
},
"revision": {
"$ref": "#/components/schemas/Revision"
}
},
"required": [
"id",
"revision"
]
}
}
},
"required": [
"items"
]
}
]
}
},
"required": [],
"description": "Filtered refresh hints, never customer snapshots. Refresh owned bookings even on status-only changes. Omit unauthorized topics entirely. Broad invalidation replaces oversized hints; do not truncate."
}ChangeEvent
{
"type": "object",
"additionalProperties": false,
"properties": {
"eventId": {
"type": "string",
"format": "uuid"
},
"event": {
"const": "club.changed"
},
"siteId": {
"$ref": "#/components/schemas/Id"
},
"scopeGeneration": {
"$ref": "#/components/schemas/Revision"
},
"recoveryGeneration": {
"type": "string",
"format": "uuid"
},
"revision": {
"$ref": "#/components/schemas/Revision"
},
"changes": {
"$ref": "#/components/schemas/Changes"
}
},
"required": [
"eventId",
"event",
"siteId",
"scopeGeneration",
"recoveryGeneration",
"revision",
"changes"
]
}ChangePage
{
"type": "object",
"additionalProperties": false,
"properties": {
"changes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChangeEvent"
}
},
"cursor": {
"type": "string"
},
"hasMore": {
"type": "boolean"
}
},
"required": [
"changes",
"cursor",
"hasMore"
],
"description": "Fixed catch-up upper bound. Filtered empty pages can advance. Persist cursor only after all page refreshes succeed; callbacks never advance it. Changed scope/recovery generation, retention expiry or a future cursor yields 410 resync_required."
}SubscriptionWrite
{
"type": "object",
"additionalProperties": false,
"properties": {
"url": {
"type": "string",
"format": "uri",
"pattern": "^https://"
},
"topics": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Topic"
},
"minItems": 1,
"uniqueItems": true
},
"enabled": {
"type": "boolean"
}
},
"required": [
"url",
"topics",
"enabled"
]
}Subscription
{
"type": "object",
"additionalProperties": false,
"properties": {
"url": {
"type": "string",
"format": "uri"
},
"topics": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Topic"
}
},
"enabled": {
"type": "boolean"
},
"verified": {
"type": "boolean"
},
"suspended": {
"type": "boolean"
},
"generation": {
"$ref": "#/components/schemas/Revision"
},
"needsResync": {
"type": "boolean"
},
"deliveryHealth": {
"type": "object",
"additionalProperties": false,
"properties": {
"lastSuccessAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"lastFailure": {
"type": [
"string",
"null"
]
},
"failureSince": {
"type": [
"string",
"null"
],
"format": "date-time"
}
},
"required": [
"lastSuccessAt",
"lastFailure",
"failureSince"
]
}
},
"required": [
"url",
"topics",
"enabled",
"verified",
"suspended",
"generation",
"needsResync",
"deliveryHealth"
]
}Challenge
{
"type": "object",
"additionalProperties": false,
"properties": {
"eventId": {
"type": "string",
"format": "uuid"
},
"event": {
"const": "subscription.challenge"
},
"nonce": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]{43}$"
},
"connectionId": {
"$ref": "#/components/schemas/Id"
},
"generation": {
"$ref": "#/components/schemas/Revision"
},
"recoveryGeneration": {
"type": "string",
"format": "uuid"
}
},
"required": [
"eventId",
"event",
"nonce",
"connectionId",
"generation",
"recoveryGeneration"
]
}ChallengeResponse
{
"type": "object",
"additionalProperties": false,
"properties": {
"nonce": {
"type": "string"
}
},
"required": [
"nonce"
]
}