{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"BookingGood Partner Booking API","version":"1.0.0","description":"Original provider-independent reservation API. BookingGood is authority for inventory/configuration. HTTPS Bearer credentials are club/environment bound. JSON input is strict; consumers tolerate additive response fields. Runtime is disabled by default; this contract does not authorize onboarding or activation."},"servers":[{"url":"/api/partner/v1"}],"security":[{"PartnerBearer":[]}],"paths":{"/connection":{"get":{"operationId":"getConnection","summary":"getConnection","description":"Read enabled connection metadata and retain bootstrapCursor before baseline work.","x-required-scopes":[],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Connection"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}}},"/configuration":{"get":{"operationId":"getConfiguration","summary":"getConfiguration","description":"Read complete permitted normalized configuration; unsupported required configuration fails closed.","x-required-scopes":["configuration:read"],"parameters":[],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Configuration"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}}},"/snapshot":{"get":{"operationId":"getSnapshot","summary":"getSnapshot","description":"All three scopes required. Half-open coverage at most seven days and 20 facilities, complete response at most 2 MiB.","x-required-scopes":["configuration:read","availability:read","bookings:read"],"parameters":[{"name":"fromDate","in":"query","schema":{"$ref":"#/components/schemas/Instant"},"required":true},{"name":"toDate","in":"query","schema":{"$ref":"#/components/schemas/Instant"},"required":true},{"$ref":"#/components/parameters/ItemIds"}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Snapshot"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}}},"/bookings":{"get":{"operationId":"listBookings","summary":"listBookings","description":"Complete owned inventory including withdrawn resources and terminal identities. Optional fromDate/toDate must be paired and select overlapping intervals.","x-required-scopes":["bookings:read"],"parameters":[{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/FromDate"},{"$ref":"#/components/parameters/ToDate"}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingPage"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}},"post":{"operationId":"createBooking","summary":"createBooking","description":"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.","x-required-scopes":["bookings:write"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/OperationExpires"}],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCreate"}}}}}},"/bookings/by-external-id":{"get":{"operationId":"findBooking","summary":"findBooking","description":"Permanent byte-exact identity lookup. URL encode literal plus as %2B. A 404 never closes an in-flight operation.","x-required-scopes":["bookings:read"],"parameters":[{"name":"externalBookingId","in":"query","required":true,"schema":{"$ref":"#/components/schemas/OpaqueReference"}}],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}}},"/bookings/{id}":{"get":{"operationId":"getBooking","summary":"getBooking","description":"Read current owned booking and ETag. Foreign connection/club and nonexistent objects return identical 404.","x-required-scopes":["bookings:read"],"parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}},"patch":{"operationId":"updateBooking","summary":"updateBooking","description":"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.","x-required-scopes":["bookings:write"],"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/OperationExpires"},{"$ref":"#/components/parameters/IfMatch"}],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingPatch"}}}}}},"/bookings/{id}/cancel":{"post":{"operationId":"cancelBooking","summary":"cancelBooking","description":"Cancel future owned pending/confirmed reservation. Already canceled returns current state even with old ETag. A started booking requires staff reconciliation. Terminal identity persists.","x-required-scopes":["bookings:write"],"parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/OperationExpires"},{"$ref":"#/components/parameters/IfMatch"}],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCancel"}}}}}},"/changes":{"get":{"operationId":"getChanges","summary":"getChanges","description":"Requires at least one data-read scope. Filter by current read grants independently of callback topics. Never infer missing events from revision gaps.","x-required-scopes":[],"parameters":[{"name":"cursor","in":"query","schema":{"type":"string"},"required":true},{"$ref":"#/components/parameters/Limit"}],"responses":{"200":{"description":"Success","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePage"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}}},"/subscription":{"get":{"operationId":"getSubscription","summary":"getSubscription","description":"Read singleton callback and sanitized health. Returns 404 if absent. Never returns a signing secret.","x-required-scopes":["callbacks:manage"],"parameters":[],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}}},"put":{"operationId":"putSubscription","summary":"putSubscription","description":"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.","x-required-scopes":["callbacks:manage"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/OperationExpires"},{"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."}],"responses":{"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":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"},"410":{"$ref":"#/components/responses/Error410"},"412":{"$ref":"#/components/responses/Error412"},"422":{"$ref":"#/components/responses/Error422"},"428":{"$ref":"#/components/responses/Error428"},"429":{"$ref":"#/components/responses/Error429"},"503":{"$ref":"#/components/responses/Error503"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionWrite"}}}}}}},"components":{"securitySchemes":{"PartnerBearer":{"type":"http","scheme":"bearer","description":"Separately provisioned cryptographically random 32-byte API credential; never query parameters or browser/MCP credentials."}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"$ref":"#/components/schemas/Id"}},"IdempotencyKey":{"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."},"OperationExpires":{"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."},"IfMatch":{"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."},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"}},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":250,"default":100}},"FromDate":{"name":"fromDate","in":"query","schema":{"$ref":"#/components/schemas/Instant"}},"ToDate":{"name":"toDate","in":"query","schema":{"$ref":"#/components/schemas/Instant"}},"ItemIds":{"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":{"Error400":{"description":"Malformed/unknown input or missing idempotency/expiry header.","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error401":{"description":"Invalid credential; cannot settle an uncertain operation.","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error403":{"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"}}}},"Error404":{"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"}}}},"Error409":{"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"}}}},"Error410":{"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"}}}},"Error412":{"description":"capacity_unavailable or revision_mismatch.","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error422":{"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"}}}},"Error428":{"description":"Conditional header required.","headers":{"Cache-Control":{"schema":{"const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error429":{"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"}}}},"Error503":{"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"}}}}},"schemas":{"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"]}}},"x-signature":{"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."},"x-signature-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"}}],"webhooks":{"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"}}}}}}}}}