Inspect the contract. Build against generated truth.
This reference reads the same typed endpoint registry that generates Booking Bible’s OpenAPI 3.1 document. Start with a group, then open only the parameters and examples you need.
Route handlers are the complete deployed /api/v1 surface. Documented operations are the partner-facing contracts currently registered for OpenAPI.
Start with the contract
Authentication and versioning are explicit
Public discovery routes need no credentials. Protected routes accept a user bearer token, an organization-scoped API key, or the method documented for that operation.
Organization-scoped credentials
Create API keys under Admin → Settings → Developer. Each key is shown once, carries explicit scopes, and remains bound to its venue.
Version pinned by header
Send X-Api-Version to pin behavior. The current documented version is 2026-04-11.
Read-only brand-scoped overview, paginated people/search, client details, classes and venue-admin crossover. Requires an active staff session, venue membership and reports.view; non-admin staff also require an explicit active brand reporting assignment. Supports staff JWT or a short-lived read-only delegated brand token. Search is in the request body, never the URL. Missing sources and capped totals are explicit. Small demographic cells are suppressed. No export or messaging authority.
Parameters, scopes and examples
Required scopes
reports.view
brandId, operation overview|people|person|classes|crossover, optional personId; filters include date range, paging, search, stage, membership/pass, attribution, geography, age band, consent, activity and watch ranges, played class/type/teacher, room/start-hour/day, and correlated device/platform/browser/operating-system/app-version filters. See docs/brand-analytics-contract.md.
Rechecks source-session revocation, MFA, current role, active venue membership, report permission and explicit brand assignment. Does not grant access by email or consumer cookie.
Parameters, scopes and examples
Required scopes
reports.view
Query parameters
brandIdstring · required
Brand UUID within the authenticated venue
GET/api/v1/admin/music/accessBearer token
Check music pilot staff access
Rechecks the current staff reporting session, brand scope, management permission and confirmed pilot identity. Returns enabled only for the pilot manager; other staff receive 403.
Parameters, scopes and examples
Required scopes
reports.viewsettings.business
Query parameters
brand_idstring · required
Brand UUID
GET/api/v1/admin/musicBearer token
List private brand music library
Staff report scope and confirmed pilot identity required. Returns brand mixes, versions, assignments, future classes and short-lived private preview URLs.
Parameters, scopes and examples
Required scopes
reports.view
Query parameters
brand_idstring · required
Brand UUID
POST/api/v1/admin/musicBearer token
Manage private brand music
Pilot staff manager/admin with settings.business may begin a path-specific signed resumable upload, finalize metadata, publish, assign, unpublish, activate/discard a replacement or delete. Every mutation is audited.
Pilot staff report scope required. Separates mix selection, attempts and bounded player-observed listening; small cells are suppressed and actual speaker or cast output is unknown.
Staff JWT only, with settings.membership for venue policy or passes.manage for recurring products. Original organization and actor are server-bound. No message or payment is sent by these controls. mode=venue returns defaults and the override list; mode=catalog returns recurring fixed and Flexible memberships only. offset pages 100 items; next_offset is null at the end. Includes venue currency, IANA time_zone, civil today, sparse stored values, server-resolved inheritance and venue reminder limit. No provider configuration is returned.
Staff JWT only, with settings.membership for venue policy or passes.manage for recurring products. Original organization and actor are server-bound. No message or payment is sent by these controls. Read-only canonical resolver and reminder timeline, bounded to 30 venue-local days. Returns preview_hash bound to original actor, target, sparse value and current policy/draft context. It is not delivery or collection authority.
Parameters, scopes and examples
Required scopes
settings.membershippasses.manage
Sparse policy value; null clears a product override. Non-recurring products are refused.
Staff JWT only, with settings.membership for venue policy or passes.manage for recurring products. Original organization and actor are server-bound. No message or payment is sent by these controls. Requires original Idempotency-Key and preview_hash. Same original request replays before mutable catalog reads. SQL locks and compares the authoritative reviewed context; stale context yields immutable refused receipt. Flexible changes use draft CAS and canonical publisher; unrelated draft changes produce draft_review_required. Unknown or malformed outcomes retain original body and key.
Parameters, scopes and examples
Required scopes
settings.membershippasses.manage
Exact preview body with returned preview_hash. Per-key omission inherits for product overrides.
Staff JWT with settings.business. Venue-only max_reminders integer 0..2147483647 and expected_max_reminders are required, with an original Idempotency-Key. Zero reminders does not disable independently configured lapse/cancellation. SQL248 checks dependent custom ladders. Missing invoice row retains daily fallback; existing invoice fields and cadence are preserved.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Returns the same full-visit detail envelope as the member route, with staff-authorized cancellation preview.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Send date and optional provider_id/location_id. The route seeds the selected venue visit’s owned service composition and returns the shared candidate contract.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Optional reason and notification_channels. Returns visit fields plus cancellation_result under data. Every leg and the refund obligation are committed together.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Uses the same provider/location/start/expected quote fields and atomic replacement contract as the member route. Optional notification_channels select one complete-visit reschedule notice.
Parameters, scopes and examples
Path parameters
idstring · required
Whole appointment visit UUID
GET/api/v1/admin/reports/attendanceBearer token
Attendance history report
Class-date attendance (default) or action-date audit history with venue-local and UTC timestamps, actor/status/credit detail, and separate ordinary check-in, Undo, and correction receipts. Missing legacy history is explicit and never receives a fabricated timestamp.
Parameters, scopes and examples
Required scopes
reports.classesmembers.view_insights
Query parameters
viewstring
Whether the date range selects scheduled class dates or recorded action datesDefault: class_date
Requires settings.business and a venue-owned pass_type_id query parameter. Returns the current pass source fingerprint, published choice/add-on identities and optional draft book from pass_component_drafts_v1. No payment, activation or country gate is exposed; responses are no-store.
Requires settings.business and Idempotency-Key. Accepts pass_type_id, expected_revision and a strict draft book with revision, source_pricing_version_id, source_fingerprint and component gross minor amounts by explicit ISO currency. Components cover registration fees, 30+ full pass totals, published add-on unit prices and a published allowance plus validated add-on quantities as a combined total. Source ownership, active published version, allowance rules and add-on eligibility are rechecked. Compare-and-set preserves other passes and active residence_commerce_v1; changed content requires a new revision. Exact replay is checked before changed source terms. Unknown writes retain the same body/key. Drafts never activate checkout, country gates, tax calculations or sends.
GET/api/v1/admin/residence-commerceBearer token
Read reviewed residence commerce price books
Venue-scoped versioned catalog price books and per-country checkout gates. Requires settings.business; a missing or invalid policy keeps checkout closed for configured products.
PATCH/api/v1/admin/residence-commerceBearer token
Revise a catalog price book and country gates
Optional country_drafts retain incomplete classification, place-of-supply and provider research separately from active rules; drafts do not approve checkout. Newly enabled or changed Stripe Tax markets are verified against seller Tax settings, active registration country/scheme coverage and charge-model-scoped product classification. Unchanged approvals retain their original reviewer and remain editable during provider outages. Requires settings.business and Idempotency-Key. Accepts a legacy pass_type_id or typed item identity and saves one owned catalog revision with compare-and-set, audits every country decision, and requires a new price book version when terms change. Fixed pass, product-package and service-bundle books use exact gross prices. Products, product packages, bundles, services and service_variant items support sparse exact gross currency drafts with all country gates closed; service variants are scoped through their owning service. Product packages verify both their own venue and their nonrecurring owning product venue. Known ISO prices retain exact currency minor precision. Reserved product_variant identities remain unsupported because the catalog has no product-variant entity. One-time foreign tax, configurable selections and unsupported checkout paths remain closed. Recurring foreign enabled countries require reviewed place-of-supply, registration and processor-account references.
Requires settings.business, Idempotency-Key and the current brand updated_at value. Writes a validated whole versioned override with compare-and-set and audit.
GET/api/v1/admin/sales-snapshotBearer or API key
Sales snapshot
Venue-local sales report, gated by reports.view. Optional range=today|yesterday|7d|mtd|30d|custom, basis=cash|accrual, from/to, methods, categories, brand, location and refresh=1. Existing aggregates remain unchanged; cashHeadline adds separate major-unit gross/count totals by recorded payment or gift-card currency and unresolvedCount. CashHeadline is null for accrual; clients must not relabel legacy aggregates with a current venue currency.
Revenue, bookings, attendance, 30-day active clients, and average revenue per client — per brand for the given period (default last 30 days). Uses bookings.brand_id and payments.brand_id populated by HYC_2. Returns venue-wide (unbranded) totals alongside the brand rows.
Parameters, scopes and examples
Required scopes
read:reports
GET/api/v1/admin/dashboard/todayBearer or API key
Today at a glance
Today's class timeline with booking counts, check-in status, and room assignments.
Parameters, scopes and examples
Required scopes
read:schedule
GET/api/v1/admin/scheduleBearer or API key
Admin schedule
Full schedule view with internal data: per-status booking counts, notes, cancellation reasons, updated_at concurrency tokens, fail-closed historical capabilities, additive `course_identifiers` pills (`{course_id,label,tone,course_name}`, hidden courses included) for workshop dates and, for a bounded window (from and to at most 62 days apart) or include=notification_availability, at most 300 rows, notification_availability.{cancel,edit,substitute}.{clients,instructor}.{email,sms,push} (available when the venue gate passes and at least one recipient is reachable; reach counts and blocked reasons included). Otherwise the key is omitted: read one class with GET /api/v1/admin/schedule/{id} (which also carries restore). include_historical=true requires scheduling.manage_history.
Parameters, scopes and examples
Required scopes
read:schedule
Query parameters
start_datestring
Inclusive ISO date/time lower bound
end_datestring
Inclusive ISO date/time upper bound
include_historicalstring
Include protected historical class rows; requires scheduling.manage_historyDefault: false
Staff JWT and bookings.manage required; API keys and mixed credentials are rejected. Calls the canonical availability engine with the authoritative active organization, purpose staff and strict read errors. Staff-only services and rescheduling during wind-down do not inherit public sale gates. Requires service_id and a valid YYYY-MM-DD date; accepts provider_id, variant_id, location_id and reschedule_id. A reschedule source is excluded from conflicts only after same-tenant and same-service ownership checks. Returns provider and opted-in facility slots with private no-store caching. Facility identity is provider_id null plus room_id/resource_kind room. Reschedule walks the tenant-verified original room through an internal staff-only override, including non-default rooms; caller room overrides are rejected. Read-only discovery never authorizes creation or changes engine/SQL mutation checks.
Parameters, scopes and examples
Required scopes
bookings.manage
Query parameters
service_idstring · required
Service UUID in the active organization
datestring · required
Venue-local calendar date YYYY-MM-DD
provider_idstring
Optional provider UUID
variant_idstring
Optional service variant UUID
location_idstring
Optional service location UUID
reschedule_idstring
Existing same-tenant, same-service appointment UUID to exclude from occupancy
GET/api/v1/admin/appointmentsBearer token
Venue appointment schedule
Business-app venue-wide appointment list with updated_at concurrency tokens and authoritative, fail-closed historical capabilities. Filters by ISO window, direction, status, provider, and location. Permission: bookings.manage.
Parameters, scopes and examples
Query parameters
fromstring
Inclusive ISO start time
tostring
Exclusive ISO end time
directionstring
upcoming | pastDefault: upcoming
statusstring
Appointment status
provider_idstring
Provider UUID
location_idstring
Location UUID
limitnumber
Maximum 200Default: 100
POST/api/v1/admin/appointmentsBearer token
Create an appointment for a client
Creates a tenant-bound current/future appointment for a known member or contact-complete guest through the canonical atomic appointment engine. Venue-local past dates and historical attestation fields fail closed until the dedicated executor is installed. Permission: bookings.manage. Idempotency-Key required. Client delivery is default-silent: only an explicit notification_channels selection of email, sms, and/or push can send. A non-empty selection requires notifications.send and an authoritative availability preflight before the mutation; an unavailable channel fails without creating the appointment. Omitted or empty channels and legacy notify booleans remain silent. notify.clients.channels is accepted as the per-audience equivalent; the 422 details carry {audience, channel, reason_code, unavailable_reason}; success adds notify_outcome.
Tenant-bound client, provider, service, location, payment, notes, lifecycle state, updated_at concurrency token, and authoritative fail-closed historical capabilities for the Business app. Additive historical_correction_eligibility returns candidate/reason_code/reason from a bounded private tenant-scoped row read. Definite SQL dependencies suppress corrections without exposing payment or operation identities. A candidate still requires locked SQL checks; global capability is not record eligibility. Permission: bookings.manage.
Parameters, scopes and examples
Path parameters
idstring · required
Appointment UUID
PATCH/api/v1/admin/appointments/{id}Bearer token
Operate an appointment
Atomic ordinary check-in, start, complete, no-show, cancel, or reschedule with the exact expected_updated_at token returned by GET. A stale token returns 409 STALE_TARGET without mutation. Each action is checked against its canonical permission. Past/terminal appointments, past reschedule targets, and historical attestation fields fail closed until the dedicated executor is installed. Idempotency-Key required. No-show, cancel, and reschedule are default-silent and accept an explicit notification_channels selection of email, sms, and/or push; a non-empty selection requires notifications.send and an authoritative availability preflight before mutation. An unavailable channel fails without changing the appointment. Omitted or empty channels and legacy notify booleans remain silent. Check-in, start, and complete are non-client-contact actions and reject notification_channels. notify.clients.channels is accepted as the per-audience equivalent (mixing both shapes returns 422 MIXED_NOTIFY_SHAPES); an unavailable channel returns 422 APPOINTMENT_NOTIFICATION_CHANNEL_UNAVAILABLE with {audience, channel, reason_code, unavailable_reason}; success adds notify_outcome. GET responses add notification_availability_by_action {cancel, no_show, reschedule} in the staff availability contract.
Parameters, scopes and examples
Path parameters
idstring · required
Appointment UUID
Discriminated appointment action with the opaque updated_at token returned by the appointment detail
Dedicated, atomic appointment-history command contract. Requires a UUID Idempotency-Key, appointments.manage_history plus the ordinary operation permission, REWRITE attestation, and a past effective_at. Existing-row operations require the exact expected_updated_at and expected_status returned by the appointment detail; retrocreate accepts only the closed non-financial appointment intent. Corrections are always silent and reject notification controls. The route returns 503 HISTORICAL_EXECUTOR_UNAVAILABLE without table-call fallback until the separately reviewed correct_appointment_historical database RPC is installed.
Parameters, scopes and examples
Required scopes
appointments.manage_history
Path parameters
idstring · required
Appointment UUID
Closed appointment correction; history_confirmation_token must be REWRITE
Tenant-bound class and appointment reviews for the Business app. Supports source, visibility, rating, and pagination filters. Anonymous reviewer identity is never returned. Permission: feedback.view.
Publishes or unpublishes one tenant-bound class or appointment review. Permission: feedback.manage. Idempotency-Key required.
Parameters, scopes and examples
Path parameters
sourceTypestring · required
class | appointment
idstring · required
Review UUID
GET/api/v1/admin/feedback-settingsBearer or API key
Read internal feedback and review cadence settings
JWT permission feedback.configure, or a venue-bound API key with write:settings. Mixed credentials are rejected. Reads fail unavailable on settings query errors and do not seed configuration. Returns the public feedback settings DTO, including first_completed_visit, repeat_every_completed_visits, visit_scope and their legacy prompt-prefixed mirrors. No raw organization settings are returned.
Parameters, scopes and examples
Required scopes
write:settings
PATCH/api/v1/admin/feedback-settingsBearer token
Configure internal feedback and review cadence
JWT permission feedback.configure only; API keys and mixed credentials are rejected. Idempotency-Key is bound to the organization, actor, operation and validated body. This saves configuration only, never sends a review request. Unknown saves return 503 SAVE_OUTCOME_UNKNOWN with phase:write and saved:unknown and retain the claim. A confirmed save whose reload fails returns saved:true, phase:postcommit, reload_failed:true; refresh, do not treat this as rollback. First completed visit N and repeat interval M must be saved together. N and non-null M are integers from 1 to 50; null M means first only. Explicit prompt_channels: [] disables internal prompt channels. Omitted fields retain existing configuration.
Parameters, scopes and examples
Required scopes
feedback.configure
Validated feedback-settings patch; aliases first_completed_visit, repeat_every_completed_visits and visit_scope are accepted.
GET/api/v1/admin/external-review-settingsBearer or API key
Read public review-request configuration
JWT permission feedback.configure, or a venue-bound API key with write:settings. Mixed credentials are rejected. Reads fail unavailable on settings query errors and do not seed configuration. Returns public review sites, configured email/SMS channels and independent completed-visit cadence. A clicked public review link is not proof that a review was submitted.
JWT permission feedback.configure only; API keys and mixed credentials are rejected. Idempotency-Key is bound to the organization, actor, operation and validated body. This saves configuration only, never sends a review request. Unknown saves return 503 SAVE_OUTCOME_UNKNOWN with phase:write and saved:unknown and retain the claim. A confirmed save whose reload fails returns saved:true, phase:postcommit, reload_failed:true; refresh, do not treat this as rollback. Public review requests use configured email/SMS channels and existing consent, suppression and delivery gates. N and M are independent from internal feedback. suppress_after_public_review means stop after the tracked public link was opened, not verified review completion.
Parameters, scopes and examples
Required scopes
feedback.configure
Public settings patch; first_completed_visit and repeat_every_completed_visits are paired. No automatic delivery or tenant-specific seeding.
JWT permission feedback.configure only. Read-only preview of saved configuration and recent completed-visit candidates. It uses the canonical visit counter and cadence decisions, but does not prove delivery reachability, consent, quiet hours or source overrides. preview_kind is cadence_configuration and delivery_verified is false. Legacy eligible means cadence qualification only. No messages, prompts or settings are created.
Parameters, scopes and examples
Required scopes
feedback.configure
Choose internal or public saved configuration; unsaved form drafts are not evaluated.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Idempotency-Key is required and bound to organization, actor, operation, resource and validated body. Web and REST share the same allowlisted writer, tenant-reference checks, plan/franchise limits, audit and cache invalidation. Uses the existing web archive core: deactivates the service without deleting existing appointments, retains the franchise lock, and emits the canonical service.deleted audit/webhook event. Returns {ok:true,write_warnings?}, not a service row. Unknown writes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim; replay the exact key or reload authoritative state. This endpoint does not notify customers.
Parameters, scopes and examples
Required scopes
bookings.manage
Required empty JSON object. Tenant, actor, service identity and notification flags cannot be supplied in the body.
Request body
{}
GET/api/v1/admin/servicesBearer token
List the venue service catalog
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Returns explicit catalog fields, excluding payout configuration. Read failure is unavailable, not a successful empty catalog. No customer appointments are returned.
Parameters, scopes and examples
Required scopes
bookings.manage
Query parameters
include_inactivestring
Include inactive services when exactly true.Default: false
POST/api/v1/admin/servicesBearer token
Create a service in the venue catalog
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Idempotency-Key is required and bound to organization, actor, operation, resource and validated body. Web and REST share the same allowlisted writer, tenant-reference checks, plan/franchise limits, audit and cache invalidation. Prices are decimal MAJOR currency units, not integer minor units. Currency must match authoritative venue currency. The route refuses body tenant IDs, unknown columns and arbitrary image URLs. Confirmed saves return the service DTO and optional write_warnings. Unknown transport/database outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim: refresh authoritative state or retry the exact operation/key, never blindly create again. Known prewrite refusals may release the claim. A failed acknowledgement cannot turn a known save into rollback.
Parameters, scopes and examples
Required scopes
bookings.manage
Validated catalog create input. Native service image, variants and provider-hours authoring are separate companion work, not implied by this endpoint.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Foreign or missing service IDs return not found; query failures remain unavailable. The catalog DTO does not contain provider payout configuration.
Parameters, scopes and examples
Required scopes
bookings.manage
PATCH/api/v1/admin/services/{id}Bearer token
Edit or deactivate a venue service
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Idempotency-Key is required and bound to organization, actor, operation, resource and validated body. Web and REST share the same allowlisted writer, tenant-reference checks, plan/franchise limits, audit and cache invalidation. Empty patches are rejected before claiming a key. Unrelated edits retain stored service currency; monetary edits validate the resulting stored-plus-patch deposit configuration. is_active:false deactivates, without deleting existing appointments. Reactivation rechecks the plan limit. Confirmed saves return the service DTO and optional write_warnings. Unknown transport/database outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim: refresh authoritative state or retry the exact operation/key, never blindly create again. Known prewrite refusals may release the claim. A failed acknowledgement cannot turn a known save into rollback.
Parameters, scopes and examples
Required scopes
bookings.manage
Only changed catalog fields; no tenant reassignment, arbitrary image URL or direct booking-mode grant.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Service ownership is checked before storage access. Strict JPG, PNG or WebP metadata, positive integer file_size up to 5 MiB. Returns {uploadUrl,token,path,maxBytes}; PUT the file bytes to uploadUrl, then call finalize. Paths have an immutable venue/service/generation prefix. Existing lazy bucket provisioning remains part of this authorized source operation; this does not mean hosted storage has been provisioned. This request does not attach an image or contact clients.
Parameters, scopes and examples
Required scopes
bookings.manage
Only declared content type and byte size; no tenant IDs or arbitrary URL.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Service ownership is checked before storage access. Idempotency-Key is required and scoped to actor, venue, service, operation and body. A confirmed save may include write_warnings for secondary failures. Unknown outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim; reload authoritative state or retry the exact key, never blindly repeat with a new key. Accepts only the original path minted for this venue and service. Validates bytes and minimum 1200 by 675 dimensions, generates immutable 1600/800/400 WebP images, and compares both previous image URL and JSON media before attaching them. Returns {imageUrl,write_warnings?}. Cleanup never lists/deletes a service prefix or removes current-generation heroes. Storage conflicts are unknown, not proof that prior bytes match. No customer notifications.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Service ownership is checked before storage access. Idempotency-Key is required and scoped to actor, venue, service, operation and body. A confirmed save may include write_warnings for secondary failures. Unknown outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim; reload authoritative state or retry the exact key, never blindly repeat with a new key. No request body. Returns {ok:true,write_warnings?}. Clears only the image and hero media keys with a compare-and-set; preserves sibling media and current appointment history. Only exact owned former hero paths are eligible for cleanup. Arbitrary external images and peer uploads are never deleted. No customer notifications.
Parameters, scopes and examples
Required scopes
bookings.manage
GET/api/v1/admin/booking-offeringBearer token
Read booking products and eligible offering changes
User JWT and settings.business required; API keys and mixed credentials are rejected. Returns contract_version:1, organization_id, mode, product lifecycle/status/sales flags and both internal transition choices with availability, reason and requiredPlanKeys where relevant. Uses the same authority as web Business settings. Read failure is 503, not an empty or class-default configuration. No prices, raw settings or client identities are returned.
Parameters, scopes and examples
Required scopes
settings.business
PATCH/api/v1/admin/booking-offeringBearer token
Change an eligible venue booking offering
User JWT and settings.business required. Strict mode-only body; Idempotency-Key binds the selected organization, actor and validated body. Uses the existing atomic offering RPC, commercial access gates and wind-down/history retention; it never changes a paid plan, charges a customer or archives a product. Success returns contract_version:1, organization_id and mode, with refresh_failed:true if independent surface refresh failed after save. Errors add write_state:not_written or unknown; PLAN_QUOTE_REQUIRED includes required_plan_keys without inventing prices. Preserve the same logical key on unconfirmed outcomes and reload before a new intent. An acknowledgement failure never replaces a known save response. Separate paid-plan preview/acceptance and native authoring companions remain required before full parity is claimed.
Parameters, scopes and examples
Required scopes
settings.business
Required mode: classes, appointments or both. Internal classes preserves grandfathered configurations; public commerce still has two model names. No organization, actor, price, tier or raw settings fields.
User JWT and bookings.manage required; API keys and mixed credentials are rejected. Evaluates the canonical appointment readiness facts for the selected organization without writing. Returns contract_version:1, organization_id, evaluated status ready|incomplete, missing labels, evaluated_at and current booking_mode. persisted_onboarding is separate onboarding provenance and is never the evaluated status; pending and seed_errors are not live facts. Read failure is 503 QUERY_FAILED, not fabricated ready/incomplete. business_type does not determine readiness. Native must bind this exact contract; this is not a claim that native screens are complete.
Parameters, scopes and examples
Required scopes
bookings.manage
GET/api/v1/admin/appointment-policiesBearer token
Read effective appointment booking policies
User JWT and settings.business required; API keys and mixed credentials are rejected. Lifecycle uses resolveAppointmentSettingsLifecycleGate, not canVenueManageAppointmentSettings: active, wind_down and read_only authorize; archived and unknown/malformed status refuse; a missing appointments product row may use booking_mode plus service count; a product-domain query failure is 503 QUERY_FAILED and never a legacy fallback. Returns contract_version:1, organization_id and the allowlisted effective configuration: slot_interval_minutes, payment_at_booking_mode with derived require_payment_at_booking, self_service_cutoff_hours plus malformed flag, grouped_visits venue settings, and row_present. Does not expose platform kill switches, raw settings, deposits, checkout or POS. Read failure is 503, not invented defaults presented as a query success. Lifecycle refusal is 403 APPOINTMENT_SETTINGS_UNAVAILABLE.
User JWT and settings.business required, plus resolveAppointmentSettingsLifecycleGate (active/wind_down/read_only; archived and unknown/malformed refuse; product query failure is not a booking_mode fallback). Strict allowlisted body for slot_interval_minutes, payment_at_booking_mode, self_service_cutoff_hours and grouped_visits. grouped_visits refund_terms require confirmed:true; platform kill switches are not writable. Idempotency-Key binds selected organization, actor, operation appointment_policies.patch and the validated body. Writes use compare-and-set merge of the shared appointments settings row so unrelated keys survive; an absent-row unique conflict retries. Success returns the GET DTO, with refresh_failed:true if independent cache/audit follow-up failed after save. Errors add write_state:not_written or unknown. Preserve the same logical key on unconfirmed outcomes and reload before a new intent. An acknowledgement failure never replaces a known save response. Native authoring companions remain required before full parity is claimed.
Parameters, scopes and examples
Required scopes
settings.business
At least one allowlisted field. payment_at_booking_mode is venue, online or client_choice. self_service_cutoff_hours may be null to clear. grouped_visits requires enabled, max_services_per_visit, refund_terms and confirmed. No organization, actor, require_payment_at_booking, raw settings, deposit, checkout or plan fields.
Uses the same tenant-bound variant deletion core as the web admin. Prefer PATCH is_active:false to keep the variant record; deletion retains existing database reference restrictions and may be refused. Requires bookings.manage and JWT; API keys/mixed credentials are rejected. Idempotency-Key binds tenant, actor, parent service and variant. Unknown writes retain the request key: reload and review before a new intent. No appointment or payment mutation is performed by this handler.
Creates a duration/price option on the parent service. Price inherits the parent service currency and is decimal major units (49.50 stays 49.50). No organization_id, payout, intake, or waiver fields. Idempotency-Key required. Permission: bookings.manage. JWT only.
Partial update of one variant that belongs to the parent service. Empty PATCH is 400. Prefer is_active false over delete. Idempotency-Key required. Permission: bookings.manage. JWT only.
Optional eligible_only=true returns only active assignments with an active service-delivering membership of the authenticated venue; a failed eligibility read returns 503. Default reads retain inactive assignments for administration. Public assignment projection for one tenant service: names, photos, duration/price overrides, primary/sort/active. No bio, contact, compensation, or payout. Empty array means none; 503 is a failed read. Permission: bookings.manage. JWT only.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
idstring · required
Service UUID
Query parameters
eligible_onlystring
true for eligible booking choices; false or omitted retains setup assignments
Upserts an active delivering membership onto the service. Instructor/service_provider primary role or additive delivering capability required. Generic staff/reception is 403. custom_price_amount is decimal major units in the parent service currency; null inherits. Idempotency-Key required and bound to tenant, actor, target and validated body. Permission: bookings.manage. JWT only. Team capacity uses the canonical plan authority; its preflight and upsert are not an atomic capacity reservation.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
idstring · required
Service UUID
providerIdstring · required
Staff profile UUID
Optional duration/price overrides, primary, sort, and active.
Partial update of an existing assignment. Empty PATCH is 400. Missing assignment is 404. Idempotency-Key required. Permission: bookings.manage. JWT only.
Removes the future offering link. Historical appointments are preserved. Inactive memberships may be unassigned. Idempotency-Key required. Permission: bookings.manage. JWT only.
Staff-detail assignment pane. staff.view plus venue membership; catalog edit is not required to read names. Empty array means none assigned. Permission: staff.view. JWT only.
Parameters, scopes and examples
Required scopes
staff.view
Path parameters
staffIdstring · required
Staff profile UUID
GET/api/v1/admin/roomsBearer token
List venue rooms and stations
Explicit room setup projection, including inactive rooms. Requires settings.business. JWT only; API keys and mixed credentials are refused. Empty list is distinct from a failed read.
Parameters, scopes and examples
Required scopes
settings.business
POST/api/v1/admin/roomsBearer token
Create a room or station
Shared web/native room core. Explicit location must belong to the venue; an omitted location is inferred only when exactly one active location exists. Floor-plan URLs require the separate upload/finalize flow. Does not create locations or change paid tiers. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
settings.business
Room name, positive capacity and supported room configuration.
One room scoped to the authenticated venue. No client, treatment, payroll or booking records. Missing room is 404; failed read is not an empty room.
Parameters, scopes and examples
Required scopes
settings.business
Path parameters
idstring · required
Room UUID in the authenticated organization
PATCH/api/v1/admin/rooms/{id}Bearer token
Update room setup
Nonempty supported room fields, including is_active for retirement. Capacity reduction retains the existing future-class cap projection; ROOM_CAPACITY_PARTIAL includes saved:true and the saved room when that secondary projection fails. No class booking or checkout change. floor_plan_url accepts only null to clear its reference; new URLs require upload/finalize. Clearing does not delete storage objects. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
settings.business
Path parameters
idstring · required
Room UUID in the authenticated organization
Supported partial room configuration.
Request body
{
"is_active": false
}
GET/api/v1/admin/locationsBearer token
List location setup references
Limited authoring DTO for selecting a room location and editing existing media/hours. Either settings.business or locations.manage permits this read. Does not create/delete locations, set primary location or expose billing settings.
Parameters, scopes and examples
Required scopes
settings.businesslocations.manage
GET/api/v1/admin/locations/{id}Bearer token
Read location media and hours
Limited authoring DTO for one tenant location. Either settings.business or locations.manage permits this read. No location billing or client records.
Parameters, scopes and examples
Required scopes
settings.businesslocations.manage
Path parameters
idstring · required
Location UUID in the authenticated organization
PATCH/api/v1/admin/locations/{id}Bearer token
Update location media and opening hours
Requires locations.manage. A media/hours patch supports image_url, gallery_urls and opening_hours. Media permits deliberate external URLs or null removal; storage-object URLs must use finalize. Alternatively send only remove_gallery_url to remove one exact saved gallery reference, including finalized uploads, while preserving the other images. Removal uses a tenant-scoped JSONB compare-and-swap, is a no-op if already absent, returns GALLERY_CONFLICT on concurrent change, and never deletes storage objects. Do not combine removal with other fields. Hours contain mon through sun, each null (closed) or open/close HH:mm with open before close; null clears hours. Not a location provisioning or billing API. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
locations.manage
Path parameters
idstring · required
Location UUID in the authenticated organization
Nonempty media/hours patch, or the single remove_gallery_url field.
Checks tenant room ownership before minting a signed venue-assets upload. JPEG/PNG/WebP only, at most 5 MiB. Returns upload_url, token, path, public_url and max_bytes. Upload authorization alone does not store the URL on the room.
Parameters, scopes and examples
Required scopes
settings.business
Path parameters
idstring · required
Room UUID in the authenticated organization
Declared format and size; finalize independently checks bytes.
Exact organization/room object path, ownership and downloaded size/magic-byte validation before storing floor_plan_url. Returns the explicit room DTO. No arbitrary file or foreign resource path. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Requires locations.manage and tenant location ownership. kind is hero or gallery. JPEG/PNG/WebP up to 5 MiB. Returns signed upload fields, max_bytes and kind; does not yet change location media.
Requires locations.manage. Exact owned path and downloaded byte checks precede persistence. Gallery append uses JSONB compare-and-swap: duplicate path is unchanged, capacity 20 refuses, malformed data refuses, concurrent change returns GALLERY_CONFLICT without dropping another image. Returns the limited location DTO. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
locations.manage
Path parameters
idstring · required
Location UUID in the authenticated organization
GET/api/v1/admin/providers/{id}/hoursBearer token
List provider working hours
Tenant-local working-hours rows for one delivering staff member. Empty array means none; 503 UNAVAILABLE is a failed read. include_inactive=true includes deactivated rows. Permission: bookings.manage. JWT only.
Creates a venue-local hours row. Times are clock values; effective_from defaults to the venue calendar date. Overlap/duplicate rows are refused. Idempotency-Key required. Permission: bookings.manage. JWT only.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
idstring · required
Staff profile UUID
Working-hours create. Location must belong to this venue when set.
Partial update of one hours row owned by the provider in this venue. Empty PATCH is 400. Idempotency-Key required. Permission: bookings.manage. JWT only.
Soft-deactivates the hours row. Historical appointments are preserved. Inactive memberships may be cleaned up. Idempotency-Key required. Permission: bookings.manage. JWT only.
Business bearer-JWT read for an individual professional whose active organization remains their own workspace. The server re-proves the active practitioner partnership, active host membership, primary/substitute assignment and relationship-scoped roster access. It returns genuine class and booking updated_at CAS tokens, venue-local day_state, operation-filtered historical_capabilities, the authoritative client_contact_visibility result, the fail-closed client_pass_visibility (visible|hidden) set by the host venue per collaborator, and only operational booking/pass details including the frozen Flexible selection, remaining allowance and expiry warnings. When client_pass_visibility is hidden every attendee pass is null and pass-derived warnings are omitted. Contact values use the shared none/masked/full redactor. Terminal cancelled rows require include_historical_records=true plus the host scheduling.manage_history and matching ordinary action grant. No unrestricted profile, account credit, unrelated passes or financial history is exposed.
Parameters, scopes and examples
Required scopes
schedule.view_own
Path parameters
classIdstring · required
Assigned host class UUID
include_historical_recordsboolean
Include supported terminal roster records when host history access permits
Business bearer-JWT contract for an individual professional whose active organization remains their own workspace. The server derives the host venue and proves the exact active practitioner partnership, active host membership, primary/substitute class assignment, a past scheduled/completed class, and scheduling.manage_history plus the operation's ordinary host permission. Only existing-booking attendance, no-show, cancellation, and invalidation corrections are accepted; home-tenant and generic admin-booking execution are never reused. A UUID Idempotency-Key, typed REWRITE confirmation, reason, effective_at, genuine expected_updated_at booking CAS, and genuine expected_class_updated_at class CAS are mandatory. Success returns the database-read updated_at and class_updated_at tokens; stale state returns 409, while failed post-commit token readback returns 503 and requires an exact retry with the same key. Delivery defaults silent; an explicit Email/SMS/Push selection first requires the host's notifications.send permission and then fails 422 before mutation because historical delivery is unsupported. Executor: 20261020000009.
Parameters, scopes and examples
Required scopes
scheduling.manage_history
Path parameters
classIdstring · required
Assigned host class UUID
bookingIdstring · required
Host class booking UUID
One existing partner-roster correction. The host organization is relationship-derived and cannot be selected in the body.
Returns venue-owned purchase and promotion catalog selections, including retired items used by historical rules, plus the categories supported by the venue capabilities. Appointment-only and mixed venues use the same catalog authority. The authenticated venue supplies the scope. No customer history or targeting authority is returned. Permission: marketing.flash_sales.
Returns consent, preference, contact and suppression-aware email/SMS reach for a venue-scoped marketing audience. audience_filter.eligibility accepts shared V1 commerce include/exclude rules for current pass state, completed purchases and prior promotion use, including inclusive custom dates in the venue timezone. Valid rules return policy code COMMERCE_AUDIENCE and are evaluated server-side; unresolved facts produce an unavailable matched count. Unknown or malformed rules are refused without being dropped. Health, attendance and fitness-behaviour sources are not accepted. Permission: marketing.flash_sales.
Returns canonical refundable headroom, currency_decimal_places, payer receipt contacts, receipt_channels availability for Email/SMS/Push with explicit disabled reasons, original card brand/last4, venue refund destinations, and eligibility. Notification availability uses the same proven recipient and preference checks as the refund workflow. Native clients must use this response instead of deriving refund options locally. Staff refund notification choices start empty; only explicit channels request contact.
Idempotency-Key required. Body uses major units: { amount, reason? (staff-only), client_receipt_comment? (client-visible), destination_id? (original or venue method id), method_reference?, guest_booking_id? }. Returns the immutable review fields and full-refund confirmation phrase. While the payment has an open critical refund recovery case (processor_result_ambiguous, refund_state_mismatch or venue_funds_restore_required) the review is refused with 409 REFUND_RECOVERY_CASE_OPEN; resolve or re-issue on the web payment detail first.
Recent published flash sales with promo code, lifecycle_status, linked campaign, and delivery outcomes for the Business app. Includes email_cta_destination {type:"offer"} or {type:"custom",url:string}; legacy rows default to offer. Private draft_input is never returned; drafts use /admin/marketing/flash-sales/drafts. Permission: marketing.flash_sales.
Creates a venue-scoped promo and public offer, optionally queuing a consent-gated email/SMS campaign. Idempotency-Key required. Permission: marketing.flash_sales; non-empty notification_channels also requires notifications.send before business mutation and X-BookingBible-Confirm-Delivery: QUEUE. Optional X-BookingBible-Reviewed-Recipient-Count compares reviewed reach with the fresh consent-safe audience before mutation. Optional email_cta_destination is {type:"offer"} (also the omission default) or {type:"custom",url:string}: an absolute HTTPS URL of at most 2048 characters without credentials, control characters or backslashes. Invalid input returns 400 VALIDATION_ERROR. The destination changes only the email button; SMS and public purchase links remain canonical venue offer links. Generated announcement copy does not reveal the internal coupon. This setting never selects a delivery channel or authorizes sending.
Parameters, scopes and examples
Flash-sale fields plus optional email-only button destination. Existing notification and confirmation controls still apply.
Deactivates the sale and its linked promo code. Idempotency-Key required. Permission: marketing.flash_sales.
Parameters, scopes and examples
Path parameters
idstring · required
Flash sale UUID
GET/api/v1/admin/marketing/campaignsBearer token
Recent campaign outcomes
Venue-scoped campaign lifecycle and delivery metrics for the Business app. Campaign type is email, sms, both (email + SMS only), or push. Push uses subject as title and sent_count as recipient acceptance count, not device count or proof of delivery. Reading history and saving unscheduled drafts require marketing.campaigns, not notifications.send. Scheduled creation, queueing, sending, and actual test delivery additionally require notifications.send in the canonical campaign engine; no destination or logo setting grants delivery authority.
Idempotency-Key required and used as the operation key. Resolves {id} in the authenticated organization, runs UTC flashSaleDuplicatePrefill, and inserts a deterministic draft UUID derived from org/actor/source/action/key. Unique conflict replays the matching org/actor/draft row without overwrite. Never mutates the source, creates a promo, publishes, or sends. The copy is a standalone sale; web admin "+ Add run" (a draft in the same run series, migration 109) is not part of this endpoint and its series fields are not returned. Permission: marketing.flash_sales.
Returns at most 50 organization-scoped draft rows (id, name, window, products, edit_revision). Raw draft_input, promo codes and campaigns are omitted. Permission: marketing.flash_sales.
Returns validated FlashSaleDraftWire plus edit_revision for a private draft in the authenticated organization. Invalid stored input fails closed. Permission: marketing.flash_sales.
Idempotency-Key required. Save-only full FlashSaleInput round-trip including presentation and mixed family. Requires expected_revision compare-and-set; stale revisions return 409 DRAFT_CHANGED. Not publication. Permission: marketing.flash_sales.
Idempotency-Key required. Converts the same draft row to published through publish_flash_sale_v1. Body is FlashSaleDraftWire plus expected_revision. Returns draft_transition {status:published, draft_id, published_sale_id}. Same key/fingerprint replays the durable receipt. A new key against the same draft returns 409 DRAFT_ALREADY_PUBLISHED. Non-none notification_channels require notifications.send and X-BookingBible-Confirm-Delivery: QUEUE before the RPC. Optional replace-the-running-sale: a 422 FLASH_SALE_OVERLAP may include error.details.replaceable_sale; repeating the request with X-BookingBible-Replace-Sale-Id and X-BookingBible-Replace-Sale-Revision (both or neither) ends that sale and publishes in one transaction through publish_flash_sale_v2, carrying its promo claims into max_redemptions and adding replacement to the response; other overlaps still fail. Permission: marketing.flash_sales.
Parameters, scopes and examples
Required scopes
marketing.flash_sales
Path parameters
draftIdstring · required
Draft UUID
GET/api/v1/admin/gift-cardsBearer token
List venue gift cards
Returns the venue gift-card ledger for the Business app, including remaining balance and module-off historical rights. Optional code query looks up one card. New issue/options remain module-gated. Permission: gift_cards.sell.
Parameters, scopes and examples
Required scopes
gift_cards.sell
Query parameters
codestring
Exact gift-card code for a balance lookup
POST/api/v1/admin/gift-cardsBearer token
Issue a desk gift card
Creates or recovers one original venue gift card. Delivery is default-silent: omitted send_notification never delivers. send_notification=true requires notifications.send plus a recipient. Idempotency-Key required; NEW issues require bb-gift-issue-v1-<UUID v4>. Already-bound originals recover with their exact original key/body; unbound legacy keys require review and never mint. Success adds purchase_preserved=true and operation_id to id/code/amount/notification_sent/warning. 409 IDEMPOTENCY_KEY_REUSE_MISMATCH, GIFT_CARD_ISSUE_PROCESSING or GIFT_CARD_ISSUE_REVIEW_REQUIRED retain the original operation; never retry with a fresh key. HTTP cache TTL is not issuance authority. Permission: gift_cards.sell.
Explicit, default-silent staff delivery of an already-paid gift card. channels chooses Email/SMS/Push; omitted or empty sends nothing. Recipient overrides freeze with the delivery and do not change purchase fields. Optional predecessors email/sms UUIDs identify an explicitly confirmed successor to a known terminal delivery; omitted means the one initial channel delivery. Changed Idempotency-Key values cannot fork either identity. Per-channel accepted, delivered, pending, needs_review, unavailable or failed results preserve the completed purchase; accepted is not a delivery receipt. Ambiguous provider outcomes remain held and HTTP claims are not released. Push reports unavailable without a durable recipient binding. Requires gift_cards.sell, members.contact, and notifications.send. Idempotency-Key required.
Parameters, scopes and examples
Required scopes
gift_cards.sellmembers.contactnotifications.send
Path parameters
idstring · required
Gift card UUID
Explicit post-sale gift delivery; optional predecessors map email/sms to terminal delivery UUIDs for a separately confirmed resend
Delivery is default-silent. send_notification=true sends the inbox reply and requires notifications.send; otherwise the body is stored as an internal staff note. Idempotency-Key required. Permission: bookings.manage.
{
"body": "We can do Thursday at 10.",
"send_notification": false
}
GET/api/v1/admin/services/bundlesBearer token
List combo packages
Phone-useful combo-package list with price and item count. Full bundle builder stays on web admin. Permission: bookings.manage.
Parameters, scopes and examples
Required scopes
bookings.manage
GET/api/v1/admin/staffBearer token
List staff
Venue-membership staff directory: name, role, membership_status and capabilities; is_active is true only for active membership. Includes additive service-delivering collaborators, but returns email/phone as null when the profile belongs to another home venue. Assignment selectors must use active status and actual delivery capabilities, never the staff label alone. No payroll or commission data. Permission: staff.view.
Parameters, scopes and examples
Required scopes
staff.view
GET/api/v1/admin/sites/statusBearer token
Website status snapshot
Returns the org-scoped venue website summary for the Business app: site identity (incl. brand/location coverage), draft/published versions, preview URL, publish-readiness blockers/warnings, connected custom-domain verification/SSL state, and the additive per-site `entitlement` block (kind paid|trial|expired|legacy_preview|none, can_write_draft, can_publish, trial_expires_at, trial_remaining_ms — S12b). Permission: sites.view.
Parameters, scopes and examples
Required scopes
sites.view
POST/api/v1/admin/sites/publishBearer token
Publish the venue website
Publishes one tenant-bound website through the canonical publish core. Requires sites.publish plus a stable Idempotency-Key. Returns readiness blockers when the draft is not yet publishable; older mobile builds may ignore additive warnings.
Parameters, scopes and examples
Required scopes
sites.publish
Tenant-scoped website publish request
Request body
{
"site_id": "00000000-0000-4000-8000-000000000001",
"note": "Published after mobile review"
}
POST/api/v1/admin/sites/ai/chatBearer token
Talk to the website builder AI
Business-app SSE transport for the org-scoped website builder assistant. Requires sites.manage. Returns the mobile AI event vocabulary over Server-Sent Events and adds `site_patch` events so clients can refresh status mid-turn.
Parameters, scopes and examples
Required scopes
sites.manage
Tenant-bound site-builder message
Request body
{
"site_id": "00000000-0000-4000-8000-000000000001",
"conversation_id": null,
"message": "Make the homepage warmer and highlight workshops.",
"attachment_ids": [
"00000000-0000-4000-8000-000000000002"
]
}
POST/api/v1/admin/sites/attachmentsBearer token
Upload a website-builder attachment
Multipart upload for the Business website builder chat. Requires sites.manage. The file is stored through the shared AI attachment pipeline and later referenced by `attachment_ids` on the site chat route.
Parameters, scopes and examples
Required scopes
sites.manage
DELETE/api/v1/admin/sites/attachmentsBearer token
Delete a pending website-builder attachment
Deletes one tenant-owned, not-yet-bound website-builder attachment before it is sent in chat. Requires sites.manage.
Returns the frozen current allowance, published recurring choices, pricing version, and pending renewal change for an organization-owned issued pass. Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID
GET/api/v1/admin/gift-cards/{id}/sendBearer token
Gift-card delivery availability
Read-only per-channel availability and current deliveries (delivery_id, status, retryable) for a paid gift card, including module-off historical rights. Provider accepted is distinct from delivered; needs_review cannot be blindly retried. Push is unavailable until a gift has a durable recipient-account/device binding; the purchaser is never substituted. Requires gift_cards.sell, members.contact, and notifications.send.
Preview or schedule a client Flexible membership change
Previews or schedules a quantity/unlimited change for renewal 1–24 against an unexpired quote. Organization ownership is enforced and the new entitlement applies only after the target renewal invoice is paid. Permission: passes.manage.
Creates, replaces, or removes the one automatic clips-empty offer for a limited recurring pass or still-valid class pack. The target may be hidden from public catalogs. Permission: passes.manage.
Read-only desk catalog of published, active, in-window flash sales with at least one available promo. Returns organization_id plus id, name, applicable_pass_type_ids, ends_at, rule_label. Promo codes are never returned. Permission: pos.access with venue-wide location access. Does not require marketing.flash_sales. Final eligibility stays on pass-preview/sale.
List recently ended flash sales for a staff exception
STAFF-PROMO-WINDOW-EXCEPTION-01. Same { organization_id, sales[] } shape as GET /admin/pos/flash-sales (id, name, applicable_pass_type_ids, ends_at, rule_label; never a promo code), listing published, still-active sales that ended by time in the last 90 days whose code is active and below its total caps. Deactivated sales are never listed. Permission: pos.access with venue-wide location access AND marketing.flash_sales in the same venue (403 otherwise). An ended sale is sold only with promo_window_exception { kind: flash_sale_after_end, reason } on POST /admin/pos/sale or /admin/memberships; final eligibility and caps stay on those calls.
Redeem one expired partner/batch code for a client (staff exception)
STAFF-PROMO-WINDOW-EXCEPTION-01. Staff JWT with pos.access OR pos.sell, venue-wide location access AND marketing.campaigns in the same venue. Body: { member_id, code, reason } (reason 5-500 characters, kept in the audit log). Waives ONLY the code’s own expiry and its campaign’s redeem-by deadline (expired in the last 90 days); a voided, redeemed, reserved, staff-expired or inactive code, a cancelled/paused campaign, per-client and total limits and customer eligibility still refuse (422). A pass-granting code is redeemed through the canonical atomic grant claim and returns { outcome: granted, pass_id, code_label }; replays for the same code and client converge. A code that unlocks a flash sale returns { outcome: sale_required, code_label, pass_type_ids } and is sold with promo_window_exception { kind: late_code_redemption, reason } on POST /admin/pos/sale or /admin/memberships. Expired discount campaign codes (422 LATE_DISCOUNT_CODE_UNSUPPORTED) and plain venue promo codes without a campaign (422 LATE_CODE_NOT_CAMPAIGN) are refused. Records a 30-minute database exception and an audit_log row; customer self-service stays refused.
Parameters, scopes and examples
Required scopes
pos.accessmarketing.campaigns
The client, the code as typed, and the staff reason
Request body
{
"member_id": "uuid",
"code": "DOWNTOWN-ABC123",
"reason": "Client was away when the code expired"
}
Staff JWT only. Requires pos.access, products.manage_inventory and venue-wide location access. Returns transaction_id, receipt line_names, and evidence-backed items with product_id, current catalog product_name, sold_quantity, returned_quantity and returnable_quantity. Bundle constituents come from original sold movements, never current bundle definitions. Foreign/missing sales return the same 404. No monetary refund or sale-status dependency.
Staff JWT, pos.access plus products.manage_inventory, venue-wide location access. Required Idempotency-Key (8-160 ASCII letters/digits/colon/underscore/hyphen). Body {items:[{product_id,quantity}],reason}; unique products, positive integer quantities, 1-500 character reason. Atomically persists physical return, stock batch and audit. The full reason is private to the canonical return; stock movements use a fixed operational label and audit records only reason_present plus operation identifiers. Replays bind organization, actor, transaction, normalized items and reason. Returns return_id, transaction_id, stock_batch_id, items, reason, created_at, replayed; clients must match that receipt to the submitted transaction/items/reason before clearing the attempt. POS_RETURN_STALE (409) requires refreshing quantities; POS_RETURN_KEY_CONFLICT (409) rejects changed intent; POS_RETURN_UNAVAILABLE (503) retries the identical key/body. Inventory only: no refund, credit, receipt total, commission, entitlement or notification changes.
Parameters, scopes and examples
Required scopes
pos.accessproducts.manage_inventory
GET/api/v1/admin/pos/favoritesBearer token
List ranked quick-sale favorites
Returns ranked quick-sale tiles (admin pins first, then rolling-90-day volume, cap 8), the saved pin list, and the active pass/product catalog used to rank them. Uses getQuickSaleFavoritesForOrg. Permission: pos.access or pos.sell with venue-wide staff location access. Does not require settings.business.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/favoritesBearer token
Save admin/manager quick-sale pins
Replaces organizations.settings.pos.favorites through savePosFavoritePinsForOrg and updateOrgSettingsWithCas. A failed or missing settings read does not write. Unrelated settings and other pos keys are preserved. Body is { pinned: [{ kind: pass_type|product, id }] } (max 24). Returns the same ranked payload as GET. Permission: pos.manage (admin/manager). Does not rewrite settings.business.
Parameters, scopes and examples
Required scopes
pos.manage
GET/api/v1/admin/pos/pass-typesBearer token
List the staff POS pass catalog
Returns every active venue pass type for authenticated POS staff, including pass types intentionally hidden from public consumer catalogs, plus pricing_mode, published flexible_pricing_config, binding_tiers, vat_config/effective_vat, is_recurring, shop_visibility, category and price_amount. Permission: pos.access with venue-wide staff location access.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/servicesBearer token
List the staff POS service catalog
Returns active appointment services, their variants and active linked providers for authenticated POS staff. Class-only venues receive an empty list. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/perksBearer token
List verified course perks for a POS client
Returns the selected venue member’s currently available product perk entitlements. Tenant membership is revalidated and entitlement reads fail closed. Claims are supported only on product/bundle-only sales; pass, service and mixed service carts are refused before collection, regardless of tender. Direct product payments commit perks atomically with sale effects. Permission: pos.access or pos.sell.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/pass-previewBearer token
Preview an authoritative standard pass total
Runs the canonical pass sale pricing, fee, discount and VAT engine without collection or writes. Optional additive flash_sale_id is resolved by the existing sale core; do not send promo_code with it. Backdated windows additionally require passes.manage. Permission: pos.access or pos.sell.
Recover the original selected-currency product sale
Financial recovery by { operation_key }. Requires pos.access or pos.sell and venue-wide location access; the original immutable sold_by_staff_id must match the authenticated actor. Returns unresolved for missing, ambiguous, malformed, canceled, quarantined or inconsistent evidence; absence never permits a replacement collection. Pending replies include the frozen original currency, amount_minor, total, subtotal, vat_amount, member_id, items and product_price_book_versions. Completed replies additionally include validated transaction_id, payment_id and payment_status after matching both scoped ledger records to the original operation. For a bound or succeeded operation, retrieves the exact frozen Stripe intent on its original account. Only verified provider success can atomically create or adopt the original payment and POS transaction and complete the frozen items and stock through the canonical completion RPC. Never creates or confirms a provider intent or sends a receipt. Quarantined operations require manual reconciliation. Only completed recovery may release a browser marker. POST keeps original keys out of URLs; responses are no-store.
Discover qualified currencies for an ordinary product basket
Read-only discovery by { member_id, items } for ordinary product quantities, without an initial currency or payment key. Requires pos.access or pos.sell, venue-wide location access and a scoped member. Returns { available_currencies: string[] }, the exact-price intersection of active product books for verified home-country catalog tax, qualified precision, owned nonrecurring products, quantity limits and available SQL recovery. Unsupported variants, benefits and commissions remain refused. Empty choices never authorize venue-currency fallback. Responses are no-store. Each chosen currency still requires its own signed catalog-preview and exact original sale/finalize body. Discovery creates no payment operation.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/catalog-previewBearer token
Preview an authoritative POS basket total
Quotes any multi-line register basket that POST /admin/pos/sale completes as one sale: products and bundles (product engine) or services with products (service engine). Runs canonical catalog repricing, course-perk resolution (product/bundle baskets), discount composition and per-line VAT without collection or writes, and returns { subtotal, vatAmount, total, discountAmount, currency }. Optional product_currency selects exact gross product books for an authenticated member with home-country evidence and ordinary product-only lines; the response additionally returns product_price_book_versions and product_quote, a signed proof of actor, member, residence evidence, exact items and inclusive tax provenance. Send the exact product_quote, those versions, that currency and total as expected_total to the sale endpoint; preserve them across retries and SCA finalize. Commission/benefit/foreign-country refusals are 422 PRODUCT_PRICE_BOOK_UNAVAILABLE. Pass lines return 422 PRODUCTS_REQUIRED (use pass-preview); bundles with services return 422 MIXED_CART_NOT_SUPPORTED; products.max_quantity_per_order returns 422 PRODUCT_QUANTITY_LIMIT with details; a register-access denial returns 403. Permission: pos.access or pos.sell.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/bundlesBearer token
List the staff POS product-bundle catalog
Returns active product bundles for authenticated POS staff. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/gift-cardsBearer token
Sell a gift card through the register
Creates a POS gift-card sale through posSellGiftCard (payment + pos_transactions). Silent by default. Permission: pos.sell. Idempotency-Key required.
Returns the same short-lived server quote used by desk Flexible sales. Selection uses passSelectionSchema ({kind:quantity|unlimited} or {kind:option, optionId}). Permission: pos.access or pos.sell.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/eodBearer token
Load end-of-day reconciliation inputs
Returns original tender legs, recorded refund allocations, account-scoped Stripe settlements and the close record for a venue-local ledger date. Unverified methods cannot establish a balanced day. Refunds use their recorded creation date; this is not a fiscal Z report. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/eod/closeBearer token
Close the register for a business date
Recalculates and stores the current end-of-day reconciliation per organization and business date. A later close replaces that date’s stored record; historical close revisions are not yet retained. Unverified lines remain unbalanced. Permission: pos.sell.
Parameters, scopes and examples
Required scopes
pos.sell
GET/api/v1/admin/pos/kioskBearer token
Read kiosk register configuration
Returns kiosk enablement, receipt mode and idle timeout without staff PIN secrets. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/kiosk/pinBearer token
Validate a kiosk staff PIN
Checks a staff PIN against the venue kiosk configuration. Permission: pos.access.
Get the Move-to-Flexible options for a client membership
FLEX-RETIRE-01 — ADMIN-ONLY. Returns the source fixed-price membership, its next renewal date (the only possible effective date), the sellable Flexible pass types with their published allowance ranges, and any pending switch. Never offered to a member. Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID (the fixed-price membership being retired)
Preview or schedule a Move to Flexible at next renewal
action=preview returns the ordinary desk-mint review (pricing, timeline, saved card, review fingerprint, Flexible quote) anchored on the old renewal date with the registration fee waived. action=schedule echoes the quote + review fingerprint + a caller-owned attempt id: it mints the Flexible membership pending_activation on that date, schedules the old subscription to end at its current period end, and links the two on the membership operation. Idempotent per attempt; a second pending switch is refused 409 SWITCH_PENDING. Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID (the fixed-price membership being retired)
Reversible until the effective date: undoes the old subscription’s scheduled cancellation first, then voids the pending Flexible pass (nothing was charged). Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID (the fixed-price membership being retired)
Business-app stream-control DTO for one tenant-bound class instance. Returns source options without provider live-stream ids, stream keys, RTMP URLs, SRT URLs, or playback URLs; action availability includes exact disabled reason codes. Visible to venue schedule/check-in readers and assigned staff roster readers. Source/go-live writes repeat module/settings gates; end remains available for safe live shutdown.
Sets or clears the occurrence-level stream source before the class goes live. Requires class.create, active streaming module/entitlement, a streamable class, an active RTMP/SRT source with an attached provider stream, tenant binding, lifecycle CAS status=scheduled, UUID Idempotency-Key, and no assigned-instructor owner-toggle block. The operation is silent; notification fields are rejected.
Starts a class stream from an existing class provider stream or an active selected RTMP/SRT source. Requires class.create, active streaming entitlement/module, streaming settings enabled, streamable class, venue-local class date, tenant binding, lifecycle CAS status=scheduled, provider-adapter enablement, UUID Idempotency-Key, and no assigned-instructor owner-toggle block. Provider enablement is compensated when the DB transition fails or loses a race unless the winner uses the same stream. The route does not mint new one-time provider streams or expose source credentials; it is operationally silent and rejects notification fields.
Ends a live class stream. Requires class.create, tenant binding, lifecycle CAS status=live, and UUID Idempotency-Key. The class completion commits before the shared-stream provider disable guard runs; the response reports provider_stop as disabled, skipped_shared, failed, or not_applicable. The route records streaming usage after the class completion commit. It is operationally silent and rejects notification fields.
Retries only the provider disable step after a class has already completed; it never re-completes the class or repeats lifecycle effects. Requires class.create, tenant binding, UUID Idempotency-Key, and an attached provider stream. The shared-stream guard is organization-scoped and no blocking occurrence identifier is returned. This route is operationally silent and rejects notification fields.
PHONE-PUBLISH-01: mints an ephemeral, class-scoped provider live stream plus a hashed single-use claim token, bound to one organization, one class occurrence, and the preparing user, with a short server-enforced TTL. Requires class.create OR the assigned instructor when the venue enables instructor go-live, active streaming module/entitlement, streamable class, venue-local class date, the phone_publisher_sessions kill switch, tenant binding, and a UUID Idempotency-Key. Returns non-secret session state and the one-time claim token; NEVER ingest URLs, stream keys, or provider resource ids. One active session per class; supersession refuses while another device is actively publishing with a fresh heartbeat. On a live class paused by the revoke of the previous phone session (or a session prepared to resume it), prepare is allowed and REUSES that provider stream instead of minting one; a live class that is not paused, paused by the operator, or paused on a venue encoder stream answers 409 ALREADY_LIVE.
PHONE-PUBLISH-01: atomically consumes the single-use claim token and returns short-lived RTMPS ingest material exactly once (Cache-Control: private, no-store). Only the session creator may claim. Deliberately NOT idempotency-cached — a duplicate claim returns CLAIM_ALREADY_USED and the recovery path is revoke + prepare a new session; the old secret is never re-displayed. No ingest material is ever stored server-side.
PHONE-PUBLISH-01: transitions the claimed session to publishing and returns the authoritative session/provider/class status snapshot. Never marks the class live — only the provider active webhook does. Owner-bound, tenant-bound, UUID Idempotency-Key required.
PHONE-PUBLISH-01: periodic liveness touch returning session state, provider connection status (ingest fields stripped), and class lifecycle status. When the provider confirms an active input and the class is still scheduled inside the phone-publisher window, the snapshot reconciles the class to live (CAS; the cron sweep is the backstop). Owner-bound and naturally idempotent, so no Idempotency-Key is required. A stale heartbeat makes a publishing session eligible for takeover by another authorized device.
PHONE-PUBLISH-01: ends the active phone publisher session, completes the class when this session took it live (usage recorded after the completion commit), and records a DURABLE provider-cleanup outcome — a failed teardown is surfaced in GET state and retried, never hidden. Requires class.create or the permitted assigned instructor, tenant binding, and a UUID Idempotency-Key.
PHONE-PUBLISH-01: revokes a session whose device was lost or reinstalled or should no longer publish; the old secret is never re-displayed and a fresh session must be prepared and claimed. When the stream of the revoked session is the one that took the class live, the class is PAUSED (`stream_paused_at`, outcome `class_paused: true`) rather than completed, so a following prepare resumes on the same provider stream (viewers keep their playback id). A self-revoke keeps that stream enabled (`provider_stop.status: skipped_paused`); a take-over/admin revoke disables it (kicks the publisher) and the resume re-enables it. Only `…/end` completes the class. Requires class.create or the permitted assigned instructor, tenant binding, and a UUID Idempotency-Key.
PHONE-PUBLISH-01: explicitly retries a failed or pending provider teardown for a terminal publisher session. Requires class.create or the permitted assigned instructor, tenant binding, and a UUID Idempotency-Key.
Parameters, scopes and examples
Required scopes
class.create
Path parameters
classInstanceIdstring · required
Class instance UUID
PATCH/api/v1/admin/schedule/{id}Bearer or API key
Edit class instance
Update start/end time, instructor, class type, capacity, or room on a single class instance. Physical and online capacity use a class-row lock and current counts; optional expected_updated_at rejects a stale edit. Concurrent count/version conflicts return 409; preliminary capacity validation may return 422 CAPACITY_BELOW_BOOKED. Class-type changes recheck retained teachers against the new type and brand. Rooms must belong to the occurrence venue/location and fit the resulting capacity. Notifications default silent: omitted controls and legacy notify_attendees never send. notify.{clients,instructor}.channels (or the older notify.{audience,channels}) sends the branded schedule-change email/SMS/push on time/instructor/room changes: clients get class_schedule_changed, instructors class_schedule_changed_instructor. A chosen plan requires notifications.send; a channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason} before the edit; mixing shapes returns 422 MIXED_NOTIFY_SHAPES. Success adds notify_outcome (per audience × chosen channel {sent, skipped, failed, pending, reasons}) or null. Idempotency-Key honored; audit_log carries per-field from/to diffs.
Correct a past class instance with an audit record
Atomic, immutable-ledger correction for a past class. Requires a UUID Idempotency-Key, scheduling.manage_history plus scheduling.manage, expected_updated_at and expected_status for an existing row, a past effective_at, and typed REWRITE attestation. Assignment-only corrections preserve linked roster, financial, course, workshop, import and streaming records; all other corrections on linked classes require specialist review. Existing tenant, location and instructor erasure guards remain enforced. Cancellation-state corrections additionally require class.cancel. Notifications are always silent.
Parameters, scopes and examples
Required scopes
scheduling.manage_history
Path parameters
idstring · required
Class instance ID
Bounded historical class-instance correction
Request body
{
"operation": "class_instance.correct_timing",
"expected_updated_at": "2026-08-20T09:00:00.000Z",
"expected_status": "completed",
"history_reason": "Signed instructor log confirms the recorded class time",
"history_confirmation_token": "REWRITE",
"effective_at": "2026-08-20T10:00:00.000Z",
"intent": {
"startTime": "2026-08-20T08:00:00.000Z",
"endTime": "2026-08-20T09:00:00.000Z"
}
}
Atomic, immutable-ledger correction for a past recurring availability window. Requires a UUID Idempotency-Key, availability.manage_history, staff_portal.availability, staff.edit for another staff member, expected_updated_at plus expected_is_active for existing rows, a past effective_at, and typed REWRITE attestation. The staff path and tenant-owned row pin ownership. Notifications are always silent.
Applies explicit venue-local start/end time, room, instructor, capacity, or class type to 1–50 tenant-owned classes with per-row conflict/failure results. Notifications default silent; notify.{clients,instructor}.channels (or the older notify.{audience,channels}) requires notifications.send, is checked against the selection before any write (422 NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason}) and sends only after each successful write. Success adds notify_outcome merged across classes, or null. Idempotency-Key required.
Cancels 1–50 tenant-owned classes in one transaction: statuses, consumed credit returns, counters, audit and durable effects commit together. A refusal returns 409 CANCELLATION_CONFLICT with no partial business mutation. Success retains succeeded/succeeded_count/failed and adds committed, operation_id and effects_status (completed, pending or held). Optional expected_versions maps IDs to row versions. Every target requires current accessible-location scope. Parsed intent and active organization scope the key; changed intent or venue returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. A positive cache entry resumes durable effects. An unconfirmed commit returns 503 CANCELLATION_COMMIT_UNCONFIRMED with operation_id, committed=unknown and retry_same_operation=true in error.details; preserve the reviewed body and Idempotency-Key. Notifications default silent: omission and legacy notify_attendees never send. Explicit notify audience plus channel requires notifications.send; clients and instructors support Email/SMS/Push. notify.{clients,instructor}.channels gives each audience its own channels (an instructor-only notice needs no client channel); the legacy notify.{audience,channels} shape gives the instructor the client channels minus Push, and its Push needs the client audience (422). Mixing both shapes returns 422. A chosen channel that reaches nobody in the selection returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE with {audience, channel, reason_code, gate_code, unavailable_reason} before any class changes. Success adds notify_outcome: per audience × chosen channel {sent, skipped, failed, pending, reasons[{code, count, label}]}. Idempotency-Key required.
POST/api/v1/admin/schedule/{id}/cancelBearer or API key
Cancel class
Atomically cancel a class and its bookings, return only actually consumed eligible credits, reset counters, and record audit plus durable delivery obligations. Optional expected_updated_at protects the reviewed version. A committed result adds operation_id and effects_status; pending/held delivery is not a failed cancellation. Retrying the same Idempotency-Key replays the operation. Notifications default silent: omission and legacy notify_attendees never send. Explicit notify.{audience,channels} or notify.{clients,instructor}.channels requires notifications.send; channel choices AND with recipient preferences. Clients and instructors support Email/SMS/Push. The per-audience shape gives the instructor its own channels (instructor-only is valid); the legacy shape gives the instructor the client channels minus Push, and its Push needs the client audience (422). Mixing both shapes returns 422. A chosen channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE with {audience, channel, reason_code, gate_code, unavailable_reason} before the class changes; success adds notify_outcome (per audience × chosen channel {sent, skipped, failed, pending, reasons}). Past classes return 409 HISTORICAL_CORRECTION_REQUIRED and must use the dedicated /api/v1/admin/schedule/{id}/historical-corrections endpoint with reason, REWRITE attestation, history permission and compare-and-set evidence. Idempotency-Key required. Unconfirmed commit returns 503 CANCELLATION_COMMIT_UNCONFIRMED with operation_id, committed=unknown and retry_same_operation=true in error.details. Keep the same reviewed body and key; never replace them after an uncertain outcome. Current notification and location authority are checked before replay. Keys are bound to parsed intent and active organization; mismatches return 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Durable replay resumes effects.
Requires scheduling.manage and Idempotency-Key. Accepts expected_updated_at, restore_booking_ids, confirm_conflicts and optional reason. auto_rebook_clients defaults false; when explicitly true it selects eligible cancellation-snapshot IDs for review. Selected rebooking also requires bookings.manage. Old cancelled rows remain immutable; canonical eligibility creates new linked bookings with confirmed, waitlisted, ineligible or pending outcomes. One rejected client does not roll back activation. Notifications start silent. notify.{clients,instructor}.channels (or the older notify.{clients,instructor} booleans with notify.channels) requires notifications.send and is checked before mutation: a channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason}. The instructor (class_restored_instructor) is told with the restore; clients (class_uncancelled) are told when their rebooking resolves — rebooked, waitlist place restored, or "book again" — through the durable staff notification outbox. Success includes committed, operationId, bookings, effectsStatus and notify_outcome (per audience × chosen channel {sent, skipped, failed, pending, reasons}); stale or historical rows return 409. Lost atomic acknowledgements return 503 REACTIVATION_COMMIT_UNCONFIRMED with operation_id, committed=unknown and retry_same_operation=true in error.details. Explicit selected booking IDs replay with the default auto_rebook_clients=false. Keep the same reviewed body and Idempotency-Key until the receipt resolves. Current location, selected-client booking and notification authority are checked before replay; a replay with a different notification plan is refused. Parsed intent and active organization scope the key; mismatches return 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Positive cached success resumes the durable restoration operation.
Parameters, scopes and examples
Required scopes
write:schedule
Path parameters
idstring · required
Class instance ID
Reviewed restoration with an optional per-audience notification plan
POST/api/v1/admin/schedule/{id}/substituteBearer or API key
Assign substitute
Replace instructor for a class. Validates no scheduling conflicts across locations. Notifications default silent. notify.{clients,instructor}.channels requires notifications.send: clients get class_schedule_changed and the instructor audience gets staff_assignment (the new substitute, and the teacher they replace) on every chosen channel (Email/SMS/Push). The older notify.{audience,channels} shape keeps its meaning (clients Push, substitute Email; other combinations 422). A channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE before the change. Success adds notify_outcome or null. Idempotency-Key is required and binds the caller, venue, class location and exact body. Replay rechecks current scheduling, location and selected notification authority. Concurrent requests are held while the scoped claim is retained; mismatched reuse returns 409. Unconfirmed outcomes return 503 SUBSTITUTE_COMMIT_UNCONFIRMED and require review without a new key. Definite scheduling conflicts remain 409 SCHEDULE_CONFLICT and permit a reviewed override. Expiring claims do not guarantee permanent deduplication.
Parameters, scopes and examples
Required scopes
write:schedule
Path parameters
idstring · required
Class instance ID
GET/api/v1/admin/checkinBearer or API key
Venue-local check-in day strip
The venue-local day's classes for the native staff check-in screen (Business app): per-class check-in/waitlist counts, room/instructor, and the day-navigation gates (today vs. read-only past/future). Defaults `date` to the venue-local today when omitted; optional `location_id` (query param or X-Location-ID header) narrows to one location. Each class carries additive `course_identifiers` (`{course_id, label, tone, course_name}`, hidden courses included; `[]` when none).
Parameters, scopes and examples
Required scopes
read:bookings
Query parameters
datestring
Venue-local date, YYYY-MM-DD. Defaults to the venue-local today.
location_idstring
Restrict results to one location. Also accepted as the X-Location-ID header.
GET/api/v1/admin/checkin/{classInstanceId}Bearer or API key
Attendee list
Class roster with member details, pass info, native course_access covering this booking, add-ons, included services, check-in status, and class/booking updated_at concurrency tokens. Whole-class cancellations retain the preserved roster and each attendee’s previous status; ordinary client cancellations remain excluded. Streaming bookers (attendance_type online) carry online_attendance { state: watching | attended | not_yet | null, playback_state, first_played_at, last_heartbeat_at } and are attended automatically when their stream plays; summary adds whole-class in_studio { total, confirmed, checked_in, no_show, waitlisted } and online { total, not_yet, watching, attended, no_show, waitlisted } counts. Every response returns a cursor (the database read time): pass it back as since= for a delta read that returns only attendees whose booking or stream session changed after since minus a 2-second overlap (merge by id; re-sends are idempotent), removed_ids for bookings that left the roster, delta: true and the whole-class summary. Poll the delta every few seconds while the screen is visible and do a full read on open, pull-to-refresh and periodically. Each row carries additive notification_availability.{booking_removal, waitlist_promote}.clients — per channel {available, reason_code, reason, recipient_id, reachable, total, blocked, reach_label}.
Parameters, scopes and examples
Required scopes
read:bookings
Path parameters
classInstanceIdstring · required
Class instance ID
Query parameters
include_historical_recordsstring
Include protected cancelled/late-cancelled roster rows; requires scheduling.manage_historyDefault: false
sincestring
The previous response cursor (ISO timestamp). Returns only changed attendees plus removed_ids; omit for the full roster.
Send a bulk email or SMS to a selected subset of one class instance. Submitted booking_ids are intersected server-side with the organization's active or whole-class-preserved roster; ordinary cancellations and stale/foreign ids are dropped and counted as skipped. Caller-supplied contact data is never accepted. Uses the canonical consent/suppression-aware bulk senders and requires the can_view_client_contact_info membership toggle. Idempotency-Key is honored.
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
classInstanceIdstring · required
Class instance UUID
Channel, roster booking ids, and message
Request body
{
"channel": "email",
"booking_ids": [
"00000000-0000-4000-8000-0000000000b1"
],
"subject": "Class update",
"message": "Hi {{first_name}} — here is an update about your class."
}
POST/api/v1/admin/checkin/{classInstanceId}/{bookingId}Bearer or API key
Check in member
Check a member into class through the canonical attendance core. Validates current tenant/location scope, class timing and late-arrival authority; after-cutoff requires booking.checkin.after_cutoff and explicit confirmation. Idempotency-Key is required for the reviewed attempt, and the committed receipt reports operation identity/effects. Notification is silent unless an explicit supported channel selection is authorized and preflighted. A streaming (online) booking is attended automatically when its stream plays: a staff check-in of one is refused with 409 ONLINE_ATTENDANCE_AUTO unless the body sets mark_attended_override: true (the audited "Mark attended" override, not subject to the late-arrival cutoff). The receipt adds attendance_type and online_attendance_override.
POST/api/v1/admin/checkin/{classInstanceId}/{bookingId}/noshowBearer or API key
Mark no-show
Mark member as no-show through the canonical attendance core. Validates current tenant/location scope and timing; no-show fee/clip effects are derived server-side and require the specific no-show grant. Idempotency-Key and the reviewed booking version protect retries. Notification is silent unless explicit channels pass notifications.send and recipient/event preflight.
Parameters, scopes and examples
Required scopes
write:checkin
Path parameters
classInstanceIdstring · required
Class instance ID
bookingIdstring · required
Booking ID
GET/api/v1/admin/membersBearer or API key
List members
Membership-driven client list with exact pre-pagination status and pass filtering. Native course grants count as covering entitlement: those clients are status=active (not no_pass) and each row may carry additive active_course_access { course_name } | null. Search by name, email, phone or venue client ID; optionally filter by tag, active pass type, or canonical pass family. pass_type_id and pass_family combine with AND semantics. Every successful response, including zero-match pages, includes meta.pass_type_options and meta.pass_family_options. Options expose distinct active-client counts across the full authenticated venue before pagination; pass types include current active templates plus archived templates still held by active clients, including types hidden from public pricing and course/workshop-managed types.
Parameters, scopes and examples
Required scopes
read:members
Query parameters
searchstring
Search by name, email, or phone
statusstring
Client status: active, inactive, new, or no_pass
tagstring
Filter by member tag
pass_type_idstring
Filter by active pass type
pass_familystring
Filter by pass family: recurring, class_pack, time_based, or intro_offer
Register an active client or send a pending client invitation. Enforces the venue plan limit, requires members.edit and an Idempotency-Key, and records a PII-safe audit event.
Full member profile: passes with course-fulfillment provenance and an additive, optional `provenance` field per pass (who sold it, how, when, plus the sale/activation/money facts of the one sale-provenance rule — gated on the caller's own payments.view, null otherwise, same rule as stripe_subscription_id/recent_payments), canonical native course_access, recent bookings/payments, tags, scores, credits, referrals, client_display_id, plus server-authoritative total_bookings and last_visit_at. The additive root-level date_format is the venue's display preference (DD/MM/YYYY, YYYY-MM-DD, DD.MM.YYYY or DD-MM-YYYY); profile.date_of_birth remains a canonical YYYY-MM-DD civil date for writes. Also returns per-channel notification_availability (email/SMS/push with exact unavailable reasons), contact_details_visibility {email,phone} (independent member.view_email / member.view_phone AND the membership contact toggle; contact_details_visible remains email AND phone for older app builds), and payments_visible. A 409 PROFILE_MERGED is returned when this profile was merged away in this venue, with primary_user_id of the survivor. Contact disclosure is fail-closed on PII_AUDIT_FAILED.
Soft-deactivates only the active membership at the selected venue; it never deletes the shared profile or changes memberships at other venues. Delivery is silent by default. An explicit notify object may select Email, SMS, and/or Push, which requires notifications.send and an availability preflight before the deactivation commits. A post-commit delivery failure is returned separately as notification_failure and never restores access. Protected Admin/Finance memberships retain their shared lifecycle authorization checks. Idempotency-Key required. Permission: members.delete.
Parameters, scopes and examples
Required scopes
members.delete
Path parameters
idstring · required
Member user ID
Deactivation reason and optional explicit client delivery channels
Tenant-scoped pass history with an exact total and opaque keyset cursor. Returns up to 100 records per page and never exposes processor subscription identifiers.
Deterministically merges live bookings and imported historical visits. Historical rows carry record_source=migration_history and read_only=true. The total is exact across both stores. Live rows with a recorded course consumption carry payment_status=course and course_coverage { course_name, state }; released course usage retains its returned state. Course coverage describes the recorded booking entitlement, not tuition payment. Every row also carries source, payment_status, class_instance.local_date and a permission-agnostic actions block (cancel, remove_waitlist, change_pass, correct_attendance, check_in, mark_no_show — all false on an imported row). meta.venue_today is the venue-local date the today gates were measured against; meta.stats (first page only, absent when after is sent) holds exact counts over the entire filtered set. Live rows carry additive notification_availability.{booking_removal, waitlist_promote}.clients in the staff availability contract.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
Query parameters
limitinteger
Items per page (1–100)Default: 25
afterstring
Opaque next_cursor from the previous page
pass_idstring
Only bookings funded by this pass
cycle_startstring
Venue-local YYYY-MM-DD start of a pass usage cycle (inclusive)
cycle_endstring
Venue-local YYYY-MM-DD end of a pass usage cycle (exclusive)
scopestring
upcoming = class start after now and the seat still held, ordered soonest first, no imported history; past = the exact complement, newest first. Omitted returns the merged view.
fromstring
Venue-local YYYY-MM-DD, inclusive, on the class start (imported rows compare on visit_date)
tostring
Venue-local YYYY-MM-DD, inclusive; converted to the next local midnight so evening classes stay in range
statusstring
Exact match on live rows; imported rows match on their normalised status, so confirmed, waitlisted and pending_payment never match one
class_type_idstring
Only classes of this class type (excludes imported history)
instructor_idstring
Only classes whose PRIMARY instructor is this person — a substitute does not match (excludes imported history)
location_idstring
Only classes at this location (excludes imported history)
brand_idstring
Only classes whose class type belongs to this brand (excludes imported history)
Tenant-scoped payments with exact total, refunds, safe card display, invoice linkage, and explicit receipt capabilities. Each refund has additive receipt_available=true only for a succeeded operation claim on a venue payment; pending, legacy non-claim and Apple merchant-of-record rows cannot offer the venue refund PDF. Additive receipt.bulk_email {available,reason_code,reason} describes eligibility for the existing bulk email route, including non-POS payments, independently of POS-only single-send capabilities. Bulk execution still requires members.contact and notifications.send and rechecks delivery policy. Older clients may ignore this field; newer clients fail closed on a malformed present field and retain the conservative single-email fallback when it is absent. Processor IDs, client secrets, and raw receipt URLs are never returned.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
Query parameters
limitinteger
Items per page (1–100)Default: 25
afterstring
Opaque next_cursor from the previous page
statusstring
Only payments with this status. Anything outside the list is a 400. Omitted = every status.
fromstring
Venue-local YYYY-MM-DD, inclusive. Applied to the page AND the total, so meta.total is the filtered total.
tostring
Venue-local YYYY-MM-DD, inclusive. from after to, or a day no calendar has, is a 400.
Re-sends through the canonical POS sender to the member contact stored on the server. Only same-venue POS-backed receipt payments are eligible; arbitrary recipients and payment retry are not supported. Idempotency-Key is required and atomically bound to the actor, venue, member, payment and selected channel. Retry the same request after response loss. A concurrent request returns IDEMPOTENCY_IN_PROGRESS; changed intent returns IDEMPOTENCY_KEY_REUSE_MISMATCH. RECEIPT_DELIVERY_UNCONFIRMED retains the uncertain outcome without another provider send. sent:true confirms provider acceptance, not delivery to the client.
Parameters, scopes and examples
Required scopes
notifications.send
Path parameters
idstring · required
Active member ID
paymentIdstring · required
Same-venue payment ID
Receipt delivery channel advertised by the payment receipt capability
Request body
{
"method": "email"
}
GET/api/v1/admin/paymentsBearer token
Org-wide recent sales
Business-app contract C6: the venue's recent payments (all statuses), newest first, mirroring the web sales drawer rows — plain-language method label, card label, money bucket (captured | recorded | internal), and the drawer's refund-offer rule (`refundable` = settled non-guest rows with money remaining). Amounts are integer minor units (øre). Each item also carries `sale_provenance` {sale, activation, money}: who made the sale and when (staff at the desk, the client online, …), when its pass starts, and how THIS payment was collected (at the desk, online, charged automatically when a scheduled pass started, a renewal, …) — `sold_by`/`channel` read the sale, never the payment. Cursor-paginated (opaque keyset cursor), limit ≤ 50. Range resolves in the venue's timezone.
Business-app contract C7: executes a claimed review through canonical processRefund. Body { review_intent_id, confirmation?, amount?, reason?, client_receipt_comment?, destination_id?, method_reference?, notify_client?, receipt_channels?: ('email'|'sms'|'push')[] }. Destination and notes must match the immutable review. Omitted or empty channels and a legacy boolean alone remain silent. Explicit channels require notifications.send before new refund admission; an unavailable channel returns 422 REFUND_CHANNEL_UNAVAILABLE with channel and reason_code. Recover an admitted operation with its original review and Idempotency-Key. A new operation is refused with 409 REFUND_RECOVERY_CASE_OPEN while the payment has an open critical refund recovery case (the same rule as the review). Success includes refund_id, refund_status, a printable bearer-authenticated PDF URL, and per-channel outcomes; pending approval or provider processing is not a settled refund or proof of notification delivery.
Parameters, scopes and examples
Required scopes
billing.refunds.same_daybilling.refunds.full
Path parameters
idstring · required
Payment UUID
Refund details (amount in MAJOR units)
Request body
{
"review_intent_id": "00000000-0000-4000-8000-000000000000",
"amount": 199,
"reason": "Client requested the refund",
"client_receipt_comment": "We hope to see you again soon.",
"destination_id": "original",
"notify_client": true,
"receipt_channels": [
"email",
"push"
]
}
GET/api/v1/admin/refunds/{id}/receiptBearer token
Download canonical refund receipt PDF
Bearer-authenticated, tenant-scoped, no-store PDF used by native print/share. Contains only the optional client receipt comment; the staff-only internal reason is never rendered.
Business-app contract C8: send a bulk email or SMS to participants of one class instance. Recipients are resolved server-side — the submitted booking_ids are intersected with the class's ACTIVE roster (confirmed/waitlisted/checked_in); stale ids are dropped and counted as skipped, and caller-supplied contact info is never accepted. Delegates to the same senders/consent semantics as the web check-in bulk bar (templates admin_bulk_email / admin_bulk_sms). Requires the TV-D can_view_client_contact_info membership toggle. Returns { sent, skipped }.
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
classInstanceIdstring · required
Class instance UUID
Channel, roster booking ids, and the message
Request body
{
"channel": "email",
"booking_ids": [
"00000000-0000-4000-8000-0000000000b1"
],
"subject": "Tonight’s class moves to Room 2",
"message": "Hi {{first_name}} — we moved tonight’s class to Room 2. See you there!"
}
Business-app parity: the eligible one-class products for paid guest spots. Uses the same guest visitor permission and catalog core as the web check-in screen.
Requires booking.checkin, current can_add_client_to_class and assigned-location access. Idempotency-Key (maximum 200 characters) and immutable full guest intent are required. Every retry reads the canonical receipt after current authorization, including after midnight; cached HTTP success never overrides current booking status. Silent: no payment or client notification.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance UUID
Freeze these guest fields together with Idempotency-Key until the operation is completed or definitely refused.
Business-app parity: add 1–20 guest spots as payment-link, paid-at-desk, or comp/free bookings. Payment-link delivery supports email, SMS, or both. Uses the same context-free core as web; Idempotency-Key required.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance UUID
Guest contact, count, payment mode, product, and delivery channels
Business-app contract C1: the venue's failed payments (payments.status='failed') in the trailing window (default 30 days), newest first, cap 100. Each row carries client + linked pass context, a plain-language method label, and `retryable` per the same pure decider the web Retry button uses. Amounts are integer minor units (øre).
Parameters, scopes and examples
Required scopes
members.view_insights
Query parameters
daysinteger
Trailing window in days (1–365)Default: 30
member_idstring
Client-account parity P3 — only this client’s failed payments (public display id or UUID). `count` is then that client’s count. An id that is not an active client of this venue answers 404.
Business-app contract C2: re-collect a failed payment through the canonical retry core (PaymentIntent confirm or off-session invoice pay on the SC2-resolved Connect account, driving handleInvoicePaid). The body may be empty for the provider default, or contain payment_method_id selected from the exact failed invoice/PaymentIntent customer wallet. A provider-proven legacy card_ default is accepted and retried as the customer default without an unsupported override. Idempotency-Key is required, atomically claimed before the provider charge, and bound to the venue, payment, and selected payment_method_id; simultaneous reuse cannot double-charge and reuse with another card returns 409. Returns status succeeded | requires_action | failed with a plain-language message.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
paymentIdstring · required
Failed payment UUID
Optional exact saved card override; omit the body to use the provider default.
Record an external settlement for a failed renewal
Business-app contract C3: the failed recurring-renewal invoice was paid through another channel (cash, bank transfer, MobilePay, external card terminal, other). Settles the Stripe invoice out-of-band so the canonical recovery reactivates the pass, attributing the recovered payments row to the real method. amount is integer minor units (øre). Idempotency-Key required.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
paymentIdstring · required
Failed payment UUID
Settlement details
Request body
{
"method": "bank_transfer",
"amount": 79900,
"paid_at": "2026-07-31",
"note": "Paid via bank transfer, ref 1234",
"notify_client": true
}
Business-app contract C4: comp the failed recurring-renewal cycle — the client keeps the period, 0 revenue is recorded (the recovered payments row is forced to 'comped' amount 0). Reason required. Idempotency-Key required.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
paymentIdstring · required
Failed payment UUID
Waive details
Request body
{
"reason": "Goodwill — studio closure week",
"notify_client": true
}
Business-app contract C5: venue-imposed suspension (distinct from the member freeze) — blocks bookings until unsuspended. Client delivery is silent by default and accepts only notify.audience.clients=true plus an explicit Email/SMS/Push selection; legacy notify_client/notify_channels inputs remain silent. A non-empty selection requires notifications.send, and an unavailable selected channel returns PASS_NOTIFICATION_CHANNEL_UNAVAILABLE before the pass changes. Idempotency-Key (UUID) required; replay returns the stored response and the stable delivery reference prevents re-sending. Delivery failure after the pass write is reported as notification.sent=false and never rolls the suspension back.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Optional reason and explicit, default-silent client notification choice
Business-app contract C5: lift a venue-imposed suspension. Body is optional and silent by default. Client delivery requires notify.audience.clients=true, an explicit Email/SMS/Push selection, and notifications.send; legacy booleans/arrays remain silent. An unavailable selected channel returns PASS_NOTIFICATION_CHANNEL_UNAVAILABLE before the pass changes. Idempotency-Key (UUID) required; replay never re-sends. A post-write delivery failure returns notification.sent=false without rolling the pass change back. 422 NOT_SUSPENDED for a pass that is not suspended.
Business-app contract C5 (PASS-REACTIVATE-01): flip an expired/cancelled NON-recurring pass back to active; a run-out window requires new_end_date ≥ venue-local today. Client delivery is silent by default and requires notify.audience.clients=true, explicit Email/SMS/Push channels, and notifications.send; legacy notification inputs remain silent. Unavailable channels reject before mutation. Idempotency-Key (UUID) required and replay never re-sends; delivery failure after the write returns notification.sent=false without rollback. Recurring memberships are refused (422 RECURRING_UNSUPPORTED) — restart via a real re-mint. Audit pass_reactivated + reverse_payload.
Review or recover the original later-booking cancellation plan
Requires bookings.manage and booking.cancel_member. Idempotency-Key is the original pass-change key; request is its exact version:1 body including reviewed expected_old_pass_id and expected_updated_at. mode:read returns null only if no plan or parent receipt exists. mode:prepare freezes the server-selected children without cancelling anything. The strict plan retains each original child key, reviewed class time/location, quiet cancellation terms and confirmed partial results in server order. A terminal original parent is returned before new planning. 503 BOOKING_PASS_CONFLICT_UNRESOLVED retains the original; it never proves absence.
Parameters, scopes and examples
Required scopes
bookings.managebooking.cancel_member
Path parameters
bookingIdstring · required
Original reassigned booking
Read or prepare with the unchanged parent request and original Idempotency-Key.
Recover, apply or close one reviewed later-booking cancellation
Requires bookings.manage, booking.cancel_member and canonical location/attendance authority. Send mode:read|apply|close and the exact server-frozen child request, with its original Idempotency-Key. Apply follows the original server order and returns only a correlated cancelled receipt. Close returns that committed receipt or closed_without_cancel; it never reverses a cancellation. Null is permitted only on read and is not closure. Changed facts, ALREADY_CANCELLED and 503 BOOKING_PASS_CONFLICT_UNRESOLVED retain the original. Notifications stay empty; shared usage remains retained. Retry the original parent only after every child has its own cancelled receipt.
Parameters, scopes and examples
Required scopes
bookings.managebooking.cancel_member
Path parameters
planIdstring · required
Frozen plan ID
childIdstring · required
Frozen child ID
mode and the exact request returned for this child; preserve all timestamps, status, class location and quiet terms.
Find the shared usage attached to a reviewed booking
Read-only. Requires passes.manage, bookings.manage and booking location access. The exact original expected_pass_id and expected_updated_at must still match. Returns null for funding with no share, or booking_id, pass_id, share_id and booking_updated_at. This only opens the explicit usage review; it never allocates usage, cancels a booking or releases an unresolved pass-change request.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
bookingIdstring · required
Reviewed booking ID
The original reviewed funding and booking version.
Current attendance state for one booking plus what a correction would do: current_status, any attendance fee and its refund payment, the venue no-show fee, whether a clip was consumed, and per-channel notification_availability (email/SMS/push with the exact unavailable reason) so the client-notify picker can disable what cannot be delivered. The read also returns booking_updated_at, can_check_in_after_cutoff, ended_class_confirmation_required, credit_applicable and no_show_forfeits_clip. can_refund_fee is always false here — refunds live in Billing.
Flip a booking between checked in, no-show, booked and removed for today or a past day (a future class returns 422 FUTURE_ATTENDANCE, a cancelled class 422 CLASS_CANCELLED). Marking a no-show additionally requires bookings.mark_no_show and removing a visit requires booking.cancel_member. Client delivery is silent by default: only an explicit notify object with a non-empty channel set sends, which requires notifications.send and a per-channel availability preflight before the correction (422 ATTENDANCE_NOTIFICATION_CHANNEL_UNAVAILABLE, 502 ATTENDANCE_NOTIFICATION_PREFLIGHT_FAILED). The legacy notify_client boolean is accepted but never delivers. refund_fee returns 409 REFUND_REVIEW_REQUIRED — refund the charged fee from its protected payment detail first. Idempotency-Key required and is bound to the actor, venue and parsed body; expected_updated_at and ended_class_confirmed are forwarded to the canonical compare-and-swap core. A changed retry returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Success is committed when returned with operationId, bookingUpdatedAt, creditDelta, seatDelta and effectsStatus; pending/held delivery remains a committed correction.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance ID
bookingIdstring · required
Booking ID
Target attendance state, fee/clip choices, expected_updated_at, ended_class_confirmed when required, and explicit client delivery channels
The client's passes that could fund this booking, each with why it is or is not eligible (clips remaining, end date, who shared it, whether it is the current pass, and whether using it would shift the pass start date). Includes booking:{booking_id,pass_id,updated_at}; retain that reviewed snapshot with the chosen pass and original request key. Eligibility comes from the booking engine itself.
Atomically change the pass funding a booking. Idempotency-Key (1–200 characters, no control characters) and the exact original body are retained for every retry. Reviewed expected_old_pass_id (including null) and expected_updated_at are required for protected POS passes; both reviewed fields must be supplied together or both omitted; omission remains distinct from null. Success includes success:true, operation_id, event_id, booking_id, previous_pass_id, new_pass_id, pass_id (legacy alias), booking_updated_at, old_credit_returned, new_credit_consumed, replayed, idempotency_key and the original version:1 request. Internal activation proof is not exposed. Shift conflicts require separate explicit resolution. Busy or malformed/unknown outcomes return 503 booking_reassign_unresolved; no error or lookup miss proves the original did not commit. Use the close endpoint before replacing a retained request.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
bookingIdstring · required
Booking ID
The chosen pass and exact reviewed booking snapshot; retain omitted fields as omitted on legacy retries.
Resolve an original pass-change attempt before replacing it
Requires bookings.manage and the same original Idempotency-Key and body as reassignment. Returns the original committed success receipt, or a strictly correlated success:false,error:booking_reassign_closed receipt with operation_id,booking_id,new_pass_id,request,replayed,idempotency_key. Only this closed receipt allows replacement; 409 conflicts, 422 refusals, missing results and 503 uncertainty retain the original. Closing never cancels or reverses a committed change and sends no notification.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
bookingIdstring · required
Original booking ID
Exact original reassignment body, with no refreshed expectations.
The venue catalogue the Business app builds the Visits filter sheet from: active class types (with their brand), this venue’s instructors, active locations and active brands. Served under the same grant as the list these choices filter, so a staff member who can see every booking can always load the sheet. Every list is org-scoped.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
GET/api/v1/admin/schedule/{id}Bearer or API key
Admin schedule item
One class instance in the GET /api/v1/admin/schedule item shape (per-status counts, concurrency token, instructor/substitute, historical_capabilities) plus notification_availability.{cancel,edit,substitute,restore}.{clients,instructor}.{email,sms,push}. Use this for a class detail instead of reading the whole schedule. Each channel entry is {available, reason_code, gate_code, reason, recipient_id, reachable, total, blocked, reach_label}: reason_code is the app vocabulary (venue_capability, unsupported, no_contact, invalid_contact, user_opted_out, no_registered_device, no_recipients, availability_error) and gate_code the precise gate. A channel is available when the venue gate passes and at least one recipient is reachable. Callers without class.cancel or notifications.send get every channel unavailable (availability_error). A protected historical class is 404 without historical read access. Requires schedule.view_all and current location access.
Parameters, scopes and examples
Required scopes
read:schedule
Path parameters
idstring · required
Class instance ID
POST/api/v1/admin/schedule/notification-availabilityBearer or API key
Notification channels for a class selection
Read-only. Returns {action, can_notify, clients, instructor} for 1–200 tenant-owned classes and one action (cancel, edit, substitute, restore). A channel is available when the venue gate passes and at least one recipient across the selection is reachable; counts and blocked reasons add up across classes. Clients start silent; the instructor picker may pre-select Email and Push when available. Restore reports unsupported until restore notifications ship. Requires schedule.view_all; can_notify reflects notifications.send, and callers with neither class.cancel nor notifications.send get every channel unavailable (availability_error). Foreign classes return 422 CLASS_SCOPE_MISMATCH.
Requires members.contact. Sends one templated request (profile_update_request) on the explicitly chosen channel asking the client to add a missing detail or confirm/update an existing one. The message is written as the client's acquisition brand of this venue (else the venue): branded email layout with a button, brand-named SMS and push. It links to the member profile opened at that detail (`/profile?field=<field>` on the brand/venue host; push data carries `type`, `field`, `url` path and `organization_id`). An address request to a member the age-based VAT exemption applies to also asks them to confirm it as their home address there. Venue-scoped, audited (member_profile_update_requested), one request per client and field per 24 hours. 404 CLIENT_NOT_FOUND outside the venue; 422 PROFILE_REQUEST_FAILED with a staff-readable reason (no email/phone on file, email blocked or not sent, no app device, already asked).
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
idstring · required
Client profile id
The detail to ask about and the one channel to send on.
Read-only payment snapshots retained during a brand split or transfer. These records are separate from the live payment ledger and do not support refunds, receipt resends, invoice actions or revenue totals. Each row retains its original amount in major currency units, ISO currency, source status, occurrence time and snapshot time. Archive IDs are not payment IDs. Pagination sorts by original occurred_at and archive ID; reuse the exact opaque cursor with the same venue and member. Invalid pagination returns 400; unavailable or unverified history returns 503 rather than an empty history. Requires members.view_insights and active membership in the authenticated venue.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active client ID in the current venue.
Query parameters
limitnumber
Page size: 1–100, default 25.
afterstring
Opaque next_cursor from the same archive collection and scope.
Removes one saved card from an active client of this venue. Requires members.contact and an Idempotency-Key (≤128 chars, bound to the venue, the operator, this client and this card; reuse for another card returns 409). The card must be on the client’s provider-proven wallet — an unknown id and another client’s id answer the same 404 PAYMENT_METHOD_NOT_FOUND, never an existence oracle. A shared platform-wallet card owned by another home venue answers 403 WALLET_FORBIDDEN. If the card was the default, the profile column and the exact Stripe customer default are cleared first (rolled back if the processor refuses) and only then is the card detached; a processor refusal answers 502 STRIPE_DETACH_FAILED. Audited; the client is never contacted.
Makes the named saved card the client’s default for future off-session charges. Requires members.contact and an Idempotency-Key. Only a card can be a default — a non-card method answers 422 NOT_A_CARD. 404 PAYMENT_METHOD_NOT_FOUND for an id not on the proven wallet, 403 WALLET_FORBIDDEN for another home venue’s shared wallet, 502 STRIPE_UPDATE_FAILED when the processor refuses (the local column write is rolled back). Setting the card that is already the default is a 200 no-op with no audit row. The client is never contacted.
Clears the client’s default card so nothing is charged off-session without a fresh choice. Requires members.contact and an Idempotency-Key. The path names the card the operator believes is current: if it is NOT the client’s default any more the request is refused with 409 NOT_DEFAULT (carrying the real default) rather than clearing a different card. 403 WALLET_FORBIDDEN and 502 STRIPE_UPDATE_FAILED as above. The client is never contacted.
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
idstring · required
Active member ID
pmIdstring · required
The card the operator believes is the current default
Emails a payment receipt for each selected payment of this venue’s active client, including non-POS rows the single-payment receipt/send route skips. Requires members.contact plus notifications.send on this request, and an Idempotency-Key. Body is { payment_ids: uuid[] } (1…100). Each item reports sent, deduped, ineligible, or failed. Preserve the original member, payment set and key when recovering an interrupted request; do not start another batch merely because its response was lost. The client is contacted by email only; omission of the key is 400, a foreign client is 404.
What the desk charge form needs before it can be shown: the venue currency, the display VAT rate (decimal) and its label, and the venue’s accounting categories. Requires pos.sell. An empty categories list means the venue has not set any up — the app disables the form and points at Settings rather than charging into a required column.
Charges an ad-hoc amount to the client’s saved card off-session (a phone payment, a fee) and records the sale. Requires pos.sell and an Idempotency-Key. amount_minor is an integer in minor units and is VAT-inclusive — exactly what the card is charged. The card’s proven Stripe customer/account is what the PaymentIntent is created on and confirmed on. 201 on success with the new payment id, the receipt reference and the card label. 422 CHARGE_NOT_CHARGEABLE with reason payments_disabled | not_on_wallet | below_minimum | connect_not_active means nothing was attempted and the same key may be reused. 402 CHARGE_REQUIRES_ACTION means the card needs the client’s own authentication — the app then offers the secure card link. 402 CHARGE_DECLINED and 500 CHARGE_FAILED keep the key. set_as_default applies the card as the default afterwards, best effort; default_updated is null when it was not requested. No receipt is sent — the app offers the receipt route afterwards with the returned payment_id.
Parameters, scopes and examples
Required scopes
pos.sell
Path parameters
idstring · required
Active member ID
The card, the VAT-inclusive amount in minor units, and the bookkeeping fields
The client’s open no-show and late-cancel debt, newest first. A declined charge stays on the list — pending and failed both mean money is still owed. Requires passes.manage, the same grant the web read uses; collecting or waiving needs billing.refunds.full. Amounts are integer minor units beside their currency, and each row carries the class name and start time when the linked booking still has them.
Collects an open no-show or late-cancel fee from the client’s card. Requires billing.refunds.full and an Idempotency-Key. The fee is loaded scoped to this venue (404 otherwise) and must still be open — a charged, waived or refunded fee answers 422 FEE_NOT_UNPAID with its status before anything is reserved. A declined card answers 422 FEE_CHARGE_DECLINED and the debt stays on the client. Every other non-charged outcome answers 422 FEE_NOT_CHARGEABLE with a plain-language message and details.collection: not_chargeable (no card on file, or card payments not ready — nothing attempted), in_progress (another charge of this fee is running), ambiguous (the provider result is not confirmed; the fee stays protected and is replayed with the same provider key), captured_unrecorded (the card was charged and the receipt is still being recorded — never charged again) or reconciliation_required (an earlier charge needs payment review). The fee is claimed in the database before the card is charged, so the same key may be reused safely. The client is never contacted.
Records that an open fee was collected outside the card processor — cash, bank transfer, MobilePay, an external card terminal, or other. Requires billing.refunds.full and an Idempotency-Key. There is no amount: a fee is always settled for its own amount. The venue-scoped load and the 422 FEE_NOT_UNPAID check run before anything is reserved. The underlying write is atomic and idempotent on a re-run. A refusal answers 422 FEE_SETTLE_REJECTED with the reason. The client is never contacted.
Parameters, scopes and examples
Required scopes
billing.refunds.full
Path parameters
feeIdstring · required
Cancellation fee ID
How the money was collected, and an optional internal note
Request body
{
"method": "bank_transfer",
"note": "Paid at the desk, ref 1234"
}
Forgives an open no-show or late-cancel fee: no money is collected and no revenue is recorded. Requires billing.refunds.full and an Idempotency-Key. The venue-scoped load and the 422 FEE_NOT_UNPAID check run before anything is reserved. reason is optional and is recorded in the audit trail only. While a card charge of this fee is running or awaits payment review, the waiver is refused with 409 FEE_PAYMENT_REVIEW_REQUIRED; a fee charged or settled meanwhile answers 422 FEE_NOT_UNPAID; a write that could not be confirmed answers 503 OPERATION_FAILED. In all three nothing is written and the same key may be retried. The client is never contacted.
Parameters, scopes and examples
Required scopes
billing.refunds.full
Path parameters
feeIdstring · required
Cancellation fee ID
Optional internal reason recorded in the audit trail
The saved cards a retry of this exact failed payment may use, default first. The wallet is read live from the failed invoice or PaymentIntent’s own Stripe customer on the account that payment was made on, so the picker can never offer a card the retry would then fail to charge. Requires passes.manage. 400 INVALID_PAYMENT_ID for a malformed id, 404 NOT_FOUND for another venue’s payment, and 422 RETRY_OPTIONS_UNAVAILABLE when the payment is not failed, has no client wallet, or has no processor customer.
Mints a one-time, seven-day link the customer can use to pay back a refund that already completed and should not have. Authority is the same one the web action uses: a venue owner or finance staff member who also holds billing.refunds.full (403 FORBIDDEN otherwise). Idempotency-Key required. The refund is loaded scoped to this venue (404 otherwise) and must be succeeded; 422 REPAYMENT_LINK_UNAVAILABLE carries reason not_succeeded | already_repaid | activated_link_exists | attribution_review_required | payer_unresolved | no_email. Nothing is sent: the platform never contacts the customer here — the operator shares the returned link themselves, exactly as on the web.
The stat tiles and revenue breakdown for a client’s Billing tab: what they owe, how much of that is overdue, their account credit (which may be negative), their gift-card balance, their lifetime spend and their spend so far this venue-local month, plus their spend split by accounting category. Requires payments.view. Every amount is an integer in minor units. Lifetime and month-to-date are exact server-side sums, not a sample of recent rows; totals_truncated is true only for the rare client whose succeeded-payment history exceeds the 50,000-row walk, and the two sums are then the newest 50,000 payments rather than the exact figure. Saved cards are deliberately not included here — read them from the payment-methods route, which resolves the account the cards actually live on.
A client’s recurring product subscriptions (lockers, rentals), newest first, with the price in minor units, the billing interval, the next billing date and any scheduled cancellation date. actions.cancel is true only while the subscription is active, pending or past due — the same rule the web section applies. Requires members.view_insights. The app shows the section only when the list is non-empty.
Stops a recurring product subscription. mode period_end lets the client keep what they already paid for; mode now ends it immediately. Requires products.manage and an Idempotency-Key. The subscription is loaded bound to BOTH this venue and this client (404 otherwise) and must still be cancellable — anything else answers 422 SUBSCRIPTION_NOT_CANCELLABLE with its status before anything is reserved. A processor refusal answers 502 STRIPE_UPDATE_FAILED and the local row is unchanged. Cancelling cannot be undone. The client is never contacted.
Permission passes.manage. Requires an Idempotency-Key and a recurring past_due or suspended pass with an outstanding failed subscription payment. A future venue-local grace day up to 90 days ahead restores a suspended pass to past_due while keeping its debt and original failure anchor. Silent; no client notification is sent.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Venue-local inclusive grace day and staff reason
Request body
{
"grace_until": "2026-10-15",
"reason": "Approved by the venue manager"
}
Client-account parity P1 (A9): push a pass's validity end date out, running the same core the web pass card, the client-list bulk extend and the AI assistant use (audit pass_extended carries the previous end date and the client, so the change stays manually reversible). A staff extension keeps original_end_date from the first extension and never adds to extension_count, which counts only the client's own self-extensions. Permission: passes.manage. Idempotency-Key required; a replay returns the stored response. Client delivery is silent by default and requires notify.audience.clients=true plus an explicit Email/SMS/Push selection AND notifications.send on the same request; an unavailable selected channel returns PASS_NOTIFICATION_CHANNEL_UNAVAILABLE before the pass changes. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'extend' } when the pass cannot be extended (a pass needs an end date); 422 INVALID_RANGE for an end before the start and HARD_END_EXCEEDED for one past the pass type's hard end; 422 NO_END_DATE / AT_HARD_END when the pass has no end date or already runs to (or past) its hard end; 409 PASS_CHANGED when the pass changed while it was being extended (nothing written — retry).
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
New end date and the explicit, default-silent client notification choice
Client-account parity P1 (A8): add or remove clips on a clip card. A zero delta is refused with 422 INVALID_DELTA, and the resulting balance floors at 0 (the web rule — staff can zero a card, never owe it). Permission: passes.manage. Idempotency-Key required; a replay returns the stored response. Client delivery is silent by default and requires an explicit channel selection plus notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'adjust_clips' } when the pass has no finite clip balance.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Signed clip delta, optional reason, and the client notification choice
Client-account parity P1 (A10): either move the start and/or end of an activated pass's validity window — the "transfer the activation date" case for manually sold or mis-dated passes — OR, for a pass that has not activated yet (on_first_use/client_chooses_start, no start_date), set how long it stays valid once it does (PASS-VALIDITY-LENGTH-01). The two modes are mutually exclusive on one request: provide at least one of new_start_date, new_end_date or new_duration_days for the DATE mode (an exact new_end_date always wins over a duration), or provide BOTH new_validity_value and new_validity_unit ('days'|'weeks'|'months', bounds 1-1,095 days/1-156 weeks/1-36 months) for the LENGTH mode — never mix the two families on the same call. 422 HARD_END_EXCEEDED when a date-mode end is past the pass type's absolute end date, 422 INVALID_RANGE for every other rejected date-mode window, 422 INVALID_LENGTH for a rejected length (an already-activated pass, a non-on_first_use/client_chooses_start type, an out-of-bounds value, a Flexible-pricing-option pass whose length was frozen at purchase, or a race where the pass activated between read and write) — each with the plain-language message the app shows verbatim. The response reports already_expired when a date-mode window ends before venue-local today (allowed — backdating a correction is legitimate); length mode never sets dates directly, so already_expired is always false for it. This never touches bookings. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'adjust_dates' }.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Date mode: new window (dates and/or duration). Length mode (unactivated passes only): new_validity_value + new_validity_unit. Optional reason, notification choice.
Request body
{
"new_start_date": "2026-02-01",
"new_duration_days": 90,
"reason": "Sold in January, first class in February",
"notify": {
"audience": {
"clients": true
},
"channels": {
"email": true,
"sms": false,
"push": false
}
}
}
Client-account parity P1 (A11): flip auto_renew, syncing Stripe cancel_at_period_end on the subscription's OWN account (a connected-account subscription is always scoped to passes.stripe_account_id), then write the admin-attributed audit row pass.auto_renew_toggled_by_admin. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default and requires an explicit channel selection plus notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'toggle_auto_renew' }.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Desired auto-renew state and the client notification choice
Client-account parity P1 (A21): the venue's non-recurring pass types in name order, minus this pass's current type and minus retired native-managed course/workshop passes — the exact list the web pass editor offers. Read-only: no Idempotency-Key, 30 requests / 60 s per operator. Permission: passes.manage. 404 for a pass that is not this venue's. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'convert_type' } for a recurring or already-ended pass.
Client-account parity P1 (A21): convert a NON-recurring pass (clip card / time pass) to another of the venue's non-recurring types. Credit-balance aware — the unused share of a price drop is issued as account credit and reported as credit_issued_major, so an upgrade never surprise-charges mid-pass. 422 TARGET_RECURRING when the source or the target is a recurring membership (convert those from the subscription page), 422 TARGET_NOT_FOUND for an unknown target or a retired course/workshop pass. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'convert_type' }.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Target pass type and the client notification choice
Client-account parity P1 (A22): move a pass to another client of the SAME venue, with both-ends safety — the pass must belong to this venue and the recipient must hold an ACTIVE membership here (422 RECIPIENT_NOT_CLIENT); a recipient who already owns the pass is refused with 422 RECIPIENT_IS_OWNER, and a recurring membership with 422 RECURRING_UNSUPPORTED (manage those from the subscription actions). A reason is required and recorded on the audit row. Both ends are notified through the selected channels — the new owner AND the previous one — so BOTH clear the per-channel preflight before the write. Find recipient_id with GET /admin/members?search=. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default and requires notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'transfer' } for anything but an active non-recurring pass.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Recipient profile id, required reason, and the client notification choice
Client-account parity P1 (A20): hide an ENDED pass from the client profile without rewriting its lifecycle — history is preserved, the profile is decluttered. Only a terminated, expired or cancelled pass may be archived (422 ARCHIVE_NOT_TERMINAL: end or terminate it first); a pass that is already archived is an idempotent success no-op. Permission: passes.manage. Idempotency-Key required. This route NEVER notifies the client (the web never does): a notify body is accepted and ignored, and notification_summary always reports no channels. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'archive' }.
Client-account parity P1 (A20): bring an archived pass back into the client profile. A pass that is not archived is an idempotent success no-op. Permission: passes.manage. Idempotency-Key required. This route NEVER notifies the client (the web never does): a notify body is accepted and ignored. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'unarchive' }.
Client-account parity P1 (A23): cancel a non-recurring pass immediately, with no refund — the pass editor's "Cancel now", server-side. Cancellation is TERMINAL by design: there is deliberately no undo, and the audit row carries everything needed to reconstruct state. Refunds are NOT part of this call — review the exact payment from the client's Billing tab. early_termination_fee reports the fee this cancellation determined applies (null once the binding period is over); this route never charges it. Permission: passes.manage. Idempotency-Key required. The branded cancellation notice is silent by default and requires an explicit channel selection plus notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'cancel_now' }; 422 RECURRING_UNSUPPORTED if a recurring membership reaches the core (terminate those from the subscription actions).
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Optional reason and the explicit, default-silent client notification choice
Client-account parity P1 (A4): the authoritative Stripe-cycle preview behind the freeze panel. POST carries the two dates but the call is READ-ONLY — it changes nothing, so it takes NO Idempotency-Key and is rate-limited as a read (30 requests / 60 s per operator). preview is null when the pass has no Stripe subscription and there is nothing financial to review. The freeze itself recalculates inside the idempotent financial engine, so this review is advisory: invalidate it whenever either date changes, and echo preview.previewToken as expected_preview_token on POST /admin/members/{id}/membership/pause (422 PREVIEW_STALE when it no longer matches). Permission: passes.manage. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'freeze' } when the pass cannot be frozen.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Proposed freeze window (pause_end must be after pause_start)
Client-account parity P1 (A5): whether the pass's Stripe subscription is ACTUALLY paused, and whether that can be proven against the processor. Drives the "acknowledge unproven pause" confirmation on POST /admin/members/{id}/membership/resume, which accepts resume_from and acknowledge_unproven_pause. Read-only: no Idempotency-Key, 30 requests / 60 s per operator. A pass with no Stripe subscription answers hasStripePause=false, proven=true. Permission: passes.manage. 404 for a pass that is not this venue's; 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'resume' } when the pass is not in a resumable state.
Client-account parity P1 (A13): returns the pass's current renewal collection method. Capability gate: billing_mode (422 PASS_ACTION_NOT_ELIGIBLE otherwise). 404 NOT_FOUND for an unknown or other-venue pass.
Switch a membership between auto-charge and venue-collected
Client-account parity P1 (A13): choose how FUTURE renewals are collected. 'external' flips the Stripe subscription to collection_method='send_invoice' (Stripe stops auto-charging but keeps raising cycle invoices, and dunning skips the pass); 'auto_charge' restores automatic card collection. The Stripe update is scoped to the pass's own connected account. Capability: billing_mode. Idempotency-Key required. Client delivery is silent by default and accepts only notify.audience.clients=true plus an explicit Email/SMS/Push selection, preflighted on the membership_admin_changed event before the pass changes. Errors: 422 BILLING_MODE_UNCHANGED when the pass already uses that method, 422 NO_CLIENT, 422 STRIPE_NOT_CONFIGURED, 500 STRIPE_UPDATE_FAILED / UPDATE_FAILED. Audit: pass.billing_mode_changed.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Target billing mode and an explicit, default-silent client notification choice
Client-account parity P1 (A14, EXTERNAL-MINT-01): record that this cycle of an externally billed membership was collected at the venue. Settles the subscription's OLDEST OPEN Stripe invoice through the canonical rails — recordManualSettlement for the attribution intent, then paid_out_of_band — so the invoice.paid recovery writes the payments row, rolls the period, resets clips and sends the receipt. Scoped to the pass's own Stripe account. Capability: billing_mode, and the pass must be billing_mode='external' (422 NOT_EXTERNALLY_BILLED). Idempotency-Key required. Never notifies. Errors: 422 NO_OPEN_INVOICE, 422 NO_SUBSCRIPTION, 422 STRIPE_NOT_CONFIGURED, 502 SETTLEMENT_FAILED / OPERATION_FAILED. Audit: payment.settled_externally (irreversible).
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
How the venue collected the cycle
Request body
{
"method": "bank_transfer",
"note": "Paid at the desk, ref 4471"
}
Client-account parity P1 (A15): what is outstanding on a failed recurring renewal — amount, when it failed, how overdue it is, whether booking is suspended, any pending late fee, original payment identity/status, bank failure reason, individual grace date, subscription-pinned card and exact payment-wallet choices. Amounts remain major units; unknown provider/wallet reads are explicit. Deliberately NOT capability-gated: a pass with nothing outstanding returns recovery=null so the card can hide itself, and 404 NOT_FOUND (unknown or other-venue pass) is the only refusal.
Client-account parity P1 (A15): settle the outstanding renewal off-session on the client's saved card, as the venue. Success uses the canonical invoice.paid finaliser and original payment receipt; deliberate termination prevents reactivation and older invoices cannot roll the current cycle backwards. Capability: renewal_recovery. Idempotency-Key required. Optional payment_id pins the original local debt; optional payment_method_id selects an owned saved card. Legacy empty body remains accepted. Returns additive receipt {operation_id,payment_id,status,settled}; 409 PAYMENT_PENDING retains an uncertain original operation. Retry the same charge endpoint with its original key/body to reconcile; durable SQL ownership survives the outer HTTP reservation. Keep the original key/body and reconcile that payment; never substitute a later debt. Never notifies directly. Errors: 402 RENEWAL_REQUIRES_ACTION when the card needs the CLIENT to confirm (the app then offers the payment link — the client secret is never on this wire), 422 RENEWAL_CHARGE_FAILED with the processor's plain-language reason and additive details.reason (the machine RenewalPayFailureCode); a receipt-less 422 whose reason is PAYMENT_NOT_COLLECTABLE, CARD_PAYMENTS_UNAVAILABLE, CARD_NOT_AVAILABLE or NO_PAYMENT_METHOD was refused before any provider call (sent only while the request owns no durable operation, else 409 PAYMENT_PENDING) and releases the original attempt, while any other or missing reason stays unresolved. Replaying the original key/body after the exact payment settled returns the ordinary paid shape (receipt omitted when no operation was admitted) instead of NOTHING_OUTSTANDING.
Client-account parity P1 (A15): email and/or SMS the no-login pay link for the outstanding renewal (deduped once per pass, failure and day). The notification IS the mechanism, so notify is REQUIRED: notify.audience.clients=true plus at least one of channels.email / channels.sms, else 400 CHANNEL_REQUIRED. Push is ignored. notifications.send is re-checked on this request and each channel is preflighted on the renewal_payment_link event before anything is sent (422 PASS_NOTIFICATION_CHANNEL_UNAVAILABLE / 502 PASS_NOTIFICATION_PREFLIGHT_FAILED). Capability: renewal_recovery. Idempotency-Key required. 422 RENEWAL_PAYMENT_LINK_FAILED when the client has no reachable address or the send fails. Audit: membership.renewal_payment_link_sent.
Client-account parity P1 (A15): delete the late-fee invoice item dunning attached to this failure, while it is still pending. Once the fee lands on a finalized invoice there is nothing to delete and the venue resolves it through the normal refund path — that case answers 422 NO_PENDING_LATE_FEE, checked before the write. Capability: renewal_recovery. Idempotency-Key required; empty body. Never notifies. 422 WAIVE_LATE_FEE_FAILED when Stripe refuses. Audit: membership.late_fee_waived.
Client-account parity P1 (A19): delete a membership SETUP row that never became a membership — only when Stripe proves the subscription is dead and nothing links to it. Real history is never deleted. expected_updated_at is the optimistic-concurrency token (409 STALE when the row moved). The Idempotency-Key IS the cleanup operation key and MUST be a UUID (400 IDEMPOTENCY_KEY_INVALID); the atomic RPC records it, so a retry whose response was lost replays the committed removal instead of a false 404. Capability: cleanup_failed_setup. Never notifies. Errors: 404 NOT_FOUND, 422 HAS_HISTORY / HAS_LIFECYCLE_HISTORY / PROCESSOR_UNKNOWN / NOT_RECURRING / NOT_SAFE / NOT_REMOVABLE with the web's messages. Audit: membership.setup_artifact_removed, or membership.setup_artifact_removal_denied on a refusal.
Client-account parity P1 (A16): the whole Share-on-the-pass state for one pass — its shares (with the recipient's display name, monthly cap and classes used this month), its pending invites, and the sharer-slot budget from pass_types.max_sharers. Capability: share (a pass type that allows no sharers is 422 PASS_ACTION_NOT_ELIGIBLE). 404 NOT_FOUND for an unknown or other-venue pass.
Client-account parity P1 (A16): add one sharer on the owner's behalf. Provide exactly one of recipient_user_id or email; both or neither is 400 VALIDATION_ERROR. An email resolves to a unique active venue member or a pending invitation. One SQL transaction commits and retains that original decision, even if the email or membership later changes. Original Idempotency-Key and body are required. Existing unexpired pre236 HTTP responses are preserved first; new requests never acquire a second HTTP mutation claim. SQL validates staff authority, venue, active pass, sharing rights and slots. Results include a strict shared or invited receipt before optional current pass projection. No token or notification send is returned. 403 PASS_SHARE_FORBIDDEN, 409 PASS_SHARE_REQUEST_CONFLICT and 503 PASS_SHARE_UNRESOLVED retain the original request; never replace its key after an uncertain response.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Exactly one of recipient_user_id or email. Optional monthly cap applies to recipient UUID grants; the legacy email branch ignores it.
Client-account parity P1 (A16): revoke access using the original pass, share and Idempotency-Key. An empty body resolves the retained share binding before mutable lookup; an explicit recipient_user_id and optional expected_usage_revision preserve the reviewed request. SQL validates staff authority and venue. Existing unexpired pre236 HTTP outcomes are preserved first. Strict revoked or closed proof precedes optional pass projection; a closed result does not claim revocation. 403 forbidden, 409 original-request conflict and 503 unknown outcomes retain the same request. Never notifies.
Client-account parity P1 (A16): cancel a share invite before its token is redeemed, freeing the sharer slot it was holding. Only a still-pending invite can be cancelled — one already accepted or revoked answers 422 INVITE_NOT_PENDING. Pass-scoped: an invite on another pass or in another venue is the same 404 NOT_FOUND. Capability: share. Idempotency-Key required (no body; the invite id rides the request fingerprint). Never notifies. Audit: pass_share_invite_cancelled.
Requires passes.manage AND bookings.manage. Service SQL validates the pass/share tenant and canonical booking location permissions. Returns a bounded page with explicit evidence, generation, revision, aggregate counter, attributed amount and unresolved remainder. after_booking_id UUID cursor; limit 1–200, default 100. Preview is read-only and never changes a counter or sends notifications. Reject an inconsistent snapshot; do not infer unlisted history. HTTP 503 means evidence is unavailable.
Requires passes.manage AND bookings.manage and canonical SQL booking location authority. Idempotency-Key (trimmed, control-free, 1–200 characters) and exact original version 1 body are required. One key cannot change actor, organization, pass/share or body. Allocations contain 1–200 unique booking UUIDs, exact reviewed timestamps/pass IDs and contribution 0 or 1; reason is trimmed, 1–1000 characters. Original replay precedes mutable review facts. Applied allocates retained remainder without increasing the aggregate; closed seals that original request without allocating. Both terminal receipts echo the original key/request and immutable pass/share. A null read only means no receipt was found, never permission to discard an uncertain request. 409 conflict, 403 refusal and 503 busy/stale/unknown do not authorize replacement; retain original body/key and read or explicitly close it. Nothing is sent.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
passIdstring · required
Original pass UUID
shareIdstring · required
Original share UUID belonging to this pass
Exact reviewed version 1 request, retained before the first submission
Requires passes.manage AND bookings.manage and canonical SQL booking location authority. Idempotency-Key (trimmed, control-free, 1–200 characters) and exact original version 1 body are required. One key cannot change actor, organization, pass/share or body. Allocations contain 1–200 unique booking UUIDs, exact reviewed timestamps/pass IDs and contribution 0 or 1; reason is trimmed, 1–1000 characters. Original replay precedes mutable review facts. Applied allocates retained remainder without increasing the aggregate; closed seals that original request without allocating. Both terminal receipts echo the original key/request and immutable pass/share. A null read only means no receipt was found, never permission to discard an uncertain request. 409 conflict, 403 refusal and 503 busy/stale/unknown do not authorize replacement; retain original body/key and read or explicitly close it. Nothing is sent.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
passIdstring · required
Original pass UUID
shareIdstring · required
Original share UUID belonging to this pass
Exact reviewed version 1 request, retained before the first submission
Requires passes.manage AND bookings.manage and canonical SQL booking location authority. Idempotency-Key (trimmed, control-free, 1–200 characters) and exact original version 1 body are required. One key cannot change actor, organization, pass/share or body. Allocations contain 1–200 unique booking UUIDs, exact reviewed timestamps/pass IDs and contribution 0 or 1; reason is trimmed, 1–1000 characters. Original replay precedes mutable review facts. Applied allocates retained remainder without increasing the aggregate; closed seals that original request without allocating. Both terminal receipts echo the original key/request and immutable pass/share. A null read only means no receipt was found, never permission to discard an uncertain request. 409 conflict, 403 refusal and 503 busy/stale/unknown do not authorize replacement; retain original body/key and read or explicitly close it. Nothing is sent.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
passIdstring · required
Original pass UUID
shareIdstring · required
Original share UUID belonging to this pass
Exact reviewed version 1 request, retained before the first submission
Requires passes.manage. Retain actor, owning organization, pass UUID, original key and exact versioned request before submitting. The submitted actor is an assertion checked against authenticated authority. The request authority must be staff. Grant uses shareId:null; revoke retains the reviewed share UUID and recipient UUID. Idempotency-Key must equal operationKey. SQL enforces tenant, location, rights and usage atomically. Read returns immutable proof or null; null and errors do not authorize a replacement request. Close seals an unapplied request and returns kind:closed; an existing committed receipt wins. No notifications are sent. HTTP403 is denied scope,409 is conflicting original input,503 is unresolved. Preserve the original request for read/retry/close.
Requires passes.manage. Retain actor, owning organization, pass UUID, original key and exact versioned request before submitting. The submitted actor is an assertion checked against authenticated authority. The request authority must be staff. Grant uses shareId:null; revoke retains the reviewed share UUID and recipient UUID. Idempotency-Key must equal operationKey. SQL enforces tenant, location, rights and usage atomically. Read returns immutable proof or null; null and errors do not authorize a replacement request. Close seals an unapplied request and returns kind:closed; an existing committed receipt wins. No notifications are sent. HTTP403 is denied scope,409 is conflicting original input,503 is unresolved. Preserve the original request for read/retry/close.
Requires passes.manage. Retain actor, owning organization, pass UUID, original key and exact versioned request before submitting. The submitted actor is an assertion checked against authenticated authority. The request authority must be staff. Grant uses shareId:null; revoke retains the reviewed share UUID and recipient UUID. Idempotency-Key must equal operationKey. SQL enforces tenant, location, rights and usage atomically. Read returns immutable proof or null; null and errors do not authorize a replacement request. Close seals an unapplied request and returns kind:closed; an existing committed receipt wins. No notifications are sent. HTTP403 is denied scope,409 is conflicting original input,503 is unresolved. Preserve the original request for read/retry/close.
Accepts a Bearer JWT with `loyalty_price.grant` OR `passes.manage`. Unlike every other capability-gated pass read, a pass whose type offers no loyalty price and whose client holds no by-hand grant answers `{ context: null }` — never a 422.
Accepts a Bearer JWT with `loyalty_price.grant` (no API-key credential — this is a desk/termination-flow action). Requires an Idempotency-Key (≤128 chars). Issues the comeback offer with `origin: save_accepted` AND applies it immediately (`accepted_via: staff_save`), mirroring the web `acceptSaveOfferAtTermination`; notify is fixed silent. The app then continues its own termination flow.
Parameters, scopes and examples
Required scopes
loyalty_price.grant
Path parameters
idstring · required
Client id
The pass being saved and the win-back template to apply.
POST/api/v1/admin/loyalty-price/revokeBearer or API key
Take away a client’s by-hand loyalty price
Accepts exactly one credential: Bearer JWT with `loyalty_price.grant`, or an API key bound to the venue with `write:passes`. Requires a UUID `Idempotency-Key`, mirroring `loyalty-price/grant`. Writes through the context-free `revokeLoyaltyPriceCore` (the ONE writer).
Parameters, scopes and examples
Required scopes
write:passes
user_id + a short reason. Optional pass_id also clears a loyalty override on that pass.
Request body
{
"user_id": "uuid",
"reason": "Client asked to go back to the standard rate"
}
Accepts exactly one credential: Bearer JWT with `passes.manage`, or an API key bound to the venue with `write:passes`. Requires an Idempotency-Key (≤128 chars). Default-silent client notification — a non-empty channel selection requires `notifications.send` re-checked on the same request (JWT only; an API-key caller can never notify) plus a per-channel preflight before the mutation — refused as 422 `PASS_NOTIFICATION_CHANNEL_UNAVAILABLE` / 502 `PASS_NOTIFICATION_PREFLIGHT_FAILED`, the same codes every P1 pass route answers.
Parameters, scopes and examples
Required scopes
write:passes
pass_id + the agreed amount + a locked_until date (venue-local, must be after today) + a reason.
DELETE/api/v1/admin/rates/lock/{passId}Bearer or API key
Remove a pass rate lock
Accepts exactly one credential: Bearer JWT with `passes.manage`, or an API key bound to the venue with `write:passes`. Requires an Idempotency-Key (≤128 chars). Default-silent client notification — a non-empty channel selection requires `notifications.send` re-checked on the same request (JWT only; an API-key caller can never notify) plus a per-channel preflight before the mutation — refused as 422 `PASS_NOTIFICATION_CHANNEL_UNAVAILABLE` / 502 `PASS_NOTIFICATION_PREFLIGHT_FAILED`, the same codes every P1 pass route answers.
GET/api/v1/admin/rates/context/{passId}Bearer or API key
Get a pass’s full rate context
Accepts a Bearer JWT with `rates.view`, falling back to `passes.manage` on a 403 (mirrors the web `getPassRateContext`), or an API key bound to the venue with `read:passes`.
Client-account parity P4 (A27): the membership’s real billing history from the processor — the current cycle, whether it is set to cancel, the last twelve invoices with their paid/failed state and hosted links, the next payment, and the card on file. Requires passes.manage (not billing.manage — this mirrors the web editor’s own gate). Capability gate: subscription_timeline, so a non-recurring pass, an unfinished setup and a pass with no live subscription all answer 422 PASS_ACTION_NOT_ELIGIBLE. Every processor call is scoped to the pass’s own connected account. 502 STRIPE_ERROR when the processor is unreachable or unconfigured; 502 INCOMPLETE_PERIOD when it returns a billing period the app cannot render — retry. Nothing is written and the client is never contacted.
Client-account parity P4 (A29): shifts the membership’s next charge to a chosen date, with no proration — the canonical processor mechanism, scoped to the pass’s own connected account. Requires billing.manage, an Idempotency-Key and capability subscription_timeline. new_date is YYYY-MM-DD and must be in the future and within one year; anything else answers 422 INVALID_BILLING_DATE with the exact reason. 422 NO_STRIPE_SUBSCRIPTION when the membership has no recurring billing to shift. 502 STRIPE_ERROR frees the key for a corrected retry — the processor refused before anything moved. 500 DB_ERROR KEEPS the key, because the anchor already moved and a retry must not shift it twice.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
The new date, plus an optional explicit client-notify selection
Client-account parity P4 (A30): changes what the membership costs from its next payment onward. A new processor price is created on the SAME product and interval and swapped onto the subscription item with no proration; per-period overrides still win for the periods they cover. Requires billing.manage, an Idempotency-Key and capability subscription_timeline. amount_minor is an integer in the venue’s minor units and must be greater than zero. 422 NO_BILLABLE_ITEM when the subscription has no item to reprice. A 502 STRIPE_ERROR frees the key when the new price was never created, and KEEPS it when the price exists but the item swap failed — a retry must not create a second price. 500 DB_ERROR keeps the key (the processor already changed).
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
The new recurring amount in minor units, plus an optional notify selection
Read a membership’s payment overrides and upcoming periods
Client-account parity P4 (A28): the SERVER-computed preview the web override panel renders, so the app never re-derives billing math. `preview` is the next `periods` billing windows (default 6, 1…36, anything else is 400 VALIDATION_ERROR) with each window marked as the base price or as a covering override, matched exactly as the invoice interceptor matches them. `overrides` is every saved row, ascending by start date; rows whose id appears in no preview period are the panel’s "Other overrides". A row with applied_payment_id set has already charged a real invoice and is locked (no edit, no delete). billing_active is false for a parked or migrated membership with no live subscription — overrides still save, they just stay staged until billing resumes, and billing_inactive_reason is the exact banner text. Requires billing.manage and capability subscription_overrides.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
Query parameters
periodsinteger
How many upcoming billing periods to preview (1…36)Default: 6
Client-account parity P4 (A28): sets an agreed price for upcoming membership payments. mode "periods" expands the membership’s real billing interval forward from its next payment date and writes one row per period; mode "range" writes one row covering an explicit window. amount_minor 0 is a comped period — nothing is charged. Requires billing.manage, an Idempotency-Key and capability subscription_overrides. 422 NO_UPCOMING_BILLING when "periods" is used on a membership with no next payment date; 400 INVALID_RANGE when the range ends before it starts; 500 DB_ERROR frees the key because nothing was written. `created` is how many rows were saved.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
Either the next N periods or one explicit window, plus an optional notify selection
Client-account parity P4 (A28): changes one not-yet-charged override’s amount and window in place. Requires billing.manage, an Idempotency-Key (bound to this pass AND this override, so a key cannot be replayed against another row) and capability subscription_overrides. The override is resolved only with both the venue and THIS pass, so an unknown id, another venue’s row and another pass’s row all answer the same 404 NOT_FOUND. 422 OVERRIDE_APPLIED when the row already charged a payment — it can no longer be edited. 400 INVALID_RANGE when the window ends before it starts. The answer is the row as stored, not the request echoed back.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
overrideIdstring · required
Payment override UUID
The corrected amount and window, plus an optional notify selection
Client-account parity P4 (A28): removes a not-yet-charged override so that period returns to the normal price. Requires billing.manage, an Idempotency-Key (bound to this pass and this override) and capability subscription_overrides. Same 404 NOT_FOUND rule as the PATCH. 422 OVERRIDE_APPLIED when the row already charged a payment — it cannot be removed retroactively. The body may be empty; send one only to choose notification channels.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
overrideIdstring · required
Payment override UUID
No fields required; send a notify selection only if the client should hear
Client-account parity P4 (A28): the panel’s multi-select writes. action "set" gives every selected period the same agreed amount — periods that already have an override are updated, base periods get a new row, and if the update fails the rows just inserted are removed again so the batch leaves nothing behind (which is why 500 DB_ERROR frees the key). action "restore" deletes the selected overrides so those periods return to the normal price, and is offered only when every selected period IS an override. Requires billing.manage, an Idempotency-Key and capability subscription_overrides. At most six periods or ids per call. 400 INVALID_RANGE for duplicate or inverted periods; 409 OVERRIDE_CONFLICT when a selected row no longer exists or a new period already has one — refresh and try again; 422 OVERRIDE_APPLIED when a selected row already charged a payment.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
Either the selected periods and one amount, or the override ids to restore
List the membership types this membership can convert to
Client-account parity P4 (A31): the venue’s other live recurring membership types, name-ordered, with each one’s price in the venue’s minor units and its billing cadence. The membership’s own type is excluded, and so are archived types — the change engine refuses those anyway, so offering one would be a dead end. Requires billing.manage and capability change_plan (a non-recurring, terminal or unfinished membership answers 422 PASS_ACTION_NOT_ELIGIBLE).
Client-account parity P4 (A31): quotes the change through the canonical membership-change engine and returns its object verbatim — the same shape GET /api/v1/admin/memberships/change already serves, including the signed, short-lived `quote` the confirm call must hand back. READ-ONLY: it changes nothing, needs NO Idempotency-Key, and runs on the read rate limit. Requires billing.manage and capability change_plan. This editor never offers a custom price, so the quote’s override_amount_minor is always null. Engine refusals map exactly as the memberships/change route maps them (404 / 403 / 409 / 422 / 402 / 503).
Client-account parity P4 (A31): applies the change the preview quoted. Requires billing.manage, an Idempotency-Key and capability change_plan, and the exact signed quote from the preview — its override_amount_minor MUST be null. Both this route and POST /api/v1/admin/memberships/change converge on the same durable quote fingerprint, so a change started on one cannot double-apply on the other. `replayed` is true when a repeated confirmation converged on an already-applied change; the client is not told twice. 409 QUOTE_STALE and the 422s free the key so the app can re-preview; 402 CHARGE_FAILED, 503 PARTIAL_APPLY, 503 CHANGE_IN_PROGRESS and 503 DATABASE_ERROR KEEP it, because the engine may already have moved money or claimed the quote.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
The target type, the signed quote from the preview, and an optional notify selection
Business POS read for an active venue client. Returns sanitized card references only (brand, last4, expiry, default, expired, chargeable); imported display-only cards are explicitly non-chargeable. Requires pos.access (register) OR members.contact (client record, the web admin gate). Never returns customer IDs, processor metadata, full card data, or client secrets.
Business POS card-setup operation for an active venue client. Requires an in-person consent attestation and Idempotency-Key. Returns the SetupIntent client secret, exact Stripe account namespace, legal merchant country, and frozen regional revision for native Payment Sheet.
Update the member phone number after an explicit staff confirmation. Requires members.edit and writes an audit record.
Parameters, scopes and examples
Required scopes
write:members
Path parameters
idstring · required
Member user ID
Supported member profile fields
Request body
{
"phone": "+4512345678"
}
POST/api/v1/admin/members/{id}/creditsBearer or API key
Issue account credit
Grant account credit to an active member (positive manual adjustment) on the same atomic, organization-scoped ledger path as the web action. The response balance is the canonical venue-available balance; profiles.credit_balance is maintained only as an account-wide compatibility cache. Currency must equal the venue currency (422 CURRENCY_MISMATCH). Client delivery is silent by default and requires an explicit canonical Email/SMS/Push selection plus notifications.send; membership and unavailable selected channels are rejected before the balance changes, and legacy booleans remain silent. Idempotency-Key is required (1–255 characters): an identical retry returns the original transaction and balance, while reuse for a different semantic request returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH.
Venue-wide duplicate suggestions for the clients-list merge wizard. Matches are never merged automatically. Requires members.merge plus members.contact and the acting membership’s full client-contact visibility.
Ranked duplicate candidates for the current client in this venue, excluding dismissed pairs. Requires members.merge plus members.contact and the acting membership’s full client-contact visibility.
Dismiss one candidate pair for this venue. Body: { candidate_user_id }. Idempotency-Key required; concurrent reuse is serialized before mutation. Requires members.merge plus members.contact and the acting membership’s full client-contact visibility, matching candidate review.
Fail-closed, venue-scoped merge preview with explicit transfer, retained-identity, indirect invoice descendant, and unsupported-data blocker counts. Immutable brand payment history is counted through a service-only scoped RPC and blocks merging the source profile when populated. Unknown or unreadable ownership is never reported as zero. Body: { primary_user_id, secondary_user_id }. The path id must be one of those two. Requires members.merge plus members.contact and full membership contact visibility. Merge is notification-silent.
Parameters, scopes and examples
Required scopes
members.mergemembers.contact
Path parameters
idstring · required
Primary or secondary member ID
POST/api/v1/admin/members/{id}/mergeBearer token
Merge duplicate client profiles
Merges the secondary profile into the primary survivor using the transactional admin_merge_venue_profiles guard. A populated original-subject brand payment archive blocks the merge, including when the preview is stale. Unsupported data, concurrent setup operations, provider-wallet/subscription ownership changes, and material row conflicts roll back without deactivating the source; retained identity/audit data remains explicit. confirm_token must be the literal MERGE. Notification-silent. Idempotency-Key is bound to venue, path member, and canonical body, with an atomic pre-mutation claim that serializes concurrent reuse. Requires members.merge plus members.contact and full membership contact visibility.
Parameters, scopes and examples
Required scopes
members.mergemembers.contact
Path parameters
idstring · required
Primary or secondary member ID
Survivor, merged-away profile, field choices, and MERGE confirmation
Issues one frozen-term comeback promise per client/template/settlement day, including after that promise closes. Delivery is silent by default; an explicit notify audience and channel set plus notifications.send is required to contact the client. Every selected channel is preflighted before the offer is created and the exact selection only narrows delivery—an SMS-only request can never fall back to email. The response reports actual delivery, and crash-safe deterministic provider/channel dedup permits a silent existing offer to be notified without double-send. Idempotency-Key is bound to venue, path member, and canonical body and atomically claimed before mutation. Permission: loyalty_price.grant.
Mark an open offer as declined (they said no) or revoked (withdrawn). Idempotency-Key required and atomically claimed before mutation. Permission: loyalty_price.grant.
Client-record invoices for this member. Drafts are excluded. Each row includes status, totals, and a share_url for the public invoice page. Permission: invoices.view. Org-wide invoice management remains /admin/invoices.
Venue-scoped family/partner/guest relationships for this client. Permission: members.view_insights. Related email fields are returned only when the caller also holds members.contact and full membership contact visibility; otherwise they are null.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
POST/api/v1/admin/members/{id}/tagsBearer token
Add a client tag
Add a manual member tag. Idempotency-Key required and atomically claimed before mutation. Permission: members.edit.
Parameters, scopes and examples
Required scopes
members.edit
Path parameters
idstring · required
Active member ID
DELETE/api/v1/admin/members/{id}/tagsBearer token
Remove a client tag
Remove a member tag. The tag travels in the query string (?tag=). Permission: members.edit.
Parameters, scopes and examples
Required scopes
members.edit
Path parameters
idstring · required
Active member ID
Query parameters
tagstring · required
Tag to remove
GET/api/v1/admin/bookingsBearer or API key
All bookings
Venue-wide booking list with filtering by date, status, class, member, and location.
Parameters, scopes and examples
Required scopes
read:bookings
Query parameters
fromstring
Start date (YYYY-MM-DD)
tostring
End date (YYYY-MM-DD)
statusstring
Filter by booking status
class_instance_idstring
Filter by class instance
user_idstring
Filter by member
location_idstring
Filter by location
POST/api/v1/admin/bookingsBearer or API key
Book for member
Create a confirmed or waitlisted booking through canonical pass, course, network and allowance eligibility. Requires a caller-stable Idempotency-Key bound to the full request, including notes and notification choices. A 503 BOOKING_COMMIT_UNCONFIRMED retains the same key and exact body; a recovered committed receipt never repeats notification dispatch or notes writes. Narrow staff timing/capacity exceptions do not waive funding. An authorized after-cutoff attempt returns 422 BOOKING_AFTER_CUTOFF_CONFIRMATION_REQUIRED before mutation; confirm with after_cutoff_confirmed and a new intent key. Same-day ended classes also require ended_class_confirmed. Client delivery is silent by default and requires an explicit Email/SMS/Push selection plus notifications.send; unavailable selected channels are rejected before booking/pass/count effects.
POST/api/v1/admin/bookings/waitlistBearer or API key
Add member to waitlist
Manually place a member on a class's waitlist at the queue tail. Always creates a waitlisted booking (never auto-confirms). Client delivery is default-silent and requires both an explicit client audience/channel selection and notifications.send; unavailable selected channels are rejected before queue effects. Current actor/location/body authority precedes org-and-intent-bound replay; ADD requires active venue membership. Atomic queue changes do not consume seats or credits. Responses include operation_id, replayed and effects_status (completed, pending or held); a committed change remains successful if follow-up work fails.
DELETE/api/v1/admin/bookings/waitlist/{bookingId}Bearer or API key
Remove member from waitlist
Remove a waitlisted booking. Waitlisted rows only (409 on a confirmed booking); never triggers auto-promotion. Idempotent. Client delivery is default-silent and requires both an explicit client audience/channel selection and notifications.send; unavailable selected channels are rejected before queue effects. Current actor/location/body authority precedes org-and-intent-bound replay; ADD requires active venue membership. Atomic queue changes do not consume seats or credits. Responses include operation_id, replayed and effects_status (completed, pending or held); a committed change remains successful if follow-up work fails.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
bookingIdstring · required
Waitlisted booking ID
Optional reason and explicit client notification channels. Omit notify to stay silent.
POST/api/v1/admin/bookings/{bookingId}/cancelBearer or API key
Cancel a confirmed booking
Cancel a member's confirmed booking on their behalf (admin cancel semantics — may charge late fees + restore clips per policy; NOT the fee-free lapsed-booking path). Decrements booked_count, writes audit + booking.cancelled webhook, and issues a 30s undo ticket. Client delivery is silent by default: notify.clients.channels (or the older notify.{audience,channels}) requires notifications.send and sends the booking_cancelled_client notice with real Email, SMS and Push content on exactly the chosen channels; an unavailable chosen channel returns 422 BOOKING_CANCELLATION_NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason} before any mutation; legacy notify_client remains silent. `notified` is true only when a chosen channel was sent or queued; `notify_outcome` reports per chosen channel {sent, skipped, failed, pending, reasons} (null when nothing was chosen). Idempotent via Idempotency-Key. Returns 409 ALREADY_CANCELLED on a cancelled booking, 409 BOOKING_CHANGED when the booking changed during the cancel (retry after refresh), and 409 ON_WAITLIST for a waitlisted row (use the waitlist remove endpoint). The success body carries `warning` (string or null): set when the booking was removed but its clip could not be returned to the pass.
Human staff JWT plus history and ordinary operation permissions. Returns data.preview with current booking/class CAS, authoritative fee/payment/refund ledger availability, consumed credit, per-audience notification availability and a ten-minute signed reviewToken bound to actor, venue, booking and target. Query operation and status (omit status for invalidate). Does not correct attendance or move money.
Requires human JWT, scheduling.manage_history and notifications.send. Finalizes the actor-owned notification_batch_id after individual corrections settle. One durable summary per instructor and selected channel includes only committed records. Repeated finalization cannot resend a claimed delivery; finalized batches reject new records. Returns effects, recordCount and recipientCount.
Parameters, scopes and examples
Required scopes
scheduling.manage_historynotifications.send
Stable UUID shared by reviewed corrections in this bulk operation
Human staff JWT and additive history and ordinary operation permissions. Idempotency-Key must be a UUID. expected_class_updated_at is a compare-and-set token; expected_updated_at is a compare-and-set token for existing bookings. Existing-booking reasons are optional and the explicit confirmation button suffices; legacy REWRITE remains accepted. GET review_token binds authoritative fee/refund and credit facts before any selected financial or notification action. financial.fee keep/refund/waive and financial.clip keep/return default to keep; explicit refund/waive requires billing.refunds.full, clip return passes.manage, and notifications require notifications.send. Charged fee badges never override succeeded refund ledger evidence. Money stays on the canonical reviewed refund engine; atomic clip/waive changes retain historical and financial audit. Email, SMS, and Push are available only through explicit notify.audience clients/instructors and channel selections; the default is silent. notification_batch_id consolidates bulk instructor notices on the batch-finalization endpoint. Returns attendance result plus per-effect held/pending/completed outcomes; no claim of full financial/delivery success from attendance alone. If the reviewed database executor is unavailable, the route fails closed with HISTORICAL_EXECUTOR_UNAVAILABLE. Retrocreate retains mandatory reason/REWRITE and has no financial or notification effects.
Parameters, scopes and examples
Required scopes
scheduling.manage_history
Path parameters
bookingIdstring · required
Class booking ID
One closed class-booking correction. Every operation requires expected_class_updated_at; existing-booking operations also require expected_updated_at. Existing-booking history_reason and legacy history_confirmation_token are optional; reviewed effects require review_token. Retrocreate still requires REWRITE.
Bearer-JWT, passes.manage-scoped server-authoritative preview for a recurring membership. Resolves the selected client and saved card in the active venue, validates the venue-local start-date policy, and returns canonical buyer-specific gross pricing, registration fee/waiver, due-today amount, access date, first charge, next renewal, card label, contract/terms summary, and — when an operator discount schedule is requested — the resolved discount_schedule block with the agreed amount, the number of discounted periods and the first full-price charge date. A Flexible recurring membership additionally requires exactly one selection (selection_kind=quantity plus quantity, selection_kind=unlimited, or selection_kind=option plus option_id) and returns flexible_selection plus a short-lived flexible_quote bound to that selection, member, venue, active immutable pricing version, regional tax facts, and full minor-unit breakdown. Create must echo that flexible_quote. Sale/preview Flexible option identity on POST /admin/pos/sale uses camelCase optionId; membership uses top-level option_id. Optional additive flash_sale_id maps to the existing core flashSaleId and is exclusive of promo_code. Optional additive promo_window_exception { kind: flash_sale_after_end | late_code_redemption, reason } prices an ended flash_sale_id (requires marketing.flash_sales) or an expired promo_code (requires marketing.campaigns) exactly as in-window; preview records nothing. Manual registration-fee concessions, free periods, and discount schedules are unavailable for Flexible memberships; only the shared recurring Flexible Flash Sale promo is accepted. Returns review {version:1,fingerprint}; create must echo it as expected_review_version and expected_review_fingerprint. saved_payment_method is null for external tender; contract_and_terms.delivery_availability is optional. Contract delivery defaults to false. This endpoint never mutates or charges.
Parameters, scopes and examples
Required scopes
passes.manage
Recurring membership options with exactly one of saved_payment_method_id (card-collected) or external_tender_method (venue-collected renewals, billing_mode=external). Optional registration_fee_discount applies a per-sale percentage discount to the one-time registration fee; legacy waive_registration_fee remains accepted. Optional discount_schedule sets an operator-agreed price for the first period, a fixed number of periods, or for as long as the membership runs; discount_reason stores the staff rationale with review, Stripe metadata, audit, and rate records.
Bearer-JWT, passes.manage-scoped recurring membership creation through the canonical subscription checkout core. Contract delivery is default-silent; explicit send_contract_and_terms=true additionally requires notifications.send before mutation. Echo expected_review_version and expected_review_fingerprint from preview.review; a mismatch returns STALE_REVIEW (409), requiring a fresh review. For an ambiguous transport or in-progress retry, retain the exact request body and Idempotency-Key. Idempotency-Key is required. Re-resolves pricing, dates, saved-card ownership, Stripe locality, VAT/age band, concessions, and legal delivery before mutation. Optional additive flash_sale_id maps to the existing core flashSaleId and is exclusive of promo_code. Optional additive promo_window_exception { kind, reason } (same permissions as preview) records an audited 30-minute database exception that waives only the sale end / code expiry; caps, per-client limits, eligibility and price are unchanged, refusals are 422, and it is part of the idempotency fingerprint. Flexible memberships require the exact unexpired flexible_quote returned by preview together with the same selection; the server rebuilds and byte-compares its versioned material and never accepts a client-authored amount. A stale, expired, or mismatched quote is refused before checkout. Returns the exact preview plus pass/subscription ids, payment status, and contract-delivery result. Off-session declines remain 402. First-invoice SCA returns 202 with client_secret/payment_intent_id for in-register confirmation, then POST /admin/memberships/finalize. Succeeded first charges also write a pos_transactions receipt row (transaction_id/payment_id). CARD_DECLINED_SETUP_REMOVED (402) proves a linked declined setup passed processor cleanup and atomic removal; a client may release only its first confirmed refusal. CARD_DECLINED, SCA_REQUIRED, unknown errors, later refusals after response loss and finalize failures do not grant fresh-operation authority.
Parameters, scopes and examples
Required scopes
passes.manage
The same options accepted by preview, plus the exact review version/fingerprint and Flexible quote returned by that review
Finalize an in-register membership card confirmation
Bearer-JWT, passes.manage-scoped completion after a 202 requires_action membership create. Verifies the PaymentIntent succeeded, completes mint/activation through the canonical invoice-paid path, and writes the pos_transactions receipt row only for that PaymentIntent’s succeeded payments row in this venue/member with a correlated membership pass_id. Missing pass_id/pass projection or invoice-handler failure is 409 PAYMENT_PROCESSING and does not cache a created membership. Duplicate finalize replays only a locally correlated payment/pass/receipt. Idempotency-Key required. Body: { payment_intent_id }. Does not bounce staff to member checkout.
Bearer-JWT, passes.manage-scoped option list for an organization-owned pass. Each option is buyer-priced by the canonical membership-change quote engine; a failed target is reported separately and cannot hide valid sibling options. Query: pass_id.
Parameters, scopes and examples
Required scopes
passes.manage
GET/api/v1/admin/memberships/changeBearer token
Preview a client membership change
Bearer-JWT, passes.manage-scoped server quote. Query: pass_id, target_pass_type_id and optional override_price_major. Returns exact charge, credit, effective date, next renewal and a short-lived signed quote binding; it never mutates or charges.
Parameters, scopes and examples
Required scopes
passes.manage
POST/api/v1/admin/memberships/changeBearer token
Apply a client membership change
Bearer-JWT, passes.manage-scoped confirmation through the canonical membership-change core. Requires Idempotency-Key and the exact signed quote returned by preview; foreign-venue passes resolve as not found and client-supplied prices are not accepted.
POST/api/v1/admin/pos/pass-mobilepay/prepareBearer or API key
Prepare original MobilePay pass sale
Requires pos.access OR pos.sell and venue-wide location access. Send the exact original POS sale body with its original Idempotency-Key. One payable pass only; configured built-in MobilePay collection rules remain authoritative. Zero-total sales use the ordinary sale endpoint. Freezes the complete sale before provider preparation; no receipt/link is sent. Retain the encrypted original body/key before calling. Response supplies the canonical transformed-request fingerprint; never hash the snake-case body for recovery. Readiness remains disabled until release acceptance.
Parameters, scopes and examples
Existing POS sale body; original Idempotency-Key header required
POST/api/v1/admin/pos/pass-mobilepay/recoverBearer or API key
Resolve original MobilePay pass sale
Requires original authorized actor/venue and current POS/location access. Reads only the exact original body fingerprint/key and optional bound PI; never reconstructs a sale or allocates a replacement source. Returns original approval or verified original completion, including after a subsequent refund; payment_status is the actual current payment state, never an invented succeeded fallback. Missing/unknown sources stay resolving. Keep approval URLs only in memory; closing the panel never cancels. Poll sequentially every three seconds for at most four minutes, then offer explicit original-status checks. No automatic SMS.
Parameters, scopes and examples
Original identity; operation_key is the original sale key, not a new request key
POST/api/v1/admin/pos/pass-mobilepay/linkBearer or API key
Explicitly send original MobilePay pass link
Requires notifications.send independently of POS transaction and venue-wide location access. Send only after an explicit staff choice. Reads the original bound source/account/PI and obtains its approval URL from the provider; never accepts a caller URL or creates/confirms a payment. Canonical recipient normalization, consent, preferences, suppression, provisioning and source-bound outbox deduplication apply. Accepted means provider acceptance, not delivery. Accepted without notification_id and unknown outcomes must lock repeat send; no automatic replacement or retry.
POST/api/v1/admin/pos/pass-mobilepay/link-statusBearer or API key
Read original pass link delivery evidence
Requires original authorized actor/venue and current POS/location access. Reads a notification scoped to this exact sale, member, organization and template. Pending/unknown is not acceptance; failed is not permission to resend. This endpoint never sends. Poll sequentially with a fixed four-minute deadline.
Process an idempotent, default-silent point-of-sale transaction with pos.access OR pos.sell and venue-wide staff location access. No receipt is queued by this endpoint; an explicit post-sale receipt choice uses the separate receipt endpoint. Supports cash, venue credit, and a server-validated saved card. Pass carts may send split_payments (max 8), Flexible/rich pass fields, and optional additive flash_sale_id (exclusive of promo_code; resolved by the existing sale core). Optional additive promo_window_exception { kind, reason } (pass sales for a client only; reason 5-500 characters): flash_sale_after_end sells an ended flash_sale_id and requires marketing.flash_sales; late_code_redemption uses an expired promo_code and requires marketing.campaigns. Only the time window is waived (published sale that ended by time / code expired in the last 90 days); caps, per-client limits, eligibility, scope and price are unchanged. It is part of the idempotency fingerprint, records an audited 30-minute database exception, and refusals are 422. One sale may carry many lines with quantities (max 50 lines, 100 units per line): products with bundles, or services with products; a pass is always its own sale (422 MIXED_CART_NOT_SUPPORTED) and bundles never combine with services. products.max_quantity_per_order applies to every cart shape, summed across lines, after replay lookup and before collection (422 PRODUCT_QUANTITY_LIMIT with details.product_id/max_quantity/requested_quantity); a retried or 3DS-completed sale keeps its frozen cart. expected_total from POST /admin/pos/catalog-preview guards product, bundle and service carts (400 PRICE_CHANGED before collection). Ordinary product card sales may select product_currency with product_price_book_versions (product UUID to accepted version), the exact signed product_quote, and required expected_total from catalog-preview. New operations verify complete residence, item and included-tax authority before collection; existing operation recovery precedes fresh quote expiry or changed-evidence checks. The member must have current home-country billing evidence; every line needs an enabled catalog-tax book. Commissions, loyalty/course/staff benefits, recurring products, product variants, bundles, stored value and other collection methods are refused with PRODUCT_PRICE_BOOK_UNAVAILABLE (422). The exact selection remains part of the retry/finalize body; replay uses frozen money even after settings change. This additive path requires its selected-product SQL authority before activation. Saved-card SCA returns a 202 challenge response and is completed with a separate idempotent finalize request. Recurring memberships stay on POST /admin/memberships. Fresh cards, MobilePay and Stripe Terminal use their dedicated flows.
Recent POS transactions filtered by location and date. Requires pos.access and venue-wide staff location access; a location filter never grants access to restricted staff. Refund headroom subtracts both succeeded and in-flight operation claims; refunded_amount reports succeeded claims and pending_refund_amount reports the reserved in-flight amount.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/transactions/{id}/receiptBearer or API key
Resend POS receipt
Send a tenant-scoped POS transaction receipt by an explicit email, SMS or push choice. Requires pos.access, notifications.send and venue-wide staff location access before delivery. Uses the client's stored contact unless an explicit recipient is supplied for email/SMS. Idempotency-Key is required and retries must keep its exact body. Atomic caller/venue/payload claims prevent concurrent re-sends during the replay window. RECEIPT_CHANNEL_UNAVAILABLE means no provider send was attempted; RECEIPT_DELIVERY_UNCONFIRMED retains the original operation for reconciliation. A sent result is provider acceptance, not device delivery.
Parameters, scopes and examples
Required scopes
pos.accessnotifications.send
Path parameters
idstring · required
POS transaction ID
Receipt delivery channel and optional recipient override
Request body
{
"method": "email"
}
GET/api/v1/admin/pos/summaryBearer or API key
Daily POS sales summary
Requires pos.access and venue-wide staff location access. Daily sales breakdown for the given date (default today): totals (gross/discounts/VAT/credits/net) plus per-payment-method and per-transaction-type buckets. Completed transactions only; same date-window semantics as /admin/pos/recent.
Parameters, scopes and examples
Required scopes
pos.access
Query parameters
datestring
YYYY-MM-DD (default today)
location_idstring
Filter by location
GET/api/v1/admin/pos/payment-methodsBearer or API key
Read configured POS tenders
Active configured tenders are available with settings.business OR venue-wide POS transaction access (pos.access OR pos.sell). include_archived=true requires settings.business. Creation, edits and archiving remain settings.business-only. Collector availability remains enforced by the canonical POS engine.
Parameters, scopes and examples
Query parameters
include_archivedboolean
Include archived tenders; requires settings.businessDefault: false
Requires booking.checkin; no-show additionally requires bookings.mark_no_show. Only canonical notify audience/client channel choices enable delivery and require notifications.send before mutation. Omitted/empty channels and legacy booleans remain silent. Each recipient is preflighted; a failed check-in never dispatches. Successful explicit check-in delivery uses attendance_corrected with the booking-owning venue. Existing check-in cutoff, history, tenant and idempotency rules remain enforced. Check-in skips streaming (online) bookings, whose attendance is recorded on stream playback, and lists them in an additive skipped array ({ booking_id, code: ONLINE_ATTENDANCE_AUTO, reason }) instead of failing them.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance UUID
Selected action, booking IDs and explicit client channels
GET/api/v1/admin/pos/transactions/{id}/receiptBearer or API key
Read receipt channel availability
Read canonical email/SMS/push contact, preference and venue capability reasons for a tenant-owned sale. Requires pos.access and venue-wide location access. Opening the sheet never sends a receipt; POST separately requires notifications.send.
Parameters, scopes and examples
Required scopes
pos.access
Path parameters
idstring · required
POS transaction UUID
GET/api/v1/admin/terminal/receiptBearer or API key
Read captured-sale receipt availability
Resolves the stored payment and POS transaction inside the authenticated venue, then returns the same canonical receipt channel availability. Requires pos.access and venue-wide location access. This read does not enable hardware collection or send a receipt.
Parameters, scopes and examples
Required scopes
pos.access
Query parameters
payment_intent_idstring · required
Recorded payment intent ID
GET/api/v1/admin/dashboard/revenue-seriesBearer or API key
Daily revenue series (sparkline)
Zero-filled daily revenue series ending today — succeeded payments bucketed by UTC day, matching the dashboard revenue_today semantics. days clamps to 1–90 (mobile uses 7 and 30).
Parameters, scopes and examples
Required scopes
read:reports
Query parameters
daysinteger
Window length in days (1–90)Default: 7
POST/api/v1/admin/members/{id}/membership/pauseBearer or API key
Pause membership
Pause (freeze) a member’s pass for a date window. Validated against the pass type’s pause policy; recurring memberships receive exact per-cycle billing credits on their own Stripe account; audit_log pass_paused. Client delivery is silent by default and requires notify.audience.clients=true plus explicit Email/SMS/Push channels and notifications.send. Legacy notification booleans remain silent. Selected-channel availability is rechecked before mutation; exact unavailable/preflight-failed responses leave the membership unchanged. Idempotency-Key honored. {id} accepts UUID or display ID (e.g. HYC-0042).
POST/api/v1/admin/members/{id}/membership/resumeBearer or API key
Resume membership
Resume a paused pass (Stripe-first ordering with compensating re-pause). Audit_log pass_resumed. Client delivery is silent by default and uses only an explicit canonical Email/SMS/Push selection. Selected-channel availability is rechecked before mutation; exact unavailable/preflight-failed responses leave the membership unchanged. Idempotency-Key honored.
Parameters, scopes and examples
Required scopes
write:members
Path parameters
idstring · required
Member user ID or display ID
Resume immediately or from a venue-local date. An unproven Stripe pause stays blocked unless acknowledge_unproven_pause types CLEAR PAUSE plus a reason. Optional explicit client delivery.
POST/api/v1/admin/members/{id}/membership/terminateBearer or API key
Cancel / terminate membership
Cancel or terminate a recurring membership with explicit effective dates: mode period_end (cancel at current cycle end), chosen_cycle (kth upcoming cycle, cycle required), or immediate. Runs the kill-switch-gated termination engine (fail-closed Stripe). Response carries the engine-confirmed effective_at. Client delivery is silent by default and requires an explicit canonical channel choice plus notifications.send; legacy booleans remain silent. Selected-channel availability is rechecked before mutation; exact unavailable/preflight-failed responses leave the membership unchanged. Idempotency-Key honored.
GET/api/v1/admin/members/{id}/membership/termination-previewBearer or API key
Termination preview (cycle picker)
Next 6 cycle boundaries (effective_at, venue-local last usable day, precedes-binding flag), venue policy defaults, and billing horizon for the terminate endpoint’s cycle picker.
Parameters, scopes and examples
Required scopes
read:members
Path parameters
idstring · required
Member user ID or display ID
Query parameters
pass_idstring
Pass ID (uuid)
POST/api/v1/admin/terminal/connection-tokenBearer or API key
Stripe Terminal connection token
Mint a Stripe Terminal connection token plus the venue Terminal location id (`{secret, location_id}`) for card-present readers and Tap-to-Pay. Ephemeral-token fetch — no Idempotency-Key (the Terminal SDK always needs a fresh token).
Parameters, scopes and examples
Required scopes
write:pos
POST/api/v1/admin/terminal/payment-intentBearer or API key
Create Terminal payment intent
Create a card-present PaymentIntent (manual capture) on the venue connected account. Returns `{client_secret, payment_intent_id}`. Money mutation — send an Idempotency-Key; replays return the cached response and the key is forwarded to Stripe.
Parameters, scopes and examples
Required scopes
write:pos
Payment intent
Request body
{
"amount": 12000,
"currency": "DKK"
}
POST/api/v1/admin/terminal/captureBearer or API key
Capture Terminal payment
Capture a confirmed card-present PaymentIntent. Returns `{captured: true, payment_intent_id}`. Money mutation — send an Idempotency-Key; an already-captured intent returns success.
Parameters, scopes and examples
Required scopes
write:pos
Capture
Request body
{
"payment_intent_id": "pi_xxx"
}
POST/api/v1/admin/terminal/receiptBearer token
Send Terminal receipt
Email or SMS a receipt for a captured Tap-to-Pay sale, resolved from the Stripe payment intent id. Requires pos.access, notifications.send and venue-wide location access before delivery. TERM-IDEMP-01: requires a caller-scoped Idempotency-Key; a replayed key returns the cached terminal response instead of re-sending. The atomic claim is bound to caller, venue and exact body. RECEIPT_CHANNEL_UNAVAILABLE is a confirmed pre-send refusal; RECEIPT_DELIVERY_UNCONFIRMED must retain the original operation for reconciliation.
POST/api/v1/admin/notifications/broadcastBearer or API key
Send broadcast
Send one or more push, email, and SMS channels to all members, selected member ids, or a server-resolved tag/pass/class audience. Idempotency-Key is required. Each channel derives a stable per-recipient delivery reference; a partial retry skips terminal successes/suppressions and resumes failed legs. Returns sent/skipped/failed counts per channel.
Parameters, scopes and examples
Required scopes
write:notifications
Broadcast
Request body
{
"channels": [
"email",
"sms"
],
"title": "New class added",
"target": {
"type": "tag",
"tag": "vip"
},
"subject": "New class added",
"body": "Check out our new Hot Power class on Saturday!"
}
GET/api/v1/admin/notifications/recentBearer or API key
Recent notifications
Recent email, SMS, and push notifications sent by the venue.
Parameters, scopes and examples
Required scopes
read:notifications
POST/api/v1/admin/staff/inviteBearer token
Invite a staff member
PROMPT_02 (S1-03) — provisions the auth user + profile + membership (status=invited), mints a staff_invitations claim token, and emails the venue-branded /auth/claim-invite link. Permission: staff.manage. Membership role may be admin, manager, reception, instructor, staff, or service_provider; profiles.role stays on the legacy CHECK (staff/provider seats persist as member there). The token is consumed by the WEB claim page (set password → membership flips invited→active); there is no separate accept API endpoint because the claim sets a password. 409 EMAIL_EXISTS when a Booking Bible account already exists for the email (adding an existing user as staff is a role change — use the admin UI). location_ids is stored on the invitation for record-keeping; location assignment remains a post-onboarding admin action. Emits staff.invited. Idempotency-Key supported.
Every staff shift in the venue for a date range. Permission: staff_scheduling.view. Joins staff profile name. Optional ?status= filter.
Parameters, scopes and examples
Query parameters
fromstring
Start ISO datetimeDefault: -7 days
tostring
End ISO datetimeDefault: +14 days
statusstring
Filter by ShiftStatus
POST/api/v1/admin/staff-scheduleBearer token
Create a staff shift
Create a new shift. Permission: staff_scheduling.manage. Note: API path skips the engine compliance pre-checks; for full compliance use the admin panel or the createShift server action.
Current walk-in queue (waiting + notified) for the authenticated org, ordered by position.
Parameters, scopes and examples
Required scopes
read:bookings
POST/api/v1/admin/walk-in-queueBearer or API key
Add walk-in
Add a walk-in to the queue. Allocates an atomic position via allocate_queue_position(). Idempotency-Key required. Add is silent; notification choice belongs to a later explicit Call. Optional service_id, client_id, preferred_provider_id and location_id must belong to the authenticated organization.
POST/api/v1/admin/walk-in-queue/{id}/callBearer or API key
Call queue entry
Atomically claim a waiting queue entry as called. Default-silent; explicit notify_sms=true requires notifications.send and SMS availability before mutation. Saved Add-time preferences never authorize delivery. A concurrent or already-called entry returns 409 INVALID_STATE. Returns sent_sms and optional notification_failure separately from committed call success. Permission: bookings.manage.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Queue entry ID
Explicit notification choice for this call only
Request body
{
"notify_sms": false
}
DELETE/api/v1/admin/walk-in-queue/{id}Bearer or API key
Remove walk-in
Cancel/remove a walk-in queue entry. Permission: bookings.manage.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Queue entry ID
GET/api/v1/admin/checkin/{classInstanceId}/qr-tokenBearer or API key
Get check-in QR token
Returns the current rotating QR token for a class instance. A new token is generated if none exists or the existing one is expired. Force rotation with ?refresh=true. Token TTL: 5 minutes. Permission: booking.checkin.
Read-only venue-local projection, reference validation, conflicts and daylight-saving time choices. No classes are created. The same body is accepted by the create endpoint.
Parameters, scopes and examples
Required scopes
scheduling.manage
Series template with venue-local wall times and recurrence.
Creates the template and projected occurrences atomically. Idempotency-Key is required; the durable operation is scoped to actor, venue and endpoint. Reusing an operation with a different body returns a conflict. Times resolve in the venue timezone; nonexistent wall times are refused and repeated wall times require an explicit choice.
Parameters, scopes and examples
Required scopes
scheduling.manage
Reviewed series template; confirm_conflicts explicitly accepts permitted conflicts.
Returns the authoritative venue-scoped template for editing. Existing occurrence times can differ from the template; clients must display and choose the intended source explicitly.
Read-only preview for this_only, all_future, from_date or all_including_past. Reports conflicts, affected bookings, capacity floors, notification availability and venue-local time ambiguity. A preview never authorizes client delivery.
Parameters, scopes and examples
Required scopes
scheduling.manage
Path parameters
idstring · required
Series UUID in the authenticated venue
Patch and edit scope; from_date is required for that scope.
Idempotency-Key is required. Start and end are independent fields. The canonical transaction validates the resulting series and current physical/online booking counts. Existing booked/past confirmation requirements remain. Staff delivery defaults silent; only explicit participant or instructor channels request delivery, with notifications.send checked before mutation. Cancelled occurrences require a separate reviewed reactivation instead of silently recreating bookings.
Parameters, scopes and examples
Required scopes
scheduling.manage
Path parameters
idstring · required
Series UUID in the authenticated venue
Patch, scope, applicable confirmations, and optional explicit notify channels.
Read-only review of eligible future cancelled occurrences and previous cancelled booking candidates. Returns opaque schedule/occurrence versions and venue-local projected times. Candidate inclusion is not booking eligibility approval; canonical booking rules are checked when each selected client is restored.
Requires Idempotency-Key and the exact reviewed schedule and occurrence versions. Selected client restoration additionally requires booking authority. Previous cancellation history remains immutable; selected clients receive new canonical bookings or individual ineligible/pending outcomes and current waitlist placement. No client notification or payment is requested by this endpoint. Operational projections retain their own recorded effect state. Stale reviews and changed retry intent return 409.
Parameters, scopes and examples
Required scopes
scheduling.manage
Path parameters
idstring · required
Series UUID in the authenticated venue
Explicit occurrence and client selection. Empty occurrences reactivates only the template.
Requires staff.view and an operational or service-delivering membership in the selected venue. Returns contract_version: 1, basic identity, membership role/status/capabilities plus additive show_on_teacher_page and selectable_for_named_booking, and profile_owned_by_venue plus editable.identity/photo. Those two booleans are not writable on this identity PATCH; use GET|PATCH /admin/staff/{staffId}/public-booking-settings. Foreign-home collaborator email, phone, bio and specialties are omitted as null. No compensation, payroll, customer treatment records or new role grants. User JWT only; API keys and mixed credentials are rejected.
Parameters, scopes and examples
Required scopes
staff.view
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
PATCH/api/v1/admin/staff/{staffId}Bearer token
Update a venue staff profile
User JWT only; mixed credentials and API keys are rejected. Idempotency-Key is required and bound to the organization, actor, target and validated body. Concurrent/replayed requests do not repeat the mutation. No client notification is sent. Mutation errors add details.write_state: not_written, partial, saved or unknown. Only proven not_written releases the reservation; partial/saved/unknown retain it. Missing or malformed metadata on older responses is unknown. PROFILE_PARTIAL alone does not prove a save. Keep the same body/key for the same uncertain operation; explicitly reload and review authoritative state before choosing a new intent. A failed acknowledgement never replaces the known HTTP response. Requires staff.edit; protected capability changes additionally enforce settings.permissions, self/hierarchy and last-admin rules. Identity belongs to the home venue; collaborator role changes do not permit editing another venue’s personal profile. Returns the refreshed staff detail DTO. Role/capabilities use membership CAS, not new profiles.role values. Last-admin occupancy plus row CAS is not an organization-wide concurrency lock.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
At least one of first_name, last_name, phone, nickname, bio, specialties and role is required. Empty updates are rejected before claiming the key. No organization, compensation, photo URL, show_on_teacher_page or selectable_for_named_booking fields.
User JWT only; mixed credentials and API keys are rejected. Idempotency-Key is required and bound to the organization, actor, target and validated body. Concurrent/replayed requests do not repeat the mutation. No client notification is sent. Requires staff.edit and home-venue profile ownership. Returns upload_url, immutable path, content_type and max_bytes (5000000) for instructor-photos storage. Upload the original to the returned signed URL, then call finalize with the same path. This step does not change the profile photo.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
Supported content_type: image/jpeg, image/png or image/webp.
User JWT only; mixed credentials and API keys are rejected. Idempotency-Key is required and bound to the organization, actor, target and validated body. Concurrent/replayed requests do not repeat the mutation. No client notification is sent. Mutation errors add details.write_state: not_written, partial, saved or unknown. Only proven not_written releases the reservation; partial/saved/unknown retain it. Missing or malformed metadata on older responses is unknown. PROFILE_PARTIAL alone does not prove a save. Keep the same body/key for the same uncertain operation; explicitly reload and review authoritative state before choosing a new intent. A failed acknowledgement never replaces the known HTTP response. Requires staff.edit and home-venue profile ownership. Accepts the owned immutable upload path or explicit null to remove the photo, never an arbitrary URL. For uploads, the server checks object existence, size and JPEG/PNG/WebP magic bytes before an organization-scoped profile update. Returns photo_url, including null after removal; a missing, oversized or invalid original is rejected without a profile write. Web and JWT share persistence and cache/path invalidation.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
Required path: the exact path returned by upload-url, or null for explicit removal. Omitted, empty and unknown fields are rejected.
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Returns contract_version 1, scope ("own" | "all"), can_manage and records[] with appointment, service name, provider name, note, normalized formula, products_used, photo URLs, privacy/pin flags and timestamps. Optional limit (1–200, default 50).
Parameters, scopes and examples
Required scopes
treatments.view_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Body: optional appointment_id (must belong to this client at this venue; an own-scope caller must have performed it, else 403 NOT_OWN_APPOINTMENT), note, optional formula (color formula schema), products_used[{name, quantity?, unit?, sku?}], is_private, is_pinned. Returns 201 with the staff record.
Parameters, scopes and examples
Required scopes
treatments.record_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Returns the staff record or 404 outside the caller’s scope.
Parameters, scopes and examples
Required scopes
treatments.view_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Own-scope callers may change only records they authored (403 NOT_RECORD_AUTHOR); managers may change any. Same body fields as create, all optional.
Parameters, scopes and examples
Required scopes
treatments.record_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Requires treatments.manage; every other scope receives 403 FORBIDDEN. Audit-logged.
Parameters, scopes and examples
Required scopes
treatments.manage
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Own scope returns tests the caller performed. Returns result, product, performed_at, expires_at and validity_hours.
Parameters, scopes and examples
Required scopes
treatments.view_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Body: result (pass|fail|pending), product, notes, validity_hours (1–168, default 48), performed_at. Requires color_formula_v1 consent. Returns 201.
Parameters, scopes and examples
Required scopes
treatments.record_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
User JWT and staff.edit required; API keys and mixed credentials are rejected. Returns contract_version:1, organization_id, staff_id, show_on_teacher_page and selectable_for_named_booking for the selected venue membership. The two booleans are independent. Missing/null named-booking coalesces to true; a present non-boolean fails closed. Not appointment-policies and not a raw membership dump. Public booking enforcement remains a required Stage B companion; this read does not change availability or checkout. This dedicated companion is explicitly silent: it never sends Email, SMS or Push and does not accept notification fields. Full Role & access saves keep their explicit channel picker and notifications.send-before-write rule.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
User JWT and staff.edit required; API keys and mixed credentials are rejected. Strict body: at least one of show_on_teacher_page and selectable_for_named_booking as booleans. Idempotency-Key binds selected organization, actor, operation staff.membership_public_booking.patch, staff_id and the validated body. Known refusals add write_state:not_written and release the claim. Unconfirmed writes return HTTP 409 SAVE_OUTCOME_UNKNOWN with write_state:unknown and keep the claim. A known save may include refresh_failed:true. Keep the same key on unconfirmed outcomes and reload before a new intent. Does not write identity, role, compensation or appointment policies. Native Business authoring remains required before full parity. This dedicated companion is explicitly silent: it never sends Email, SMS or Push and does not accept notification fields. Full Role & access saves keep their explicit channel picker and notifications.send-before-write rule.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
At least one boolean. No organization, actor, notification, identity or appointment-policy fields.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
GET/api/v1/admin/rates/distributionBearer or API key
Get revenue distribution by rate
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication. The client and pass type must both belong to that venue.
Accepts exactly one credential: Bearer JWT with `rates.override`, or an API key bound to the venue with `write:passes`. Dual credentials are rejected before authentication.
DELETE/api/v1/admin/rates/override/{passId}Bearer or API key
Clear a pass rate override
Accepts exactly one credential: Bearer JWT with `rates.override`, or an API key bound to the venue with `write:passes`. Dual credentials are rejected before authentication.
Parameters, scopes and examples
Required scopes
write:passes
Path parameters
passIdstring · required
Pass id
Response example
{
"data": {
"ok": true
},
"error": null
}
GET/api/v1/admin/private-eventsBearer or API key
List private-event bookings
Accepts exactly one credential: Bearer JWT with `private_events.view`, or an API key bound to the venue with `read:private_events`. Targets are non-enumerating and venue-scoped.
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
Parameters, scopes and examples
Required scopes
write:private_events
Private-event booking. Every PS-B2 field below is OPTIONAL and additive: client_id / save_as_client (link or create the client the session is for), partner_id + billing_target/billing_address/billing_vat_number/po_number/department/cost_center (bill a company — the billing block prefills from the partner record), brand_id, location_id, staff_note (a message the client sees), pricing_override ({mode: per_person|total, amount} — total is VAT-inclusive), and start_mode (confirmed | inquiry | confirm_on_payment) with payment_due_at. The legacy `status` field keeps working.
GET/api/v1/admin/private-events/{id}Bearer or API key
Get a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.view`, or an API key bound to the venue with `read:private_events`. Targets are non-enumerating and venue-scoped.
PATCH/api/v1/admin/private-events/{id}Bearer or API key
Update a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
Parameters, scopes and examples
Required scopes
write:private_events
Path parameters
idstring · required
Booking id
Fields to update. PS-B2 adds the same optional fields the create route takes (client_id, partner_id + billing block, brand_id, location_id, staff_note, pricing_override, payment_due_at) plus `reprice` (recompute the frozen subtotal/VAT/total/deposit) and `notify_client` ({enabled, channels}). The response carries the client-visible change summary.
POST/api/v1/admin/private-events/{id}/approveBearer or API key
Approve a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
POST/api/v1/admin/private-events/{id}/cancelBearer or API key
Cancel a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
POST/api/v1/admin/private-events/{id}/quoteBearer or API key
Send a private-event quote
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
Accepts exactly one credential: Bearer JWT with `loyalty_price.manage`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
GET/api/v1/admin/loyalty-price/members/{memberId}Bearer or API key
Get a client loyalty-price context for a grant-capable operator
Accepts exactly one credential: Bearer JWT with `loyalty_price.grant`, or an API key bound to the venue with `write:passes`. Returns only the named active member’s programme label and standing; venue-wide configuration and aggregate counts remain manage-only.
POST/api/v1/admin/loyalty-price/grantBearer or API key
Give a client the loyalty price
Accepts exactly one credential: Bearer JWT with `loyalty_price.grant`, or an API key bound to the venue with `write:passes`. Requires a UUID `Idempotency-Key`; a replay is bound to the same venue, caller and grant payload. Writes through the context-free grant core (never a cookie action), so the audit trail and status recompute are identical to the admin web surface.
Parameters, scopes and examples
Required scopes
write:passes
user_id + a short reason. Optional venue-local expiry, and an optional pass_id with an agreed price on the catalog (MAJOR) scale — pass_price_override requires pass_id.
Request body
{
"user_id": "uuid",
"reason": "Agreed with the owner at the desk",
"expires_on": "2027-01-31",
"pass_id": "uuid",
"pass_price_override": 249
}
Staff with members.view_insights (the same read permission as the client's pass history; changing it needs passes.manage). The client's default pass at the caller's venue and every pass that could be chosen (active, past_due, pending_activation, paused) with is_default, usable and unusable_reason (clips_exhausted | expired | paused | past_due | not_eligible | not_found) for the venue-local today; a renewing membership whose current cycle is spent stays usable (it refills). 404 NOT_FOUND when the client is not an active member of the caller's venue.
Staff with passes.manage. Sets or clears (pass_id null) which pass the client's bookings at the caller's venue use first. The client must be an active member of the caller's venue (404 NOT_FOUND) and the pass must be that client's pass at the same venue (404 PASS_NOT_FOUND, including another venue's pass); non-selectable passes are 422 PASS_NOT_SELECTABLE. Audited as member.default_pass_set with the staff actor. The client is not notified.
Server-to-server exchange of a single-use short-lived handoff bound to browser nonce, state, destination, brand, venue and approved origin. Returns a read-only staff report token for httpOnly cookie storage. Existing BookingBible business login authorizes the handoff; no new password or email allowlist.
Parameters, scopes and examples
Exact handoff fields from the authorized business redirect; nonce remains in the site httpOnly cookie.
The caller's referral status for their active org: code, referred-friend count, conversions, rewards earned, and an anonymized (first-name + last-initial) per-referral list. Returns an empty summary when the `referrals` module is disabled.
Everything the caller currently owes in one read: renewing memberships in past_due/suspended with a failed renewal charge, overdue client invoices with a balance at venues that take card payments online (including the remaining balance of a partially paid invoice past its due date), and declined one-off charges. Each item carries its frozen ISO currency, amount (major) and amount_minor, a plain-language failure_message, a state_key for "dismiss until it changes", and the action to take (pay_renewal, retry_invoice or update_payment_method). A membership_renewal item also carries cards_on_file: the member's saved cards on the Stripe account the membership bills through (the only cards pay_renewal can charge), with is_subscription_card marking the card the membership charges today and is_default the customer's default; omitted when the cards could not be read. Totals are per currency. Read-only; never charges. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
The caller’s ten most recent declined charges (failure_code present), newest first, with description for banner copy. Prefer GET /api/v1/me/outstanding-payments, which de-duplicates renewals and names the pay action. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
POST/api/v1/me/invoices/{id}/retryBearer token
Pay my invoice on my saved card
Charges the saved default card for the balance still owed (total − amount_paid) on the caller’s sent, viewed, overdue or partially paid invoice (one charge per invoice at a time); a partially paid invoice is settled by paying the remaining balance and becomes paid. Optional body { expected_amount_minor }: the balance the client showed, in the smallest currency unit (use outstanding_balance_minor from GET /api/v1/me/invoices). The invoice is read again right before charging; if the balance no longer equals that quote the call returns 409 BALANCE_CHANGED and charges nothing — refetch and show the new amount. Returns status succeeded; processing (bank still settling — do not pay again); requires_action with client_secret and stripe_account for Stripe 3-D Secure (then call …/retry/confirm); failed with failure_code/decline_code; or paid with already_settled when an earlier payment already covered it. Settlement records the invoice payment like a Payment Link payment and deactivates the invoice's Payment Link. An open 3-D Secure attempt never outlives a balance change: any payment write on the invoice cancels open retry PaymentIntents (unless still for exactly the balance owed), and a challenge whose balance changed while it was being created is cancelled instead of returned (409 BALANCE_CHANGED). Staff payment writes share the per-invoice lock, so a charge started during one answers 409 PAYMENT_PENDING. A charge received but not yet applied returns status processing with applied: false (never paid; do not pay again), plus review_required: true when the venue must apply or refund it. Every refusal carries error.details.next_step (update_card | refresh | contact_venue | null) and a failed charge carries data.next_step — show only that action: update_card only for a real card decline, null for a processor hiccup (processing_error, try_again_later) or a failure without a reason. 422 ALREADY_PAID, NOT_RETRYABLE or NO_PAYMENT_METHOD (no usable saved card); 422 CARD_PAYMENTS_UNAVAILABLE when the invoice's venue cannot take card payments online (no charge is attempted; can_pay_now is false for such invoices); 409 PAYMENT_PENDING (an earlier payment is processing or another is in progress), BALANCE_CHANGED or RECONCILIATION_REQUIRED (an earlier payment no longer matches the balance; the venue is alerted); 400 VALIDATION_ERROR for a malformed body; 404 for an unknown, foreign or malformed id. Owner scoped, 5/min, audited. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
Optional. expected_amount_minor = the balance the client showed (outstanding_balance_minor); omit the body to charge the balance read when the request starts.
Body { payment_intent_id }. Verifies first that the PaymentIntent belongs to this invoice, venue and member (retrieved on the invoice venue's own accounts), then reports: paid (applied like a Payment Link payment), pending (only while Stripe is processing it; do not pay again) or failed — nothing was charged — with reason not_started (the bank check never ran, e.g. Stripe.js did not load; next_step null, the client may pay again), not_completed (the check ran and failed; failure_code is the processor's reason and next_step is update_card only for a real card decline) or cancelled (the platform stopped it because the amount owed changed; next_step refresh). Call it after the 3-D Secure step whatever the SDK returned. 422 PAYMENT_MISMATCH (not this invoice or not equal to the balance) or NOT_RETRYABLE; 409 RECONCILIATION_REQUIRED; 400 VALIDATION_ERROR; 502 RETRY_FAILED. Owner scoped, 5/min. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
The PaymentIntent id returned by POST /api/v1/me/invoices/{id}/retry.
Request body
{
"payment_intent_id": "pi_123"
}
GET/api/v1/me/venue-affinitiesBearer token
My venue affinities
Active public venues ranked by explicit favourite, then canonical pass, class-booking, and appointment signals. Returns counts and last activity; caller identity is server-bound.
Member-JWT, tenant-scoped same-attempt recovery for one already-created recurring membership. Requires the owned pending pass, its immutable checkout operation and disclosure, an exact matching incomplete Stripe subscription, an open unpaid first invoice, and a still-resumable existing PaymentIntent. The route returns the existing client_secret only; it never accepts price, selection, promotion, quote, or checkout attempt input, and never creates, cancels, reprices, or re-evaluates a sale. Paid, terminal, ambiguous, mismatched, or concurrently changed operations fail closed with 409. Confirm the returned existing PaymentIntent through POST /me/checkout/confirm, which answers a lapsed reservation with 409 OFFER_CONTACT_VENUE at the settle step (never OFFER_HOLD_EXPIRED) whenever fulfilment was refused, including while its refund is still pending or needs a manual touch — a lapsed reservation is terminal there regardless of where the refund itself has gotten to. This endpoint RENEWS the reservation on every call (rate-limited to 15/user) — it is not a passive re-read or a safe poll. Call it only when the buyer explicitly resumes or restarts a checkout, never on a timer to "keep the countdown fresh": each call extends the hold by another 3 minutes, so polling it would hold a place indefinitely. The response carries hold_expires_at (ISO 8601 UTC instant the renewed reservation lapses, or null when no offer hold applies) and server_time (ISO 8601 UTC, the server clock at response time), always together, alongside the still-valid client_secret. Compute the countdown once as hold_expires_at minus server_time and run it locally, never against the device clock. A renewal that fails because the reservation already lapsed and a restart could still succeed fails closed with 409 OFFER_HOLD_EXPIRED; one where restarting cannot work (capacity gone, the per-client limit reached, or a late charge already refunded) fails closed with 409 OFFER_CONTACT_VENUE, which never offers a restart and instead points the buyer to the venue desk. OFFER_SOLD_OUT does not apply here: this endpoint only ever renews a reservation that already existed, never opens a first one.
Idempotently removes only the authenticated member and requested venue pair.
Parameters, scopes and examples
Path parameters
organizationIdstring · required
Venue organization UUID
GET/api/v1/me/credits/balancesBearer token
My venue credit balances
Complete ledger-derived balances grouped by venue and currency. Each row carries `balance` (the venue ledger total), `available` (that total minus credit held by open checkout reservations, which is what checkout will actually spend) and `reserved`. Amounts are in major units. Consumer is account-wide; branded requests are fail-closed to x-organization-slug.
POST/api/v1/me/avatar/upload-urlBearer token
Create avatar upload ticket
Returns a caller-owned, MIME-bound storage path and two-hour signed upload URL for PNG, JPEG, or WebP up to 5 MB.
PATCH/api/v1/me/avatarBearer token
Finalize my avatar
Validates caller path ownership, metadata, size, and image magic bytes before deriving and saving the public URL.
DELETE/api/v1/me/avatarBearer token
Remove my avatar
Idempotently clears the profile reference and removes only the caller-owned canonical avatar object.
GET/api/v1/me/workspace-profileBearer token
My active venue operating profile
Server-authoritative Business-app profile for the active venue selected by X-Organization-ID. Returns booking_mode (classes, appointments, or both), business_type, resolved class/appointment operation gates, venue surface applicability, appointment access/counts, active_modules, and additive operations.appointments.creation_enabled (verified active basic/full staff creation) and historical_creation_enabled (also history permission plus the protected retrocreate executor). Missing creation flags are false; history_enabled preserves existing-row reads after downgrade/archive, separately from management_enabled and new creation. Intersect history with bookings.manage. reschedule_enabled is independently true only for verified active/wind-down fulfilment, including hidden verticals; missing is false. the resolved vertical_modules visibility map. business_type is informational and never used to infer booking_mode. Surface values are venue-level applicability; clients must still intersect them with the caller's effective permissions from GET /api/v1/me.
Current user profile with all active venue memberships and roles. Each membership carries `permissions: string[]` (the caller's OWN effective permission keys for that org — per-user overrides applied over role/capability defaults, resolved identically to requireApiPermissionWithDefaults) and `capabilities: string[]` (the membership capability set, surfaced for every membership). To bound per-request cost in this multi-tenant app, `permissions` is resolved for the ACTIVE org only (top-level `permissions_scope: "active_org"`; non-active memberships carry `[]`) — mobile refetches /me on org switch. Workspace ownership is server-projected as `is_individual`, `is_owned`, `is_workplace`, `is_relationship`, `is_selectable`, and an explicit `workspace_group` (`owned`, `works_at`, `member_venues`, or `relationships`). Accepted role-bearing employer memberships remain selectable in Business under “Works at”; member-only Network relationships do not. Business clients must only put selectable rows in their workspace picker. Gates UI on these instead of discovering denials via 403s. A PATCH /admin/permissions/user/{userId} is reflected within ≤60s (permission-cache TTL). Caller's own permissions only. See docs/api/ME_PERMISSIONS_CONTRACT.md.
POST/api/v1/me/active-organizationBearer token
Switch my active workspace
Authoritatively switches the caller to an active, selectable workspace. When the caller owns an individual professional venue, accepted role-bearing employer memberships remain selectable; only non-operational/member-only relationships return WORKSPACE_NOT_SELECTABLE. The response includes effective permissions for the selected workspace so native role gating is safe immediately.
Resolved feature-module map for the caller's active org (C07): `{ <module_key>: { enabled, source, tier?, settings? } }` — the same four-tier resolution (plan → group → venue → tenant) the admin sees at /admin/features. Also includes `professional_collaborations`, which reflects the platform-wide teacher-settlements rollout independently of the venue-to-venue `network` plan gate. Drives every <FeatureGate> in the branded mobile app. Multi-membership callers must send X-Organization-ID; without it the map resolves empty (all off).
GET/api/v1/me/minimalPublic
Minimal auth check
Cross-origin auth check for venue marketing sites. Returns { logged_in, first_name, venue_id, preferred_brand_id } — or logged_in=false when no session. CORS is gated by the venue/brand embed_allowed_origins allowlist; unknown origins get no CORS headers (treated as "not logged in" by the caller).
PATCH/api/v1/meBearer token
Update profile
Update profile fields. Cannot modify role, balance, or org membership.
Create an account-local Stripe SetupIntent plus matching Customer/ephemeral-key credentials. Requires an Idempotency-Key header. Optional expected_renewals:[{pass_id,payment_id}] freezes the complete original renewal set after member, venue, payment and billing-account validation; omitted retains legacy behavior, [] pins only. The normalized target list is bound to the key and durable SetupIntent metadata, so a later debt cannot be substituted. An unknown outcome requires checking the same operation. The response freezes the server-owned venue country and exact Connect account for native Payment Sheet initialization.
Member self-service cannot detach saved cards. This endpoint returns PAYMENT_METHOD_REMOVAL_NOT_ALLOWED; add a replacement card or contact venue staff instead.
Promote a saved card to the Stripe customer default (invoice_settings.default_payment_method; default_source for a legacy card_ source). Optional expected_renewals:[{pass_id,payment_id}] is the complete frozen debt target set; omitted retains legacy fanout, [] pins without collection. An explicit list requires Idempotency-Key. Targets must belong to the actor, venue, pass and original billing account before a provider update. The key binds the normalized list as well as caller, venue and card; a changed list returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. An explicit unknown/foreign venue slug or stale active-venue pointer is 404 ORG_SCOPE_INVALID; a scope read failure is 503 ORG_SCOPE_UNAVAILABLE. A profile row with an explicit null active-venue pointer permits a platform-wallet default with no automatic membership collection; a missing or malformed profile is unavailable. A provider-ambiguous or post-provider failure is 503 DEFAULT_CARD_OUTCOME_UNKNOWN: read the current default and outstanding payments; the same key stays pending while its outcome is unknown. A proven no-effect failure releases the key for retry. GET /me/payment-methods then returns is_default:true on the matching row (PAY-P3.1). The response lists memberships[] with pin and original renewal outcomes, including payment_id when collected. A requires_action renewal can include client_secret and stripe_account for the existing attempt; complete that challenge then confirm settlement without another collection request. Contract: docs/api/MEMBER_SELF_PAY.md §2d.
Setup-only flow for Stripe Payment Sheet (PAY-P1.1). Requires an Idempotency-Key header. Returns customer_id, ephemeral_key, setup_intent_client_secret, and apple_merchant_id in the exact SetupIntent home account: connected only in direct mode, otherwise platform. Use when collecting a saved card before any purchase.
Register an Expo push notification token for iOS/Android/web. app_variant is required so member, branded-venue, and staff deliveries cannot cross application boundaries. Branded tokens require an explicit venue the member belongs to (X-Organization-Slug, or a member-validated X-Organization-ID; a conflicting pair is refused) and never use the profile's active venue, otherwise 400 ORG_REQUIRED. Business tokens require a validated organization context. A re-registered branded or business token is moved to a different venue only when that venue is named explicitly.
Deactivate the authenticated user's token or device before logout. The token/device selector is sent in the JSON body.
Parameters, scopes and examples
At least one token or device_id is required
Request body
{
"device_id": "installation-uuid"
}
GET/api/v1/me/notificationsBearer token
Notification history
Cursor/page-paginated email, SMS, push, and in-app history. Rows include source-aware `data`, `read_at`, and `app_variant`; X-App-Variant filters app-specific inbox events, while X-Organization-Slug narrows branded clients to their venue.
Self-scoped read marker. Idempotency-Key is required; another user’s row returns 404.
Parameters, scopes and examples
Path parameters
idstring · required
Notification id
POST/api/v1/me/notifications/read-allBearer token
Mark notifications read
Marks all of the caller’s unread rows read. X-Organization-Slug narrows a branded client to its exact venue; otherwise Consumer marks its cross-venue inbox. Idempotency-Key is required.
Returns the canonical ten-category catalog with effective email/SMS/push defaults, frequency caps, and per-member quiet hours for the active/requested organization.
Upserts canonical category toggles/frequency caps and quiet hours. Unknown categories are rejected and every database failure is returned; Idempotency-Key is required.
GET/api/v1/me/paymentsBearer token
List my payments
Cursor-paginated receipt-bearing payment ledger for the caller. Pending and failed attempts are excluded; successful, refunded, partially-refunded and disputed originals remain available with their payment receipt. An App Store purchase is shown at the price the member paid Apple, with `receipt_source: "apple"` (Apple issues that receipt).
Streams the receipt PDF (application/pdf) for one of the caller's payments — branded merchant header, line items, VAT breakdown, totals. Cached in storage after first render. App Store purchases return 409 APP_STORE_RECEIPT: Apple issues their receipt.
Emails the venue-branded receipt PDF for one of the caller's own payments to the address already on file for their account — the same document served by the PDF download. No recipient field exists; any caller-supplied recipient is ignored. Idempotency-Key is required; a retried key replays the cached result instead of re-sending. App Store purchases return 409 APP_STORE_RECEIPT.
Parameters, scopes and examples
Path parameters
paymentIdstring · required
Payment id
Empty body — the request is never read.
Request body
{}
GET/api/v1/me/refundsBearer token
List member refund receipts
Owner-scoped successful refund operations, cursor-paginated and optionally restricted by the branded organization slug. Split-tender operations are returned once with a signed negative amount. Apple’s refunds of App Store purchases show the member’s own price, `receipt_source: "apple"` and no `receiptPath`.
Parameters, scopes and examples
Query parameters
limitnumber
Page size (default 20, max 100)
afterstring
Opaque cursor from a previous page
GET/api/v1/me/refunds/{refundId}/pdfBearer token
Refund receipt PDF
Owner- and venue-scoped canonical refund receipt PDF. Non-final, sibling, cross-member and cross-venue refund ids return a uniform not-found response. Apple’s refunds of App Store purchases return 409 APP_STORE_RECEIPT.
Parameters, scopes and examples
Path parameters
refundIdstring · required
Refund id
GET/api/v1/me/loyaltyBearer token
Loyalty balance + history
The caller's org-scoped loyalty point balance plus a recent per-event history slice. Full paginated history is on /api/v1/me/loyalty/points.
GET/api/v1/me/loyalty/pointsBearer token
Loyalty points history
Cursor-paginated per-event loyalty point ledger for the caller.
GET/api/v1/me/streakBearer token
Attendance streak
Current + longest attendance streak, freezes remaining, and at-risk flag.
GET/api/v1/me/rewardsBearer token
Redeemable rewards catalog
Active loyalty rewards for the caller's org with affordability (is_locked) computed against the caller's balance.
POST/api/v1/me/rewards/redeemBearer token
Redeem a reward
Redeem a loyalty reward. Idempotency-Key supported; audited.
Parameters, scopes and examples
Redemption
Request body
{
"reward_id": "uuid"
}
POST/api/v1/feedbackBearer token
Submit feedback & tip
Rate a class (1-5 stars), leave a comment (optionally `anonymous`), and optionally tip the instructor via Stripe. The tip carries its own `anonymous` flag. The tip block of the response returns `client_secret`, `customer_id`, `ephemeral_key`, and `stripe_account_id` (non-null only in DIRECT charge mode).
Self-scoped class/appointment review and tip eligibility. Organization, target, settings, MobilePay capability, and prompt decision are server-derived from the owned source. Reads are side-effect-free unless `claim_prompt=true` is explicitly supplied by a prompt-mode entry check.
Parameters, scopes and examples
Query parameters
source_typestring · required
class or appointment
source_idstring · required
Owned booking id (class) or appointment id
claim_promptboolean
Reserve an in-app prompt only when true
POST/api/v1/post-attendance/reviewsBearer token
Submit class or appointment review
Creates one source-aware review after server-authoritative attendance/settings checks. Idempotency-Key required. `professional_rating`, tags, recommendation, anonymity, moderation, recipient notification, analytics, and webhooks are venue-controlled.
POST/api/v1/tipsBearer token
Tip a professional (no review)
Create a class or appointment tip in major currency units (`amount: 20` means DKK 20). Organization, professional, currency, Stripe account, and available methods are server-derived. Customer + ephemeral key are optional: customerless PaymentSheet still supports adding a card. MobilePay is returned only for verified Danish/DKK/venue-capable configurations. Idempotency-Key required.
Authenticated tipper-only reconciliation after PaymentSheet/MobilePay/3DS returns. Retrieves the server-owned PaymentIntent in its frozen Stripe account namespace, validates amount/currency/metadata, and emits receipts only after Stripe reports succeeded. Idempotency-Key required.
Parameters, scopes and examples
Path parameters
idstring · required
Tip id
GET/api/v1/tips/{id}Bearer or API key
Tip status
Poll a tip's status after confirming its PaymentIntent (incl. MobilePay / 3DS redirect returns). Access: the tipper (JWT), an org admin/manager (JWT), or an org-scoped API key. Cross-user / cross-tenant reads return 404.
Submit an Art. 15/16/17/20/21/22 request (access, erasure, portability, rectification, objection, art22 review). 30-day SLA. For erasure, account access is disabled immediately and the response reports erasure_status=pending_fulfillment; a super-admin performs the guarded erasure cascade within the SLA, while the SLA cron only alerts. Statutory records may be anonymised and retained for their legal period. Idempotency-Key required.
Parameters, scopes and examples
DSR request
Request body
{
"kind": "access",
"details": "Please send all data you have on me."
}
Re-trigger the guardian verification email for the caller's outstanding parental-consent request (C06). Matched by the authenticated email — no enumeration. Rotates the token and refreshes the 7-day expiry on the existing pending row (never a duplicate request). Empty body; Idempotency-Key supported; throttled 3/min per IP + 5/hr per user.
GET/api/v1/me/consent-statusBearer token
Active consents
Latest consent record per type for the authenticated user, with marketing decisions kept separate by organization_id. An explicit organization_id query or X-Organization-ID header requires active venue membership and must agree with the branded slug; foreign and platform-wide marketing rows are excluded. is_active is fail-closed and true only when the grant is unwithdrawn and policy_version matches the server-canonical current_policy_version; stale grants return requires_reacceptance=true.
Parameters, scopes and examples
Query parameters
organization_idstring
Exact active venue for marketing consent decisions
GET/api/v1/me/consentBearer token
Current native consent state + venue requirement
The venue's photo/video consent requirement (when organization_id is given) plus the caller's version-aware state for legal, marketing, analytics, photo/community and health-questionnaire consent types. Health consent is returned only for the exact membership-verified venue, with the active policy prompt_text for explicit native checkbox capture. A stale policy version is inactive and requires reacceptance.
Parameters, scopes and examples
Query parameters
organization_idstring
Resolve exact active venue marketing/health consent and photo-consent requirement; must match organization headers
POST/api/v1/me/consentBearer token
Capture native consent
Grant or withdraw one supported legal, marketing, analytics, photo/community or health-questionnaire consent for the caller. Health consent requires an explicit membership-verified organization_id, records the current server policy text with checkbox evidence, and a rejection closes prior health grants for that venue. Marketing decisions also require an exact venue. Other venue IDs require active membership. Grant versions are server-canonical; an unavailable policy returns 503 without writing.
The caller's health-questionnaire completion timestamp (completed_at, null when never submitted). Pre-check for the mobile hot-yoga booking gate.
POST/api/v1/me/health-questionnaireBearer token
Submit health questionnaire (Art. 9)
Submit the spa/hot-yoga health questionnaire for the caller's active org. Runs the Art. 9 contraindication consent gate, inserts a health_questionnaires row (plaintext responses; encrypted at rest by cron), stamps profiles.health_questionnaire_completed_at so the booking gate clears, and writes audit_log/user_events. Requires an Idempotency-Key (a double submit replays). Org resolved via X-Organization-ID / active membership.
Read the caller's external calendar-feed state: { token, enabled, generatedAt }. token is the opaque secret embedded in the public .ics feed URL (null when no feed is provisioned).
Enable the caller's external calendar feed and return the token. Idempotent — an existing token is returned unchanged (never rotated); a new one is minted (256-bit, base64url) only when absent. Empty body. Audited (calendar_feed_token_generated).
Revoke the caller's calendar feed: clears the token and disables the feed (the public feed then 404s). Empty body. Audited (calendar_feed_token_revoked).
Parameters, scopes and examples
Response example
{
"data": {
"enabled": false
},
"error": null
}
GET/api/public/calendar-feed/{token}Public
Public calendar feed
UNAUTHENTICATED — the opaque token in the path IS the credential. Returns one user's bookings as JSON for an external calendar subscription: { bookings, cancellations, userId, generatedAt }. bookings are upcoming events for the next 90 days; cancellations are bookings cancelled in the last 7 days (so calendar apps emit STATUS:CANCELLED). 404s on an unknown or disabled token (indistinguishable). Scoped strictly to the token's single user — no other user's data. 60 req/min per token.
Venue-scoped browser checkout adapter over the canonical pass purchase engine. Requires member JWT, x-organization-slug and Idempotency-Key. Accepts pass_type_slug, optional expected_pass_type_id from the original authenticated preview, start_date, selection_kind/quantity/option_id, addons, credit_amount and the complete flexible_quote for Flexible passes. A mismatched expected_pass_type_id returns 409 CHECKOUT_PRODUCT_CHANGED before pricing, promotion or purchase effects; omitted identity keeps legacy behavior. Optional confirm_saved_card:true is reserved for the buyer's explicit Pay action; omitted/false prepares an intent without off-session saved-card confirmation. Prospective checkout_operation_version:1 requires expected_pass_type_id and a fresh checkout:v1:<UUIDv4> key; never upgrade an old attempt. An existing versioned attempt returns CHECKOUT_OPERATION_RESOLVING and must use read-only original-attempt confirmation, never mint replay. The initial bounded free/account-credit fixed-pass cohort retains durable completion; unsupported initial configurations keep existing behavior. Exact retries keep the original body and key. Optional flash_sale_id selects a direct offer and is mutually exclusive with promo_code; fixed direct offers require the complete preview direct_purchase_quote. Sale identity, eligibility, lifecycle, promotion, caps, product and signed reviewed pricing are revalidated server-side before a new payment. Unavailable direct offers return FLASH_SALE_UNAVAILABLE; changed or expired proof returns QUOTE_* without falling back to normal price. Generic requests without flash_sale_id retain existing behavior. Returns the existing browser intent_type, client_secret, payment_intent_id, provider account/customer and canonical pricing envelope. Additive pass_type.id identifies the resolved product. purchase_confirmation is null unless canonical no-PI completion is proven; a proof contains confirmed:true, pass_id, pass_type_id and checkout_attempt. Payment status alone never proves fulfilment. Retain the original preview pass_type_id and attempt before dispatch. A recurring migrated legacy card cannot be prepared without an explicit Pay action: 409 SAVED_CARD_PAY_ACTION_REQUIRED occurs before subscription creation. When the checkout opens or re-opens an offer hold, the response also carries hold_expires_at (ISO 8601 UTC instant the reservation lapses, or null when no offer hold applies) and server_time (ISO 8601 UTC, the server clock at response time), always together, next to client_secret. Compute the countdown once as hold_expires_at minus server_time and run it locally, never against the device clock; at zero, disable the pay step and stop any confirm from starting. A lapsed hold that can still be retried returns 409 OFFER_HOLD_EXPIRED; a lapsed hold with no way to restart (capacity gone, the per-client limit reached, or a late charge already refunded) returns 409 OFFER_CONTACT_VENUE, which never offers a restart and instead points the buyer to the venue desk. A first attempt with no reservation ever held, against an offer that is already fully booked, returns 409 OFFER_SOLD_OUT with plain copy and no charge attempted. The response also carries an additive subscription_id (nullable), so a client replaying this same request body later can re-read the countdown on a recurring reservation without opening a second checkout. A recurring membership whose first period is free today (a 0 kr offer or partner code, a free trial, a first cycle fully discounted) and that has no card on file returns intent_type setup with a SetupIntent client_secret (or a payment client_secret when a registration fee is due): nothing is activated and no offer place or code is consumed until that card step succeeds. Confirm it, then call POST /me/checkout/confirm. Abandoning it leaves the code unused; the same member can start a new checkout with the same code. A declined saved-card first invoice returns 402 INSUFFICIENT_FUNDS when the original Stripe charge proves that issuer reason; otherwise it returns 402 PAYMENT_FAILED.
Parameters, scopes and examples
Optional flash_sale_id and the exact flexible_quote or fixed direct_purchase_quote are forwarded unchanged from preview; client amounts never authorize a charge. Exact retained HTTP receipts replay the original payment status after offer expiry, without new provider work. A missing or pruned receipt still requires current offer validation.
Buyer-facing canonical PricingBreakdown with additive pass_type_id to retain before purchase — net/VAT split, registration fee, total today + recurring, localized policy terms, the start-date window, the required legal artifacts (with already_signed), and the buyer's saved signatures. NO charge. Member-JWT + stable x-organization-id (preferred) or legacy x-organization-slug, both membership-scoped. Query: pass_type_slug (required), start_date, binding_months, locale (en|da). A selected binding tier is validated and priced server-side; unavailable tiers return 422. Optional flash_sale_id (UUID) selects a direct venue offer and is mutually exclusive with promo_code. The server resolves the linked promotion and checks venue, lifecycle, eligibility, caps and product compatibility; unavailable offers return 422 FLASH_SALE_UNAVAILABLE, never a normal-price fallback. Flexible direct-offer quotes add flash_sale_id, flash_sale_rule_fingerprint and purchase_obligation_fingerprint; pass the complete flexible_quote unchanged to purchase. One-time direct offers are fixed-price products only: the existing compatibility policy continues to reject Flexible class-pack and time-pass flash sales. Direct offers add purchase_obligation {status:available} for canonical one-time purchases and supported non-deferred introductory memberships. minimum_total_payable is the all-tender contractual minimum in minor units. Recurring disclosures include intro_through_date inside purchase_obligation. Immediately reachable self-service notice retains earliest_cancellation_effective_on. Staff-managed cancellation instead includes purchase_obligation.cancellation {kind:conditional_contractual_minimum, request_method:contact_studio, condition:timely_valid_notice, earliest_possible_end_on} and omits earliest_cancellation_effective_on. Clearly display that both the minimum and earliest possible end depend on timely valid notice; neither is confirmation of an actual cancellation, receipt time or staff response SLA. Aligned trial schedules and exact first/whole-cycle coupon schedules use canonical renewal and commitment rules. Deferred, inexact coupon cadence, immediate-cancellation or unreadable policy contexts return {status:unavailable, reason_code, message} without purported exact facts. One-time purchases omit cancellation facts. Both signed quote authorities bind all facts and policy/anchor inputs; changed authority is refused before new payment side effects. Existing accepted purchase recovery keeps its original snapshot. Fixed direct offers instead return direct_purchase_quote {version:1, organization_id, user_id, pass_type_id, flash_sale_id, authority_fingerprint, credit_applied_minor, payable_minor, issued_at, expires_at, fingerprint}. This five-minute signed proof binds the reviewed canonical price, commercial terms and exact account-credit/cash split. Both tender fields are nonnegative integer minor units; payable_minor equals charged_today. A changed credit balance or tender requires a new review (QUOTE_STALE), never a larger cash charge. Direct gift cards are not supported unless quoted; generic gift-card checkout is unchanged. Send the proof unchanged to purchase. Optional direct_offer {flash_sale_id, name, normal_amount, offer_amount} is display metadata for fixed one-time offers (amounts in minor units); charged_today is the payable authority. Deprecated for promo codes: a promo_code in the query string lands in logs and referrers; send it with POST /api/v1/me/checkout/preview instead.
Parameters, scopes and examples
Query parameters
pass_type_slugstring · required
Venue pass slug
flash_sale_idstring
Optional direct-offer UUID; mutually exclusive with promo_code
promo_codestring
Deprecated here (URLs land in logs): send it with POST instead
POST/api/v1/me/checkout/previewBearer token
Checkout pricing preview (parameters in the body)
Identical to GET /api/v1/me/checkout/preview — same auth, rate limit, validation and response — with the parameters as a flat JSON object instead of the query string, so a promo code never travels in a URL. Use this whenever promo_code is sent.
Alternatively provide checkout_attempt and the original preview pass_type_id for DB-read-only completion reconciliation: confirmed:true includes purchase_confirmation {confirmed:true,pass_id,pass_type_id,checkout_attempt}; missing or expired proof returns confirmed:false,purchase_confirmation:null,payment_state:resolving. This branch performs no purchase replay or provider work and does not authorize a retry. Free-pass recovery currently requires a retained original response proof; durable raw-attempt mapping remains incomplete. Confirm the exact existing PaymentIntent or deferred SetupIntent for an authenticated venue member. A successful client-side payment is not proof of pass fulfilment. The canonical finalizer can return 409 CUSTOMER_ELIGIBILITY if admission is refused or unresolved after settlement. Preserve the original purchase operation and show a neutral resolving outcome; do not start a replacement payment. Other existing 409 outcomes remain distinct. For a free-start membership (setup_intent_id), this call activates the membership and redeems its offer, exactly once together with the setup_intent.succeeded webhook; a SetupIntent that has not succeeded yet (for example still in 3-D Secure) returns 409 PAYMENT_NOT_COMPLETED and activates nothing. If the offer place can no longer be granted it returns 409 OFFER_HOLD_EXPIRED (start again; nothing was charged and the code was not used) or 409 OFFER_CONTACT_VENUE, and an attempt that was already closed returns 409 MEMBERSHIP_SETUP_EXPIRED.
Parameters, scopes and examples
Exactly one payment_intent_id, setup_intent_id, or checkout_attempt paired with original pass_type_id
Records a waiver / ToS / privacy / contract acceptance with IP + user-agent + version + signature. Idempotent on (user, document, version); a stale version → 409 force-refetch; a minor (DOB < 18) → 409 + parental consent. Supports saved-signature reuse (saved_signature_id) honouring signature_kind. Linked contracts require pass_type_slug and may include start_date; the endpoint idempotently creates/adopts the exact current-version pre-purchase contract before signing.
Read-only payment snapshots retained during a brand split or transfer. These records are separate from the live payment ledger and do not support refunds, receipt resends, invoice actions or revenue totals. Each row retains its original amount in major currency units, ISO currency, source status, occurrence time and snapshot time. Archive IDs are not payment IDs. Pagination sorts by original occurred_at and archive ID; reuse the exact opaque cursor with the same venue and member. Invalid pagination returns 400; unavailable or unverified history returns 503 rather than an empty history. Always scoped to the authenticated member. Optional X-Organization-Slug restricts the collection to one venue; an unknown slug returns no rows, never a global fallback. Without that header, the collection includes this member’s archived history across venues.
Parameters, scopes and examples
Query parameters
limitnumber
Page size: 1–100, default 25.
afterstring
Opaque next_cursor from the same archive collection and scope.
POST/api/v1/me/checkout/recoveryBearer token
Inspect an original product-package or paid service purchase
Read-only, non-minting recovery for an active authenticated venue member. Requires x-organization-slug and the exact original purchase key. kind product_package reads the canonical product-package operation; kind service reads the paid appointment operation. Returns frozen amount_minor/currency and durable/provider status, never client secrets. unknown/read errors are distinct from an authoritative not_found; unbound creation stays pending and provider success stays pending until durable fulfillment. Every outcome retains the original key; not_found is a point-in-time read and never authorizes a replacement financial operation. Passes, recurring products, bundles and staff POS are unsupported.
Returns the authenticated Namasté Online member’s Apple-billed pass status and exact expiration. A null result means this account has no linked Apple purchase.
POST/api/v1/me/apple-subscriptionBearer token
Link a verified Apple subscription
Verifies a StoreKit signed transaction against Apple’s certificate chain and binds its appAccountToken to the authenticated Namasté Online member. Replays update the same pass. A Production purchase is also recorded once as an App Store sale at estimated net proceeds; Sandbox purchases never are.
Parameters, scopes and examples
StoreKit 2 signed transaction JWS
Request body
{
"signed_transaction": "<Apple signed transaction JWS>"
}
GET/api/v1/me/entitlementsBearer token
My entitlements
The caller's entitlement matrix for one venue: can_book_physical (any active pass with grants_in_person), can_watch_online (grants_online_class_access), online_only, bookable_class_type_ids ("all" when any usable pass is unrestricted), and an active_passes[] summary (slug, category, grants, validity, clips, allowed_brand_ids). Optional brand_id OR class_instance_id scopes the capability flags to passes valid for that brand / occurrence (effective brand ∪ class-type brand ∪ "Also show under…" brands) and is echoed as brand_scope. Errors: 400 INVALID_BRAND_SCOPE (both, or a non-UUID), 404 BRAND_NOT_FOUND / CLASS_NOT_FOUND, 500 BRAND_LOOKUP_FAILED (brand read failed), 503 PASS_RESTRICTION_UNVERIFIED (pass restrictions unreadable; retry). The booking engine enforces the same matrix; booking refusals include PASS_BRAND_RESTRICTED and CLASS_TYPE_RESTRICTED (422).
Parameters, scopes and examples
Query parameters
organization_idstring · required
Venue to resolve entitlements for
brand_idstring
Optional brand (UUID, this venue): flags for classes owned by that brand
class_instance_idstring
Optional class occurrence (UUID, this venue): flags under the booking engine brand scope
Cursor-paginated list of the caller's member-visible client invoices. Drafts are excluded and every row includes an authenticated document_path for the print-ready HTML invoice. Every row also carries outstanding_balance (the balance still owed on an open invoice, 0 once paid or closed, major units of the invoice currency), outstanding_balance_minor (the same in the smallest unit) and can_pay_now (true when POST /api/v1/me/invoices/{id}/retry accepts it: sent, viewed, overdue or partially paid with a balance, at a venue that can take card payments online) and pay_now_unavailable_reason (card_payments_unavailable when an owed balance cannot be paid online — tell the client to contact the venue — else null). Show "Pay remaining balance" when amount_paid > 0 and pass outstanding_balance_minor as expected_amount_minor.
Owner-scoped detail for one issued client invoice, including line items and the totals breakdown (subtotal, discount, VAT, total, amount_paid), plus outstanding_balance, outstanding_balance_minor, can_pay_now and pay_now_unavailable_reason (same meaning as on GET /api/v1/me/invoices).
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
GET/api/v1/me/invoices/{id}/documentBearer token
Invoice print document
Authenticated owner- and venue-scoped print-ready HTML for one member-visible invoice.
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
GET/api/v1/me/guest-invitesBearer token
Can I bring a guest to this class?
GUEST-INVITE-01 — whether the caller's passes qualify them to host a guest at this class, the venue guest price, the standard single-class price to strike through (compare_at_price, display only), and any invitations they already have open for it. `reason` is plain-language copy safe to render verbatim when `eligible` is false. For the two Vibro brands, a booked host also receives `age_band_prices` for a separate one-class guest sale.
Creates the invitation plus its pending guest seat (a GUEST-PAY-01 `pending_payment` booking that holds NO capacity until paid). `payer:'guest'` returns the link to share; `payer:'host'` additionally returns a Stripe Checkout URL (saved card, new card, or MobilePay). `return_base_url` must be an allowlisted host or it is ignored. Vibro Yoga and Vibration Shower require a host-owned class booking, `payer:host`, and an `age_band` of `u30` or `o30`; U30 also requires `guest_date_of_birth`, and Vibro Yoga requires `guest_address`.
For a pending host-paid guest invitation, returns the existing or idempotently prepared PaymentIntent so a brand site can reconcile an uncertain MobilePay or card return. A paid invite returns status `paid`. The caller must be the original host in the same venue.
DELETE/api/v1/me/guest-invites/{id}Bearer token
Withdraw a guest invitation
Withdraws an UNPAID invitation and releases its pending seat. A paid guest spot is a real booking — cancel it through the normal booking cancellation path so the venue's refund and fee rules apply (409 `ALREADY_PAID`).
GET/api/v1/guest-invites/{token}Public
Resolve a guest invitation (public)
GUEST-INVITE-01 — the invitation landing page a friend opens. Anonymous-allowed by design (the token is the capability); returns who invited them, the class, the price and the struck-through standard price, and nothing else about the host's account. `state` is `needs_account` for a signed-out visitor, `payable` once signed in, plus `already_paid` / `cancelled` / `expired` / `class_started` / `class_full`.
POST/api/v1/guest-invites/{token}/checkoutPublic
Pay a guest invitation without an account
GUEST-INVITE-01 — the Guest Visitor branch. ANONYMOUS-ALLOWED (the token is the capability): the invited friend pays without creating an account and receives a Stripe Checkout URL. Deliberately does NOT claim the seat, so no profile is created and `bookings.user_id` stays the host. Confirmation is still the verified-payment webhook. Trade-off the calling site MUST surface: with no login, only the host or the venue can cancel it afterwards. Refuses with 409 `ALREADY_CLAIMED` once someone has linked the invitation to an account.
GUEST-INVITE-01 — the class filled up before the invited friend accepted. ANONYMOUS-ALLOWED. An unpaid invitation never held a seat, so this is a normal outcome, not an error: the friend joins the waiting list and is NOT charged. If a spot opens, `reinviteWaitlistedGuests` sends a fresh payment link. Returns `{ position, already_on_waitlist }`.
The invited friend, now signed in, takes ownership of the guest seat and gets a Stripe Checkout URL. Claiming rebinds `bookings.user_id` to their profile (the host stays on `host_user_id`), which is what makes the spot appear in their own bookings and cancellable by them under the venue's ordinary cancellation rules. Capacity is still only taken by the verified-payment confirm RPC.
Confirmed pilot member and active scoped viewer session required. Stable event IDs dedupe selection, attempt, player progress, stop and error observations. Actual output is not inferred.
List authenticated user's bookings across all venues, or only the venue named by X-Organization-Slug when that header is sent (an unknown slug, or an organization_id naming another venue, returns an empty list). Supports cursor and offset pagination. Active upcoming bookings carry `cancellation_terms` (CANCEL-PREVIEW-01 v1.1): that booking’s effective window (`source` course | location | brand | venue), its late and no-show fee and `pass_effect_if_late` for its pass, by the same rule as GET /api/v1/me/bookings/{id}/cancellation-preview; null for cancelled, past or imported rows and when it cannot be read.
Venue-local YYYY-MM-DD start of a pass usage cycle (inclusive). Requires organization_id.
cycle_endstring
Venue-local YYYY-MM-DD end of a pass usage cycle (exclusive). Requires organization_id.
afterstring
Cursor for pagination
limitinteger
Items per pageDefault: 20
POST/api/v1/bookingsBearer or API key
Book a class
Create a booking. Validates pass eligibility, capacity, booking window, daily limits, and class restrictions. A venue daily-limit refusal is DAILY_LIMIT_REACHED with details { daily_limit, daily_used, daily_limit_scope, workshops_count, course_sessions_count } and a message naming what does not count; a pass per-day cap refusal is CLASS_LIMIT_EXCEEDED with details { pass_daily_limit, pass_daily_used, workshops_count, course_sessions_count }; if a pass per-day cap cannot be checked the booking is refused with 503 DAILY_LIMIT_UNVERIFIED (nothing booked; retry). Supports idempotency via Idempotency-Key header. Default pass: when the member (JWT) has chosen a default pass (PUT /api/v1/me/default-pass) that is used up or cannot be used for this class and another pass can, the response is 409 DEFAULT_PASS_UNUSABLE with details { default_pass: { id, name, reason: clips_exhausted|expired|paused|past_due|not_eligible|not_found }, suggested_pass: { id, name, remaining, end_date }, choices }. Retry with pass_id = suggested_pass.id plus switch_default: true (book and make it the default) or use_once: true (book, keep the default). switch_default and use_once are mutually exclusive and require pass_id; API-key bookings never receive this prompt. With no default the existing order applies silently; the booking response names the pass used (passes.pass_types.name). Waiver: when the class type requires one, an unsigned member gets 400 WAIVER_REQUIRED; if the signature check cannot run the booking is refused with 503 WAIVER_CHECK_UNAVAILABLE (nothing booked; retry). The response carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.2): the new booking’s effective cancellation terms at booking time, from the same rule as GET /api/v1/bookings/{id}; null when none apply.
Retrieve a single booking with class details, pass info, and check-in status. Optional X-Organization-Slug restricts access to that venue; unknown or mismatched venue and another member return 404 NOT_FOUND before effects. No header preserves account-wide member ownership. An active upcoming booking carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.1) — its effective window, fees and `pass_effect_if_late`, by the preview’s rule; null otherwise.
Parameters, scopes and examples
Required scopes
read:bookings
Path parameters
idstring · required
Booking ID
POST/api/v1/bookings/buddyBearer token
Invite a client to the same class
Creates and emails a venue-branded invitation to an existing active client at the same venue. The inviter must already have a confirmed booking. The recipient books with their own pass or payment; no guest funding or inviter entitlement is used. Idempotency-Key supported.
Accepts a buddy invitation for the authenticated recipient and books the same class with that recipient’s own eligible pass or normal venue booking rules. The invited email/account and active venue membership must match. Idempotency-Key supported.
Cancel a booking with the same rule as GET /api/v1/me/bookings/{id}/cancellation-preview. data.applied is that CancellationDecision for what the cancel actually did (fee, clip effect, copy); cancellation_fee (major units) is unchanged. Send accepted_outcome and accepted_fee_minor from the preview the member confirmed: when the fresh terms differ (the window closed meanwhile, the fee changed, or online cancellation is no longer allowed) nothing is cancelled and the response is 409 CANCELLATION_TERMS_CHANGED with error.details.decision holding the fresh terms. 503 CANCELLATION_TERMS_UNAVAILABLE means the venue rules could not be read and nothing was cancelled. Send accepted_outcome and accepted_fee_minor together or neither. A body that is not JSON, only one of the two, or a malformed reason / accepted_*, is 400 VALIDATION_ERROR and nothing is cancelled. Optional X-Organization-Slug restricts access to that venue; unknown or mismatched venue and another member return 404 NOT_FOUND before effects. No header preserves account-wide member ownership.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Booking ID
Optional reason plus the terms the member confirmed
Cookie-authenticated alternate cancellation endpoint with its legacy raw response envelope (the RPC result plus `applied`, the CancellationDecision for what the cancel did). Accepts the same accepted_outcome / accepted_fee_minor as DELETE /api/v1/bookings/{id} (409 cancellation_terms_changed with details.decision when they no longer hold; 400 validation_error for a body that is not JSON, only one of accepted_outcome / accepted_fee_minor, or a malformed reason / accepted_*; nothing cancelled). Honors X-Organization-Slug and member ownership before mutation; unknown or conflicting venue returns 404.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Booking ID
POST/api/v1/bookings/{id}/checkinBearer token
Self check-in
Member self check-in. Available within configured time window before class start. Optional X-Organization-Slug restricts access to that venue; unknown or mismatched venue and another member return 404 NOT_FOUND before effects. No header preserves account-wide member ownership.
NAMASTE-GATES-01 / LIVE-PARTICIPATION-01 — entitlement-based live-watch path. An existing physical booking returns 409 PHYSICAL_BOOKING_SWITCH_REQUIRED with error.details {booking_id, class_instance_id} before reservation, signing or admission; a Join tap never converts attendance or assesses a fee. The explicit switch endpoints are not yet released. Requires an active pass whose type grants online class access and covers the class type; finds or creates the caller’s attendance_type=online booking (idempotent, respects online_capacity, consumes a clip only for clip-based passes) and returns a provider-signed playback URL plus viewer-session telemetry token. The additive playback_transport is hls or whep. A WHEP input requires X-Playback-Transports containing whep; legacy clients receive 426 PLAYBACK_TRANSPORT_UNSUPPORTED before booking or clip consumption. WHEP never falls back to HLS. First entry opens 10 minutes before start and closes exactly at start; admitted viewers and an entitled viewer with a server-confirmed pre-start waiting reservation may recover through end + 5 minutes unless they explicitly leave after start. A not-ready/paused response includes additive error.details {viewer_session_id, class: {name, instructor, start_time, end_time}} only when the reservation RPC supports durable waiting; legacy RPC deployments omit that promise. Waiting creates no booking or clip consumption and every retry rechecks current entitlement. Stream readiness is checked BEFORE any booking side effect: the class instance must be status=live AND carry a playback id, otherwise 409 STREAM_NOT_READY (retryable) — a prepared playback id alone is never readiness, because the phone publisher stamps one while the instance is still scheduled. The additive stream_paused flag is true while the operator has paused the stream; the URL stays valid, so render a paused notice instead of the player. The additive music_pilot_enabled field controls music UI; signed private music_url, stable mix IDs and brand choices are issued only to the confirmed pilot account. GET /api/v1/bookings/{id}/online-join (the booking-scoped twin for members who already hold an online booking) returns the same STREAM_NOT_READY code and the same stream_paused flag. A request with `X-App-Digital-Content: none` (store build without in-app digital content) is refused by both routes with 403 IN_APP_DIGITAL_CONTENT_DISABLED before any booking or signed playback URL.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
Query parameters
music_brand_idstring
Optional brand UUID for the private music pilot; only the confirmed pilot account receives signed mix choices.
max_resolutionstring
Optional Mux playback cap: 720p or 1080p, embedded in the signed token. Omit for automatic quality. Invalid values return 400 INVALID_QUALITY; Cloudflare delivery ignores this cap.
What cancelling one owned class booking NOW means, from the venue’s current rules and in the venue’s timezone. `data.decision` is the CancellationDecision every client renders: outcome free | late | not_cancellable, reason_code, the moment the free window closes (ISO with the venue offset), the exact fee (minor units, currency, VAT breakdown, formatted), the pass effect (clip_returned | clip_deducted | unlimited | course_booking_returned | course_booking_used | none), the policy line, the consequence message and the confirm-button label, each in English and Danish. The window resolves course-access snapshot → class location → venue → 3 h; the fee resolves from the venue’s cancellation matrix (pass type → category → default) and the location/venue late_cancel_fee_amount. The window is closed AT its closing instant. The member cancel endpoints decide with the same rule, so the preview is what the cancel does; send accepted_outcome / accepted_fee_minor on the cancel to have it refused (409 CANCELLATION_TERMS_CHANGED) when the terms changed meanwhile. Computed per request and served with Cache-Control: no-store — never cache it across the window boundary. The pre-v1 flat fields (can_cancel, blocked_reason, cancellation_window_hours, is_late, will_charge, fee_amount in major units, currency, will_forfeit_clip, course_access, will_forfeit_course_booking, message) remain, derived from the same decision; locale=da selects the language of the flat message. Honours the optional x-organization-slug tenant scope; another member’s or venue’s booking returns 404. A failed rules read returns 500 CANCELLATION_PREVIEW_QUERY_FAILED — clients must then refuse to cancel blindly and offer a retry.
Parameters, scopes and examples
Path parameters
idstring · required
Booking UUID
Query parameters
localestring
Language of the legacy flat `message` fieldDefault: en
Response example
{
"data": {
"booking_id": "uuid",
"can_cancel": true,
"blocked_reason": null,
"cancellation_window_hours": 3,
"is_late": true,
"will_charge": true,
"fee_amount": 50,
"currency": "DKK",
"will_forfeit_clip": false,
"course_access": false,
"will_forfeit_course_booking": false,
"message": "Cancelling now counts as a late cancellation: the cancellation window closed at 06:30 today (venue time). You will be charged 50 kr.",
"decision": {
"version": 1,
"booking_id": "uuid",
"outcome": "late",
"reason_code": "inside_window",
"computed_at": "2026-09-24T05:05:00.000Z",
"timezone": "Europe/Copenhagen",
"class": {
"name": "Hot Yoga 60",
"starts_at": "2026-09-24T09:30:00+02:00"
},
"window_hours": 3,
"window_closes_at": "2026-09-24T06:30:00+02:00",
"hours_before_start": 2.42,
"fee": {
"amount_minor": 5000,
"currency": "DKK",
"vat_included": true,
"vat_rate_percent": 0,
"vat_amount_minor": 0,
"formatted": "50 kr"
},
"pass_effect": "unlimited",
"pass": {
"id": "uuid",
"name": "Unlimited Monthly"
},
"refund": null,
"policy_summary": {
"en": "Free cancellation until 3 hours before class. After that, a 50 kr late-cancellation fee applies.",
"da": "Gratis afmelding indtil 3 timer før holdet. Derefter koster en sen afmelding 50 kr."
},
"message": {
"en": "Cancelling now counts as a late cancellation: the cancellation window closed at 06:30 today (venue time). You will be charged 50 kr.",
"da": "Afmelder du nu, er det en sen afmelding: afmeldingsfristen udløb kl. 06:30 i dag (lokal tid). Du bliver opkrævet 50 kr."
},
"confirm_label": {
"en": "Cancel and pay 50 kr",
"da": "Afmeld og betal 50 kr"
}
}
}
}
POST/api/v1/qr/checkinBearer token
QR self-check-in
Client-facing: exchange a valid QR token for a check-in on the caller's booking. Token must be active and not expired. Anti-replay: a booking can only transition to checked_in once. Every scan writes an audit_log entry regardless of outcome.
Returns offers and current server eligibility for the authenticated member. Optional venue filter narrows active memberships; branded organization slug scope is enforced. Paused, ended and unavailable offers may remain visible without valid proof. No cached result is proof.
Parameters, scopes and examples
Query parameters
organization_idstring
Venue UUID filter
POST/api/v1/benefits/proofBearer token
Create a live benefit card
Rechecks the vendor agreement, venue settings and exact qualifying pass or service settlement under the agreed trigger. Proof rotates every 20 seconds and expires after 60 seconds. verification_url contains an opaque fragment token; never log or persist this response.
Parameters, scopes and examples
Selected offer and owning venue
Request body
{
"deal_id": "uuid",
"organization_id": "uuid"
}
GET/api/v1/admin/benefitsBearer token
Read venue benefits configuration
Requires passes.manage. Returns approved deals, pass/service choices, saved assignments, rollout enabled state and optimistic revision for the authorized venue.
PUT/api/v1/admin/benefitsBearer token
Configure venue benefits
Requires passes.manage. Atomically replaces pass and service assignments within current vendor constraints. accepted_version must match each enabled agreement; expected_revision prevents lost edits. Does not notify clients.
Parameters, scopes and examples
Complete configuration; disabled assignments retain history of accepted terms
Requires passes.manage and resolves the target member only within the authenticated venue. Evaluates saved configuration with the canonical eligibility service; never issues a proof for staff.
Parameters, scopes and examples
Query parameters
user_idstring · required
Member UUID
Discovery22 documented operations
GET/api/v1/discoveryPublic
Discover live venue inventory
Bounded, paginated Universe discovery for one canonical world and venue-local date. Returns capped live class or service/appointment summaries with explicit partial failures; member coordinates are not accepted.
Parameters, scopes and examples
Query parameters
worldstring · required
Required: classes, treatments, or salon
datestring · required
Required venue-local date (YYYY-MM-DD)
time_windowstring
any, morning, afternoon, or eveningDefault: any
searchstring
Venue, location, class, or service search
pageinteger
Page numberDefault: 1
limitinteger
Venue items per page (max 4)Default: 4
GET/api/v1/discovery/countsPublic
Legacy activity and published service venue counts
Lightweight Explore landing counts. Legacy classes, treatments and salon fields retain their same-day activity signals for older clients. Additive offering_treatments and offering_salon fields count eligible venues with at least one active published service. Offering counts do not promise a free slot today; use /discovery for actual availability.
Full venue profile including brands, locations with rooms, opening hours, amenities, photos, booking mode and branded app configuration. settings.booking.max_bookings_per_day is the daily booking limit (0 = no limit); settings.booking.daily_limit_counts_workshops and daily_limit_counts_course_sessions (default true) say whether workshops and course sessions count toward it and toward pass per-day caps. fresh=1 requests an uncached configuration read for active mobile venue refreshes. Failed configuration reads return 503 rather than a successful empty configuration.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
Query parameters
freshstring
Set to 1 for a private no-store configuration read
GET/api/v1/venues/{slug}/joinBearer token
Check venue join eligibility
Bearer-authenticated, read-only membership check for Consumer apps. Resolves the target from its slug or organization UUID and reports member, can_join, or an unavailable reason without changing account state.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug or organization UUID
POST/api/v1/venues/{slug}/joinBearer token
Join a venue with explicit consent
Bearer-authenticated and Idempotency-Key protected. Requires {consent:true}; creates one active member relationship without changing an existing role, assigns the venue client ID, and emits the canonical audit, analytics, and member.created integration events.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug or organization UUID
Explicit user consent to add this venue to their account.
Request body
{
"consent": true
}
GET/api/v1/venues/{slug}/schedulePublic
Get class schedule
Live class schedule with real-time availability. Filter by date range, location, brand, class type, instructor, or online-only. `class_type.image_url` is the nullable class hero image for native discovery and booking. Each row includes the additive general-policy `requires_workshop_entry` flag and a nullable public `workshop_entry_target`; `is_bookable` retains its capacity/status/time meaning. Each row carries `course_identifiers` (additive; `[]` when none): `{course_id, label, tone, course_name}` pills the venue set on the public course(s) a workshop date is linked to, `tone` one of default|success|warning|danger|info|purple|pink. Each row also carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.1): the class’s effective cancellation window (`window_hours`, `window_closes_at` in the venue timezone, `source` location | brand | venue), the venue-default `late_fee` / `no_show_fee` (`varies_by_pass` when some passes have their own rule), `online_cancellation` and a `policy_summary`, from the same rule as the cancellation preview; null when it cannot be read. Static per class, so safe to cache; the preview stays authoritative at cancel time.
Bearer-authenticated exact class detail for Consumer push deep links. Requires X-Organization-ID for an active member relationship, retains public/member-entitled completed or cancelled classes, and never exposes an unlisted class. `class_type.image_url` is the nullable class hero image. Includes the same additive general-policy `requires_workshop_entry` and nullable public `workshop_entry_target` fields as the venue schedule, plus the additive `course_identifiers` pills; `is_bookable` remains capacity/status/time-only. Also carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.1), the class’s effective cancellation window and venue-default fees by the preview’s rule; null when unavailable.
Active residence price books add residence_commerce with price_book_version, exact prices_minor and enabled countries mapping to currencies. No legal research or provider/reviewer identifiers are public. Invalid configured policy returns an empty unavailable book; an unactivated draft retains ordinary catalog behavior. Display discovery is not a signed purchase quote. All active pass types with pricing tiers, binding commitments, class restrictions, and location availability. Returns an `{ org, pass_types }` envelope (BB-R4): `org` carries `slug`, `name`, `currency`, `timezone`, and `vat_exempt_age_threshold` (for an under-/over-threshold pricing toggle); each `pass_types` entry includes its `slug` for `/buy/{slug}` deep-links. Configurable recurring entries also include `pricing_mode`, billing cadence, the immutable active pricing version, quantity range/step, volume tiers, unlimited option, and change-cycle policy. Every entry carries `registration_fee_u30_amount` / `registration_fee_o30_amount`: the pass's own pre-offer registration fee per age band, priced exactly as the public offers feed prices it (identical when no age split applies). Optional `category`, `location_id`, `brand_id` filters apply to `pass_types`. Each entry's `purchase_channel` (native in-app payment vs web) counts a `pass_type_video_access` live/recording level as a digital grant. Send `X-App-Digital-Content: none` from a store build without in-app digital content: an entry that includes any in-person grant then reads `native`, and a digital-only entry stays `web`. Absent or `full` keeps today's rule (any digital grant means `web`). The shared cache varies on that header; a `none` response is private.
The venue's live and scheduled flash-sale offers for one placement. `surface` is required and is one of `pricing_page` (the venue's own website), `global_app` (the Booking Bible consumer app) or `branded_app` (the venue’s own app, plan-gated). An absent or unknown `surface` returns an empty list rather than an error. Optional `brand_id` keeps only products whose pass type is venue-wide or linked to that brand, and drops a sale left with no eligible product. Each product carries the offer and normal prices for both VAT age bands, plus its canonical pre-waiver under-30 / 30+ registration-fee totals and the effective decimal VAT rate when Danish age pricing applies. A configurable (`pricing_mode: "flexible_quantity"`) product additionally carries a `flexible` block with its immutable published price book, the quantity vocabulary and the normal-price range: its scalar `price_amount` / `normal_price_*_amount` describe the CHEAPEST selection, and a product whose active published pricing version is missing or mismatched is omitted rather than quoted at a fallback price. Anonymous; a Bearer JWT is used only to apply the sale’s `exclude_active_pass_clients` rule to that client. Each offer includes `presentation` with `style` (clean, venue, or magic), `showRemainingCapacity`, and `showTimeToEnd`; absent legacy presentation defaults to clean with both visibility flags false. `capacity` contains `maximum`, `claimed`, and `remaining`; maximum and remaining are null for an unbounded offer. Render capacity and deadline only when the corresponding presentation flag is true. These are display values, not reserved inventory or checkout authority. Pass the offer UUID as `flash_sale_id` when requesting the canonical checkout quote. An optional eligibility decision is a provisional buyer-specific preview, or unresolved for an anonymous viewer; it is never admission or a payment guarantee. Canonical checkout preview, intent creation, and settlement revalidate it. Each product carries `purchase_channel` (native | web) from the same caller-aware rule as venue pricing: a `pass_type_video_access` level counts as digital, and with `X-App-Digital-Content: none` a product with any in-person grant is native while a digital-only product stays web. The response varies on that header.
Returns one usable published offer with the same product, published Flexible pricing, presentation and capacity fields as the offer list. Invalid IDs and unavailable offers return 404. `surface` defaults to public_link, the direct-link placement; pricing_page, global_app and plan-gated branded_app are also accepted. The route does not accept brand_id. A visible offer or remaining-capacity value does not grant purchase eligibility: the authenticated canonical checkout still validates the buyer, selected product, price version and current offer availability. An optional public eligibility decision is provisional, not final admission. Products carry the same caller-aware `purchase_channel` as the list (`X-App-Digital-Content`); the response varies on that header.
Class catalog with descriptions, difficulty levels, durations, and included services.
Parameters, scopes and examples
Required scopes
read:classes
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/instructorsPublic
Get instructors
Instructor profiles with bios, photos, and specialties.
Parameters, scopes and examples
Required scopes
read:instructors
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/locationsPublic
Get locations
Physical locations with rooms, capacity, opening hours, amenities, and Google Maps integration.
Parameters, scopes and examples
Required scopes
read:locations
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/brandsPublic
List brands
Active brands at this venue. Each entry includes identity (name, slug, description), theming (colors, logo, hero), social links, and a class_types_count for quick summary rendering.
Parameters, scopes and examples
Required scopes
read:brands
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/brands/{brandSlug}Public
Get brand detail
Full brand record plus class_types tagged to this brand and the pass_types available for it (respecting pass_type_brands restrictions — passes with no brand-junction rows are venue-wide and are included).
Parameters, scopes and examples
Required scopes
read:brands
Path parameters
slugstring · required
Venue URL slug
brandSlugstring · required
Brand slug within the venue
GET/api/v1/geoPublic
Geo prefill for the signup form
Anon utility that reads Vercel's request-geo headers (`x-vercel-ip-country`/`x-vercel-ip-city`) so a client can prefill signup's optional `country`/`city` fields from the caller's own IP before submitting POST /api/v1/auth/signup. `country` is an ISO 3166-1 alpha-2 code; `city` is URI-decoded free text. Either is `null` when the header is absent (e.g. local dev). No DB touch; never cached (per-caller response).
PUBLIC (no auth) balance lookup for a venue gift card, for a storefront "check your balance" widget. Org resolved from {slug}; lookup scoped to that org's cards by the FULL generated code OR the printed physical barcode (same fallback `redeemGiftCard` uses). Returns the minimal `{ code, remaining_amount, currency, status, expires_at }` — never purchaser/recipient PII. Enumeration-hardened: an unknown code, a cross-org code under the wrong slug, and a cancelled card all return the SAME generic 404. IP rate-limited (20/min).
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
codestring · required
Full gift-card code or the printed physical barcode
PUBLIC (no auth) gift-card preview so a BRANDED storefront can show a recipient what they were gifted ("Alex sent you a 3-month membership") before prompting signup/redeem — instead of bouncing them to BB's /gift/redeem/[code] venue portal. Accepts the generated code OR the printed physical barcode. Returns `{ code, gift_type, sender_name, gift_description, pass_name, amount, currency, status, expires_at }` — the sender's display name + a human gift description only, NEVER recipient/purchaser contact info, the personal message, or the redeemer. Enumeration-hardened: unknown code, wrong slug, and a cancelled card all return the SAME generic 404. IP rate-limited (20/min). Redeem itself is member-authenticated (POST /api/v1/gift-cards/redeem).
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
codestring · required
Full gift-card code or the printed physical barcode
Member-authenticated request-a-booking for a service that accepts inquiries (`accepts_inquiries: true` on the public services list). Free-text preferred time, not a real slot — the venue converts it to a real appointment once a time is agreed. `Idempotency-Key` is optional but MUST be a UUID when sent (400 `INVALID_IDEMPOTENCY_KEY` otherwise); it is the database ingest request id. Every 201 and every 503 `INQUIRY_RECONCILE_FAILED` returns an `Idempotency-Key` RESPONSE header (mirrored as `error.details.request_id` on the 503) carrying the identity the inquiry was accepted under — your key when you sent one, the server-generated UUID when you did not. Retry a 503 with that exact value as `Idempotency-Key`: it replays the accepted inquiry (no second row) and re-drives only what is still missing. Retrying without it mints a new identity and files a duplicate inquiry. Rate-limited 10/min. Errors: 503 INQUIRIES_DISABLED (kill switch off), 404 NOT_FOUND (venue), 422 SERVICE_NOT_ACCEPTING_INQUIRIES, 422 VALIDATION_FAILED, 409 IDEMPOTENCY_KEY_REUSE_MISMATCH (same key, different answers — nothing written), 500 SUBMIT_FAILED (`details.reason` passthrough).
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
Inquiry details
Request body
{
"service_id": "uuid",
"preferred_time": "Tuesdays or Thursdays after 17:00",
"message": "Looking for a 90-minute deep tissue session.",
"contact_name": "Jane Doe",
"contact_email": "jane@example.com",
"contact_phone": "+4520123456"
}
Response example
{
"data": {
"id": "uuid"
},
"error": null
}
GET/api/v1/me/inquiriesBearer token
My booking inquiries
The caller's own booking inquiries across every venue, newest first. Status is mapped to plain language (never the raw form_submissions enum): 'Sent — waiting for the venue', 'The venue replied', or 'Closed'; archived/spam/deleted rows are never returned.
Exchange a Google or Apple identity token for a Booking Bible session. Existing email/password accounts are unchanged. New accounts require a venue (organization_id or organization_slug). Unclaimed imported emails are not auto-linked.
Exchange a signup confirmation token for a session on an explicitly owner-configured partner receiver for a validated organization and brand. Requires signed signup provenance, active tenant membership, password and MFA checks. Never accepts recovery or mobile magic-link credentials. Rate-limited 5/10 min per IP.
Exchange a device-bound mobile token_hash or a 6-digit email OTP for a session. Mobile token hashes require the callback request_id and device-held code_verifier. Rate-limited 5/10 min per IP.
Check whether an email already has an account before signup. Returns a state hint (absent | active | password_never_used | imported_unclaimed | unknown). Rate-limited 20/min per IP. Constant-time floor of 250ms to prevent enumeration.
Create a TOTP challenge for one of the caller's factors. Returns {id, expires_at}; pass the id as challenge_id to /auth/mfa/verify. Repeating mints a fresh challenge (intended resend). Rate-limited 5/min per user.
Verify a 6-digit TOTP code (enrollment confirmation or login challenge). Accepts {type: totp, factor_id, code}, {type: backup_code, code}, or the challenge-bound {challenge_id, code} (factor resolved from the preceding /mfa/challenge). Rate-limited 5/5 min per user.
Generate 10 single-use backup codes for MFA recovery. Previous unused codes are invalidated. Codes are shown once in plaintext — only hashes are stored.
Parameters, scopes and examples
Response example
{
"data": {
"backup_codes": [
"ABCD1234EF",
"..."
],
"warning": "Save these codes securely. They will not be shown again.",
"count": 10
}
}
POST/api/v1/auth/mfa/resetBearer or API key
Admin MFA reset
Admin-initiated MFA reset for a user. Unenrolls all factors and invalidates backup codes. Permission: admin.users.manage.
Exchange refresh token for new access and refresh tokens.
Parameters, scopes and examples
Refresh token
Request body
{
"refresh_token": "xxx"
}
POST/api/v1/auth/logoutBearer token
Log out current session
Revoke the refreshable Supabase session represented by the caller JWT. The access JWT remains valid until its encoded expiry.
Parameters, scopes and examples
Response example
{
"data": {
"ok": true
}
}
POST/api/v1/auth/password/forgotPublic
Forgot password
Send a single-use 6-digit password verification code. Honours venue branding when an org is identified and never reveals whether the email exists. No reset link is generated.
Verify the single-use numeric code and permanently save a new password in one operation. Recovery tokens and reset links are not accepted. When MFA is required or assurance cannot be checked, success returns session:null and sign_in_required:true; mfa_required:true identifies a verified MFA requirement. Sign in normally with the saved password to complete verification.
Authenticated password change that still requires a fresh single-use numeric code. Submit the code and new password together; current-password-only and session-only changes are rejected. A successful save can return session:null with sign_in_required:true (and mfa_required:true when verified); normal sign-in must satisfy configured MFA before a recovery session is released.
NAMASTE-GATES-01 — mint a one-time SSO handoff code (60s TTL, single-use, SHA-256 hashed at rest) bound to an allowlisted destination domain. The destination site exchanges it at /auth/handoff-exchange for a fresh session.
NAMASTE-GATES-01 — consume a one-time handoff code (atomic single-use) and receive a fresh Supabase session for the bound user. Same session shape as /auth/login.
TV-DEVICE-AUTH-01 — RFC 8628-style device authorization (mint side). An input-constrained device (TV) receives a 256-bit device_code (its poll credential) plus a short user_code (shown as XXXX-XXXX + QR). Both are SHA-256 hashed at rest, bound to one 10-minute expiry, single-use.
TV-DEVICE-AUTH-01 — a SIGNED-IN member submits the user_code shown on the TV (normalized: uppercase, dashes/spaces stripped). Binds the pending device code to the caller so the TV poll returns a session. Every failure (unknown / expired / attempts-capped) is the same generic 400 INVALID_CODE; per-code attempts<5 cap.
Parameters, scopes and examples
The short code shown on the TV
Request body
{
"user_code": "ABCD-EFGH"
}
Response example
{
"data": {
"approved": true
}
}
POST/api/v1/auth/device/tokenPublic
Poll TV device code for session
TV-DEVICE-AUTH-01 — the TV polls with its device_code (every `interval` seconds). 400 AUTHORIZATION_PENDING until approved; 400 EXPIRED_TOKEN / 403 ACCESS_DENIED / 400 INVALID_CODE are terminal. On approval the code is consumed atomically (single-use) and a fresh Supabase session is returned — same shape as /auth/login.
List active passes across all venues with usage stats. Each pass carries is_default (the member's chosen default at that venue), usable and unusable_reason (clips_exhausted | expired | paused | past_due | not_eligible | not_found, evaluated on the venue-local today; a renewing membership whose current cycle is spent stays usable; class-specific restrictions are decided at booking time).
Parameters, scopes and examples
Required scopes
read:bookings
GET/api/v1/passes/{id}Bearer token
Pass detail
Single pass detail with usage stats, freeze/binding state, configurable recurring selection/allowance, and the pass-type + venue. Owner-scoped: cross-user reads return 404. FLEX-PARITY-01 — a top-level `allowance` block ({kind, quantity, class_allowance, interval, interval_count, label}) names the purchased tier of a Flexible (configurable-quantity) membership, e.g. "1 class / month"; null for a fixed pass. `pass_type.slug`/`pricing_mode` are additive.
Returns the authenticated member’s frozen selection, current published recurring choices, pricing version, and any pending renewal change. Owner-scoped and available only when member changes are enabled by the venue.
Preview a venue-approved quantity/unlimited change for renewal 1–24, or schedule it with the exact unexpired quote fingerprint. The change takes effect only after the target renewal invoice is paid.
Cancels the authenticated member’s pending future allowance change without altering the current frozen entitlement.
Parameters, scopes and examples
Path parameters
idstring · required
Issued pass UUID
GET/api/v1/me/passes/{id}/addonsBearer token
Get add-ons for my Flexible membership
Returns the active, pending and currently eligible recurring extras for an authenticated member’s active monthly Flexible pass, with server-priced monthly amounts.
Parameters, scopes and examples
Path parameters
idstring · required
Issued pass UUID
POST/api/v1/me/passes/{id}/addonsBearer token
Preview or buy a recurring Flexible add-on
Preview the Stripe-calculated charge through the next membership renewal, then buy with that quote and an Idempotency-Key. The extra starts after a confirmed paid invoice and renews with the membership. Pending or scheduled subscription changes are refused.
Pass-type catalog detail used by checkout — price, duration, benefits, binding tiers, eligible class types, configurable kind, and the immutable published Flexible pass configuration when enabled. Active public items need no authentication; a hidden exhausted-credit target requires member JWT authentication plus its clips_empty_offer_id capability. `purchase_channel` follows the same caller-aware rule as venue pricing: a video-access level counts as a digital grant, and `X-App-Digital-Content: none` makes an entry with any in-person grant `native` while a digital-only entry stays `web`.
Parameters, scopes and examples
Path parameters
idstring · required
Pass-type id
Query parameters
clips_empty_offer_idstring
Opaque clips-empty path UUID. Revalidated against the authenticated member’s exhausted, still-valid source pass before a hidden target is returned.
The existing compatibility policy still rejects direct flash sales for Flexible class packs and time passes; one-time direct disclosure applies to supported fixed products only. Direct Flexible proofs also carry purchase_obligation_fingerprint: preserve it unchanged. The canonical preview reports purchase_obligation availability and all-tender minimum_total_payable. Supported recurring schedules expose intro_through_date and either reachable self-service earliest_cancellation_effective_on or a distinct cancellation {kind:conditional_contractual_minimum, request_method:contact_studio, condition:timely_valid_notice, earliest_possible_end_on}. Staff-managed facts are conditional on timely valid notice, not cancellation confirmation. Render this distinction before acknowledgement. One-time totals come from final canonical pricing and omit cancellation dates. Unsupported shapes report unavailable without guessed facts. These are purchase-time disclosures, not a replacement for existing live cancellation policy. Initiate pass purchase. Returns a PaymentIntent or SetupIntent client_secret (`client_secret_type` identifies which) with the provider-frozen customer, ephemeral key, Connect account, merchant country, and regional revision. Customer credentials are paired and may both be null only for a supported generic-sheet/no-customer result. Optional binding_months must identify a current server-side tier and is priced by the same canonical resolver as checkout preview; unavailable tiers return 422 rather than falling back. Flexible recurring passes require selection_kind=quantity with quantity, or selection_kind=unlimited; Flexible class/time passes require selection_kind=option with option_id. A recurring Flexible pass accepts only a Flash Sale backed by one shared introductory amount, which applies regardless of the chosen allowance. Fixed passes retain promo codes, gift cards, and account credits. Optional flash_sale_id (UUID) selects a direct venue offer and cannot be combined with promo_code. Offer price, linked promotion, dates, buyer eligibility, caps and compatible passes are server-authoritative; unavailable offers return 422 FLASH_SALE_UNAVAILABLE without charging normal price. Flexible purchases must send the complete preview flexible_quote, including its optional flash_sale_id and flash_sale_rule_fingerprint; stale or changed quotes return a typed QUOTE_* error. Reuse the same Idempotency-Key only for the same purchase choices. The pricing response remains canonical: charged_today is payable; optional direct_offer {flash_sale_id, name, normal_amount, offer_amount} is display-only fixed one-time metadata in minor units. Existing requests without flash_sale_id retain generic checkout behavior. When an offer applies, the response also carries hold_expires_at (ISO 8601 UTC instant the reservation lapses, or null when no offer hold applies) and server_time (ISO 8601 UTC, the server clock at response time), always together. Compute the countdown once as hold_expires_at minus server_time and run it locally; never compare hold_expires_at against the device clock. A lapsed hold that can still be retried returns 409 OFFER_HOLD_EXPIRED; a lapsed hold with no way to restart (capacity gone, the per-client limit reached, or a late charge already refunded) returns 409 OFFER_CONTACT_VENUE, which never offers a restart and instead points the buyer to the venue desk. A first attempt with no reservation ever held, against an offer that is already fully booked, returns 409 OFFER_SOLD_OUT with plain copy and no charge attempted — never the raw promo-code refusal reason. A canonical customer eligibility refusal or unresolved decision returns 409 CUSTOMER_ELIGIBILITY with a safe decision in error.details.eligibility; this is final admission for this purchase attempt, unlike a provisional public preview. Show the reason in EN/DA and current options at the trusted venue. This route mints a native in-app Stripe payment, so a pass type whose catalog purchase_channel is web (any digital entitlement: online classes, course, video library, digital materials) is refused before any charge work with 409 PURCHASE_CHANNEL_WEB_ONLY and error.details {purchase_channel: 'web'}; send the buyer to the venue website instead (App Store 3.1.1 / Google Play payments). The channel is the one the catalog serves to the same caller: a `pass_type_video_access` level counts as digital, and a request with `X-App-Digital-Content: none` (store build without in-app digital content) may buy a pass with any in-person grant; digital-only passes stay refused.
Parameters, scopes and examples
Purchase. Optional flash_sale_id: UUID (mutually exclusive with promo_code). Optional flexible_quote: unchanged preview quote; required for Flexible purchases. Direct-offer quote authority includes flash_sale_id and flash_sale_rule_fingerprint. Fixed direct-offer purchases require direct_purchase_quote: the unchanged five-minute signed preview proof {version:1, organization_id, user_id, pass_type_id, flash_sale_id, authority_fingerprint, credit_applied_minor, payable_minor, issued_at, expires_at, fingerprint}. Both tender fields are nonnegative integer minor units; payable_minor equals preview charged_today. Before new payment work, the canonical engine verifies the reviewed price, terms and exact account-credit/cash split. Changed balances cannot silently increase the payment. Direct gift_card_code is rejected unless equivalently quoted; generic gift cards are unchanged. Missing, invalid, expired or changed proof returns QUOTE_REQUIRED, QUOTE_INVALID, QUOTE_EXPIRED or QUOTE_STALE (409 at adapter preflight, 422 at native core); an already committed matching operation retains its frozen result.
MEMBER (JWT) redeem endpoint so a branded storefront can host the whole redeem flow on its own domain. Applies the gift `{ code }` — generated code OR printed physical barcode — to the caller's account: a custom-amount gift credits the balance (`{ type:"credit", amount, newBalance }`); a pass gift creates + activates a pass (`{ type:"pass", passId }`). Atomic SELECT FOR UPDATE claim — two concurrent calls can never both redeem. A logged-out recipient must sign up / log in first (that creates/links the BB member); this endpoint is member-only by design. Org from the caller's active membership (X-Organization-ID header or single membership). Errors: 401 UNAUTHORIZED, 403 NO_ORG / MODULE_DISABLED, 400 VALIDATION_ERROR, 404 INVALID_CODE, 409 ALREADY_REDEEMED / EXPIRED / NOT_AVAILABLE, 500 REDEEM_FAILED.
Parameters, scopes and examples
Gift card code to redeem onto the caller's account
Authenticated buyer-only gift purchases, scoped to the active organization. Historical access remains when new gift sales are disabled. Optional page (positive integer, default 1) returns up to 50 records ordered newest first: {id, amount, currency, status, paid, code, purchased_at, recipient_name, message, expires_at, delivery_method, scheduled_send_at, delivery}. Delivery is a channel-to-status map from durable provider evidence: accepted is not delivered, failed includes a verified bounce. Code is null until webhook-confirmed payment. Recipient email, buyer identity and public bearer tokens are never returned. Private no-store response.
Parameters, scopes and examples
Query parameters
pageinteger
Positive page number, default 1. Each page contains up to 50 purchases.
GET/api/v1/gift-cardsBearer token
My gift cards
Gift cards the caller purchased or received (buyer, redeemer, or addressed recipient email). Without scope=account, scoped to the active org and returning an array. With scope=account, returns `{cards, failed_organization_ids}` across current and former venues; each card includes organization_id and organization_name. A branded x-organization-slug narrows the account read to that venue. Cards include `{id, code, initial_amount, balance, currency, status, recipient_email, recipient_name, message, expires_at, created_at}` with status `active|redeemed|expired|void`. Owned history stays readable when new gift-card sales are disabled.
Parameters, scopes and examples
Query parameters
scopestring
Use account for an owner-scoped cross-venue wallet; omit for the legacy active-venue array.
POST/api/v1/gift-cards/purchaseBearer token
Purchase gift card
Buy a gift card (custom amount or a gifted pass) for a recipient. Creates a Stripe one-time PaymentIntent and returns client_secret plus customer_id + ephemeral_key for the Stripe Payment Sheet. Fixed gifts retain their existing contract. A published Flexible pass is available only when is_giftable is not false and flexible_gift_supported is true; flexible_gift_funding_modes currently supports prepaid only. Send preview:true with the published flexible_selection, optional flexible_addons and prepaid duration to receive the canonical unknown-recipient gross amount (this endpoint reports amount in major units), flexible_gift disclosure and an opaque flexible_quote without creating a provider intent or gift card. The explicit purchase must echo that flexible_quote unchanged. The recipient authenticates when redeeming the gift, and the prepaid pass starts then; it does not auto-renew or charge the recipient for the funded period. Gated on the gift_cards module. Idempotency-Key supported.
Parameters, scopes and examples
Gift card purchase. Additive Flexible fields: preview?: boolean; flexible_selection?: {kind:"quantity",quantity:number}|{kind:"unlimited"}|{kind:"option",optionId:string}; flexible_addons?: Array<{key:string,quantity:number}>; flexible_quote?: opaque server-signed preview object echoed exactly. Existing fixed-gift inputs and behavior are unchanged.
Anonymous (or logged-in) native gift-card checkout — phase 1. Mints a Stripe PaymentIntent for a gift card and returns client_secret so the buyer can mount Stripe Elements in a modal. Two gift kinds (exactly one of the two fields): a CUSTOM-AMOUNT gift via `amount` (smallest currency unit, min 5000, max 5000000), or a PASS-BASED gift via `pass_type_id` (GIFT-PASS-API-01 — must be an active, giftable, non-intro pass type of this org; price is server-resolved via calculateGiftPrice, optional `duration_months` 1–120 prepays a recurring membership). Fixed gifts retain their existing contract. A published Flexible pass is available only when is_giftable is not false and flexible_gift_supported is true; flexible_gift_funding_modes currently supports prepaid only. Send preview:true with the published flexible_selection, optional flexible_addons and prepaid duration to receive the canonical unknown-recipient gross amount in minor units, flexible_gift disclosure and an opaque flexible_quote without creating a provider intent or gift card. The explicit purchase must echo that flexible_quote unchanged. The recipient authenticates when redeeming the gift, and the prepaid pass starts then; it does not auto-renew or charge the recipient for the funded period. The gift_cards row is created only on confirm, so an abandoned payment leaves no orphan. Anonymous callers must pass a Turnstile token. VAT is accounted at redemption (multi-purpose voucher) so vat_amount is 0. Gated on the gift_cards module. Rate-limited 10/min.
Parameters, scopes and examples
Gift card checkout. Additive Flexible fields: preview?: boolean; flexible_selection?: {kind:"quantity",quantity:number}|{kind:"unlimited"}|{kind:"option",optionId:string}; flexible_addons?: Array<{key:string,quantity:number}>; flexible_quote?: opaque server-signed preview object echoed exactly. Existing fixed-gift inputs and behavior are unchanged.
The sender-enabled source uses SQL93 durable recipient authority; source readiness is not a deployment or provider-delivery claim. Recipient email/SMS requires positively identified Vercel production runtime; preview, development, missing or mismatched identity is held. The 423 maintenance contract below also applies if the same-schema recipient pause is restored. Native gift-card checkout — phase 2. Finalizes the original PaymentIntent gift (custom-amount or pass-based), canonical accounting and buyer receipt. Temporary recipient maintenance returns 423 GIFT_RECIPIENT_DELIVERY_HELD with data:null,error:{code,message} when immediate delivery is held. Payment/gift may already be durably confirmed: preserve the original intent, show held, never pay again and never claim sent or activated. A scheduled gift may return200 before its scheduled delivery is held;200 is not proof of recipient delivery. Idempotent on the original intent; ordinary success returns the last4 code, masked recipient email, gift_type and separate durable delivery channel statuses when available. Provider accepted is not delivered; a missing status stays unknown. organization_slug is recommended for direct-charge venues. The authenticated recurring SetupIntent branch preserves the original giver/account/card and uses the same held contract without claiming a payment was taken. Its held error.details adds giver_receipt_state (succeeded, busy or manual_reconciliation) and payment_collected:false. 409 GIFT_CONFIRMATION_PROCESSING or GIFT_GIVER_RECONCILIATION_REQUIRED also retain the original SetupIntent; they never authorize a new checkout or ambiguous historical resend. Durable delivery incomplete returns409 GIFT_RECIPIENT_DELIVERY_PENDING or GIFT_RECIPIENT_DELIVERY_REVIEW_REQUIRED, with purchase_preserved:true and retry_same_confirmation in error.details. Only pending permits rechecking the same confirmation; review never authorizes another purchase or automatic resend. Recurring responses additionally preserve giver_receipt_state and payment_collected:false. Accepted channels are not proof of delivered messages, and all original activation requirements must complete before recipient-dependent confirmation succeeds.
{
"data": null,
"error": {
"code": "GIFT_RECIPIENT_DELIVERY_PENDING",
"message": "Your gift is preserved, but its confirmation is not complete. Keep this checkout and check its confirmation again. Do not purchase again.",
"details": {
"purchase_preserved": true,
"retry_same_confirmation": true
}
}
}
GET/api/v1/me/passes/{id}/pauseBearer token
Get pass pause capability and policy
Owner-scoped, read-only pause capability for an issued pass. Resolves the current venue-local date and timezone, earliest allowed start, next billing date and notice deadline, currency, pause policy and termination boundary. can_pause plus reason/reason_code is authoritative for availability. preview_required identifies a subscription whose configured notice or calendar-month policy requires a reviewed token; clients must not calculate policy, billing amounts or date bounds independently.
Preview or commit an owner-scoped pass pause for an inclusive venue-local date range. preview:true performs no mutation and returns the canonical resolved policy, dates and financial cycle schedule. A normal commit retains the existing pause response. An opted-in subscription with preview_required:true requires the exact preview_token from the reviewed preview; a missing token returns 409 with error.details.code PAUSE_PREVIEW_REQUIRED, while changed or expired review authority returns 409 PREVIEW_STALE. Other fixed and legacy pause behavior remains unchanged. The server enforces notice, calendar-duration, annual allowance, binding and billing rules; clients must not calculate credit or charge amounts. Idempotency-Key supported for commit.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Inclusive pause window. Use preview:true without preview_token to review; for a commit with preview_required:true, resend the same dates and exact preview_token.
Confirmed owner-scoped cancellation alias for /terminate. Requires acknowledged=true, accepts reason, preview_token and optional cycle from the termination preview. Enforces current venue, brand, product and purchased rules; provider synchronization fails closed. Idempotency-Key supported.
Move a deferred (pending_activation) membership start to today or an earlier future date: re-anchors Stripe billing, charges the first membership payment, and activates the pass. Owner-scoped. Idempotency-Key supported; rate-limited 5/min. Returns payment_status succeeded | requires_action (confirm with client_secret; the invoice.paid path then activates) | pending. Errors: PASS_NOT_FOUND, FORBIDDEN, ALREADY_STARTED, IN_PROGRESS, INVALID_START_DATE, PAYMENT_FAILED, STRIPE_UNAVAILABLE.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
New start date (must be earlier than the current start)
Owner-scoped read-only cancellation summary from stored pass brand, product, purchased terms and venue rules. Optional cycle selects a server-offered later end date. summary.cycleChoices contains {cycles,effectiveAtIso,effectiveDateVenueLocal}; empty when unavailable. Includes final scheduled payment, commitment refusal and preview_token; confirm with the same cycle and token. Omitted cycle preserves default notice.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Query parameters
cycleinteger
Optional offered billing cycle, 0–12; 0 only for an immediate default
POST/api/v1/me/passes/{id}/terminateBearer token
Terminate a recurring membership
Confirmed owner-scoped membership termination. Enforces venue allow_member_cancel, minimum membership age, binding period, required reason and the configured termination boundary. Stripe synchronization is fail-closed and Idempotency-Key is supported.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Explicit acknowledgement, optional/venue-required reason, and selected preview token/cycle. Omitted cycle retains default notice. A revoked later choice returns CANCELLATION_CHOICE_UNAVAILABLE; changed dates or terms return PREVIEW_STALE.
Return the authenticated member’s venue-scoped self-extension policy and live quote: proposed expiry, price/currency, configured duration, remaining extension allowance, clips, a machine-readable unavailable_reason, and the pass type’s purchase_channel (native | web, the same value the catalog serves to this caller, including the `X-App-Digital-Content` rule). A paid extension with purchase_channel web must be sold on the venue website, not in the app. Requires X-Organization-ID and fails closed on invalid venue configuration.
Revalidates the venue’s live self-extension policy and creates a durable operation before any processor call. Paid responses include PaymentSheet customer/ephemeral-key credentials in the exact frozen Stripe namespace; direct mode returns stripe_account_id. Free responses still return operation_id but do not mutate the pass. A paid extension of a pass type whose purchase_channel is web is refused before any idempotency replay, operation claim or processor call with 409 PURCHASE_CHANNEL_WEB_ONLY and error.details {purchase_channel: 'web'}; the channel follows the catalog's caller-aware `X-App-Digital-Content` rule. Requires X-Organization-ID and Idempotency-Key.
Authoritatively rechecks owner, tenant, venue policy, maximum count, hard end, frozen Stripe provenance and payment status under database locks. Paid success atomically records payment, fee, audit, pass, and operation; explicit post-charge conflicts are idempotently refunded. Nonterminal 202 statuses are finalizing or refund_pending and are safe to retry. The Stripe webhook shares this reconciler. Idempotency-Key is required.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Durable operation reference plus PI reference for paid extensions
Request body
{
"operation_id": "uuid",
"payment_intent_id": "pi_xxx (omit when free)"
}
Settles the outstanding renewal of the caller’s past_due or suspended membership. Optional JSON body { payment_method_id, payment_id }: payment_id is the original local renewal UUID from the outstanding item and prevents collecting a later debt. Legacy bodies without it remain accepted. payment_method_id (v1.2) is a saved card (pm_… or a legacy card_… source) that becomes the subscription’s default and the customer’s default before the open renewal invoice is paid with it, so this and every later renewal charge it. Without a body the card the member set as default is used when it differs from the subscription’s card, else the subscription’s card. Returns payment_id and an additive receipt with operation_id, payment_id, status (settled, failed, pending) and settled. Keep both IDs through all retries and bank outcomes. Paid requires a scoped durable receipt; requires_action with client_secret and stripe_account for 3-D Secure (then call …/pay-renewal/confirm); processing while the processor still works on it (do not pay again). Every refusal carries a machine code and error.details.next_step: 422 PAYMENT_FAILED for a real card decline (next_step update_card, details.decline_code); 422 NO_PAYMENT_METHOD when there is no usable saved card; 422 CARD_NOT_AVAILABLE when the sent card is not saved on this membership’s customer; An unproven closed attempt stays pending; 400 VALIDATION_ERROR for a malformed id; unclassified processor failures return processing and require confirmation of the original payment; legacy 502 RETRY_FAILED is also an uncertain result (next_step null); 422 PAYMENT_NOT_COLLECTABLE or CARD_PAYMENTS_UNAVAILABLE when only the venue can take it (next_step contact_venue); 422 NOTHING_OUTSTANDING (next_step null). Owner scoped, 5/min. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Optional payment_id binds the original local renewal UUID before collection. Optional Idempotency-Key atomically reserves the authenticated actor, owning venue and exact body; mismatches refuse. The durable operation retains its exact original provider body and key; bounded retries may replay only that command. Unknown outcomes remain processing and require original-operation/payment confirmation. Optional (v1.2) payment_method_id: a saved card from GET /api/v1/me/payment-methods (pm_… or a legacy card_… source). Pinned on the subscription and the customer, then charged for this renewal.
Verifies the exact original renewal payment with the processor and its scoped durable receipt. Retain payment_id and the additive receipt.operation_id from the outstanding item/pay response before setup/default/challenge, and send it on every confirmation including SDK cancel/error. An active membership is not payment proof. Without payment_id, legacy confirmation examines the latest scoped payment and accepts only a verified subscription_cycle invoice; purchase invoices and native PI-only rows cannot prove a renewal. Returns settled true only after the exact receipt, otherwise false, plus receipt.status when an operation exists. Only provider-proven terminal decline permits selecting a new card; pending never permits a replacement operation. Owner scoped, 5/min. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Optional payment_id and operation_id bind the original renewal and durable recovery operation. Return receipt.status settled, failed or pending; unknown outcomes require the same operation. Empty legacy bodies remain accepted conservatively.
Accept a pending pass-share invitation by token. Verifies the caller’s email matches the invite recipient, then grants booking access by appending the caller to `passes.shared_with` (respecting `pass_types.max_sharers`) and converges the share into the `pass_shares` table. Emits `pass.share_accepted`. Member-JWT. Original Idempotency-Key is required. Exact token/body/key replays the immutable accepted or closed receipt; unknown outcomes retain the original request.
Authenticated recipient only. Use the exact original {token} body and Idempotency-Key. Existing acceptance wins closure. No inline notification. Errors never authorize replacement.
Authenticated recipient only. Use the exact original {token} body and Idempotency-Key. Existing acceptance wins closure. No inline notification. Errors never authorize replacement.
Member-JWT. Sets which of my passes a venue uses first for bookings, or clears it with pass_id null ("let the system choose"). The venue is the pass's own venue; organization_id (required when clearing) and the optional X-Organization-Slug scope must agree with it (400 ORGANIZATION_MISMATCH / ORGANIZATION_REQUIRED). Another member's or another venue's pass is 404 PASS_NOT_FOUND; a pass that is not active, past_due, pending_activation or paused is 422 PASS_NOT_SELECTABLE; no active membership is 404 MEMBERSHIP_NOT_FOUND. Audited as member.default_pass_set. Without a default, bookings use passes in good standing first, then clip cards before unlimited passes, then the soonest-expiring, then the fewest clips left. When the default is used up or cannot be used for a class, POST /api/v1/bookings answers 409 DEFAULT_PASS_UNUSABLE.
Published VODs and class replays for the caller's venue. Visibility public + members only; pass_restricted items are accessible via /video-catalog/:id once the pass check passes. Signed Mux playback URLs valid for 2 hours. A request with `X-App-Digital-Content: none` is refused with 403 IN_APP_DIGITAL_CONTENT_DISABLED before any lookup or signed URL.
Parameters, scopes and examples
Query parameters
pageinteger
Page numberDefault: 1
limitinteger
Items per page (max 50)Default: 20
categorystring
Filter by category (class_recording | tutorial | workshop)
Single video detail with signed playback URL. Pass_restricted videos require a qualifying active pass (returns 403 PASS_REQUIRED otherwise). A request with `X-App-Digital-Content: none` is refused with 403 IN_APP_DIGITAL_CONTENT_DISABLED before any lookup or signed URL.
The caller's required/pending intake forms for their active org. Each entry is annotated with whether the member already submitted (the pre-booking form gate's source of truth). Returns [] when the `forms` module is disabled.
Parameters, scopes and examples
Response example
{
"data": [
{
"id": "uuid",
"slug": "new-client-intake",
"name": "New Client Intake",
"description": "Tell us about your practice and any injuries.",
"required": true,
"submitted": false,
"submission_id": null,
"submitted_at": null
}
]
}
GET/api/v1/forms/{id}Bearer token
Get form schema
Render schema (fields, steps, submit label) plus the venue's configured `legal_basis` (`consent` | `contract` | `legitimate_interest` | `legal_obligation`) for a single published form. Use `legal_basis` to render the matching privacy notice and, for a `consent` form, to present its required consent checkbox as the gate it is — a consent-basis submission is refused unless that box was ticked. Scoped to the active org — forms in other orgs return 404.
Parameters, scopes and examples
Path parameters
idstring · required
Form UUID
Response example
{
"data": {
"id": "uuid",
"slug": "new-client-intake",
"name": "New Client Intake",
"description": "Tell us about your practice and any injuries.",
"required": true,
"legal_basis": "consent",
"schema": {
"version": 1,
"fields": [],
"steps": null,
"submit_label": "Submit"
},
"thank_you": {}
}
}
POST/api/v1/forms/{id}/submitBearer token
Submit a form
Submit `{ answers }` for a published form. Validates required fields + types, persists a submission stamped with the caller, and routes it into the unified inbox. `Idempotency-Key` is optional but MUST be a UUID when sent (400 `INVALID_IDEMPOTENCY_KEY` otherwise) — it is both the HTTP replay token and the database ingest request id. The same key with the same answers replays the original response; the same key with different answers writes nothing and returns 409 `IDEMPOTENCY_KEY_REUSE_MISMATCH`, so mint a new key whenever the answers change. Every 201 and every 503 `SUBMIT_RECONCILE_FAILED` returns an `Idempotency-Key` RESPONSE header (mirrored as `error.details.request_id` on the 503) carrying the identity the submission was accepted under — your key when you sent one, the server-generated UUID when you did not. Retry a 503 with that exact value as `Idempotency-Key`: it replays the accepted submission and re-drives only the missing delivery. Retrying without it mints a new identity and files a duplicate. 422 with `details.missing[]` on required-field failures; 422 `CONSENT_REQUIRED` when a consent-basis form was sent without its consent box ticked; 409 `FORM_CONSENT_MISCONFIGURED` when the form itself cannot lawfully collect.
Returns the authenticated member’s appointments with the exact updated_at concurrency token required for cancellation and the owning venue’s id, name, slug and timezone for local date/time presentation, including retained history after membership ends. Supports upcoming/past direction, status, venue narrowing and cursor pagination.
Parameters, scopes and examples
Query parameters
directionstring
upcoming | pastDefault: upcoming
statusstring
Appointment status
organization_idstring
Optional venue UUID narrowing
POST/api/v1/appointmentsBearer token
Book my appointment
Creates a free, pass-covered, or pay-at-venue member appointment. Retries recover the matching durable operation before current availability, named selection or payment policy; keep the same Idempotency-Key and booking details. Recovery is scoped to the authenticated actor and resolved organization, never a user-only HTTP cache. New Any requests retain unnamed intent in their operation hash while storing the concrete assignment. A legacy concrete-only hash cannot establish earlier Any intent and returns a conflict rather than guessing. Fresh named choices require active assignment/membership, selectable_for_named_booking and client_picks policy; disabled names return 409 NAMED_PROVIDER_NOT_SELECTABLE. Send provider_id=any for automatic assignment without removing hidden staff capacity. Explicit room_id is separate from provider_id. Paid-at-booking appointments use the checkout endpoints below. When the venue mode is client_choice, omitted payment_choice defaults to online; venue is allowed only when the canonical quote permits it. X-Organization-ID and a stable Idempotency-Key are required; client communication follows the locked member-transactional policy rather than staff-selectable channels.
GET/api/v1/appointments/{id}Bearer token
Get my appointment
Returns one appointment owned by the authenticated member, including its updated_at concurrency token, rescheduled_to_id replacement pointer, visit_id for a composed visit, and the owning venue’s id, name, slug and timezone for local date/time presentation, including retained history after membership ends. Pending or discarded visit legs are hidden. Open the whole-visit endpoint when visit_id is present. The optional organization_id query narrows the owned result to one venue, including retained history after an offering is removed. Follow replacement pointers using the same venue scope; each target is independently authorized.
Parameters, scopes and examples
Path parameters
idstring · required
Appointment UUID
Query parameters
organization_idstring
Optional venue UUID narrowing; never grants access to another member’s appointment
DELETE/api/v1/appointments/{id}Bearer token
Cancel my appointment
Atomically cancels one owned current appointment. Clients must send the exact rendered updated_at token; a missing token returns 400 VALIDATION_ERROR and a stale token returns 409 STALE_TARGET. Refresh the displayed appointment before retrying. Configured venue self-service cutoffs return SELF_SERVICE_CUTOFF when the remaining time is strictly less than the cutoff; exactly the cutoff remains eligible. Paid/deposit appointments return REFUND_REQUIRED and package legs return PACKAGE_REQUIRES_STAFF until the venue handles the whole-visit/refund workflow. Composed-visit legs return GROUPED_VISIT_REQUIRES_VISIT_ACTION; use the whole-visit routes. X-Organization-ID and a stable Idempotency-Key are required.
Read-only preview of the consequence of cancelling one owned appointment right now. The window and fee come from the service row (services.cancellation_window_hours / cancellation_fee_amount, defaults 24 / 0) — the exact pair the cancel RPC enforces — so the number shown matches the number charged. An appointment with a paid deposit or a linked payment is blocked with blocked_reason "refund_required" rather than previewing a self-service refund; a terminal appointment is blocked "not_cancellable". A Combo Package leg is blocked "package_requires_staff", with package_linked=true, because its payment and change workflow belong to the whole package. The separate optional venue self_service_cutoff_hours restricts when clients may act, independently of late fees: blocked_reason "self_service_cutoff" means staff must help. Exactly the configured number of hours remains eligible. A null cutoff preserves ordinary behavior unless malformed configuration produces a blocked decision; clients must use can_cancel/blocked_reason as authority. Composed-visit legs return can_cancel=false, blocked_reason "grouped_visit_requires_visit_action", visit_linked=true and visit_id; preview and manage the whole visit instead. Pending and discarded legs return 404. Honours the optional x-organization-slug tenant scope; an appointment outside the resolved scope, or belonging to another client, returns 404.
Returns the authenticated member’s server-authoritative effective service price, deposit, amount due at booking, remaining venue balance, payment timing, and payment_at_booking_mode (venue | online | client_choice). Named choices require active service assignment and venue-membership permission. Any or omitted requests return provider_id=null without changing the concrete internal quote. Keep Any intent in subsequent requests. An any provider request resolves through public availability, including active assignments and membership, venue-local hours, service options, location and occupancy, before pricing. Optional payment_choice=online|venue is honoured only when the venue mode is client_choice; omitted choice defaults to online so older clients keep paying at booking. A configured deposit still requires the deposit online. Requires X-Organization-ID.
Parameters, scopes and examples
Query parameters
service_idstring · required
Service id
provider_idstring
Selectable provider UUID or any; omitted is Any. Unnamed responses return provider_id=null
start_timestring · required
ISO appointment start
location_idstring
Optional location UUID; required for a location-restricted service. Must match availability and checkout.
variant_idstring
Optional service option UUID; the same option must be used for availability and checkout.
pass_idstring
Optional owned pass UUID; only validated service coverage affects the quote.
payment_choicestring
Optional online | venue. Omitted = pay now when the venue lets the client decide.
requested_currencystring
Optional uppercase ISO currency for an enabled home-country service/option book. Requires an uncovered service and named selectable professional (facilities use Any). Returns selected_currency_quote for exact acceptance. Service country enablement remains closed pending cross-surface qualification.
Claims a durable, tenant-bound operation before creating an account-pinned Stripe PaymentIntent. Accepts a selectable provider UUID or any. Fresh operations check public selection before claim/payment; existing operations keep the frozen provider after admin settings change. Returns PaymentSheet credentials and the frozen operation quote, not current venue policy. New unnamed operations return quote.provider_id=null while retaining the actual provider internally. Retry the same intent/key; changing named/any intent or an explicitly supplied payment choice conflicts. Older operations without snapshot metadata remain resumable. X-Organization-ID and a stable Idempotency-Key are required.
Parameters, scopes and examples
Exact live slot and provider selection. provider_id may be a concrete UUID or "any"; the server freezes one provider and its effective price before payment. Optional payment_choice online|venue follows shared quote policy. A choice requiring no online payment returns APPOINTMENT_PAYMENT_NOT_REQUIRED; use unpaid create after review. Optional selected_currency is {currency, acceptedQuote}, where acceptedQuote is the exact selected_currency_quote returned by review. Retain the complete original body and key on every retry. Home-country only; enabled owned service/option books and migration-backed claim required. Finer-than-two-decimal currencies, professional custom prices, offers, passes, bundles and composed visits refuse. Activation remains closed pending all consumer qualification.
Retrieves the exact account-scoped PaymentIntent, requires processor status succeeded, creates the appointment idempotently, and atomically links payment/accounting. Legacy operations recheck live policy; accepted selected-currency operations use their frozen original price/tax while canonical scheduling and eligibility remain authoritative. Slot conflicts are compensated with the original idempotent refund; 202 finalizing states retain the original key. The Stripe webhook uses the same reconciler.
Read public appointment times with named or Any intent
The slug resolves the authoritative venue. A named provider must be an active assigned venue member allowed by the service policy and named-booking flag. Any or omitted provider keeps all operational capacity and returns one deterministically priced slot per instant with provider_id=any, provider_name empty and provider_selection_intent=any. Named slots retain their permitted UUID/name and provider_selection_intent=named. Room-only slots remain opt-in and use provider_id=null, resource_kind=room and a real room_id, never an aliased provider. Failed reads return 503, denied names 409; responses are private/no-store. Staff fulfillment and existing booking history use separate authorized operational reads.
Parameters, scopes and examples
Path parameters
slugstring · required
Public venue slug
Query parameters
service_idstring · required
Active service UUID in this venue
datestring · required
Venue-local calendar date YYYY-MM-DD
provider_idstring
Selectable professional UUID or any; omitted means Any
Read a venue service and its named booking choices
Public, rate-limited service detail. The venue slug resolves the owning organization; active service and provider assignments are scoped to that venue. providers contains only active members selectable_for_named_booking when the service policy is client_picks (null policy retains that default). any_available and admin_assigns return no named choices. Team-page listing is independent. Provider-read failure returns 503 PROVIDERS_UNAVAILABLE, not an unfiltered list. This endpoint does not determine Any capacity, quote pricing, booking authorization or historical provider identity.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Available only when appointment detail returns paid_self_service_available. Atomically attaches the existing appointment and verified payment to a complete-visit management record; returns data {visit_id}. It does not book or charge again. The original checkout must have frozen explicit owner-configured self-service refund terms. Legacy payments, prepaid pass/series/bundle bookings and unsupported payment evidence retain staff management. The venue cutoff still applies to subsequent cancellation or movement. Checkout quote refund_terms describes the accepted single-appointment terms before payment.
Parameters, scopes and examples
Path parameters
idstring · required
Owned appointment UUID
POST/api/v1/appointments/visits/planBearer token
Plan my complete appointment visit
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send date (YYYY-MM-DD), ordered services, optional provider_id/location_id and, for a new booking, optional provider_selection_intent. With intent "any" (or provider_id "any") candidates return provider_id "any", an empty provider_name and provider_selection_intent "any": times and prices stay real, the professional is assigned by the venue and can include people not offered for named choice. With intent "named" and a provider_id, every selected service must allow that professional by name (409 NAMED_PROVIDER_NOT_SELECTABLE, 503 PROVIDER_SELECTION_UNAVAILABLE); candidates carry that UUID and provider_selection_intent "named". Without intent the earlier concrete candidate contract is unchanged. An owned from_visit_id move never takes intent. One service may probe capability; new bookable candidates require at least two. An owned confirmed visit adopted from an eligible paid single appointment may retain its one service when moving. An owned from_visit_id may seed the original services with services: [] for a move or rebooking. Returns capability, max_services/max_services_per_visit, refund_terms and server-calculated candidates with provider_id, location_id/location_name, start_time/end_time, total_price_amount, amount_due_at_booking, currency and each leg. An existing confirmed visit can be fulfilled while new sales are closed.
POST/api/v1/appointments/visitsBearer token
Book my complete unpaid appointment visit
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Services are an ordered array of 2–8 {service_id, variant_id?}; send provider_id, concrete location_id, start_time and the planner’s expected_total_price_amount, expected_amount_due_at_booking and expected_currency. The server calculates prices, buffers and availability again. New bookings may add provider_selection_intent: "any" (provider_id "any"; the venue assigns the professional the plan priced) or "named" (a professional the venue allows clients to pick by name on every selected service; otherwise 409 NAMED_PROVIDER_NOT_SELECTABLE, 503 PROVIDER_SELECTION_UNAVAILABLE when the check cannot be read). A concrete provider_id without intent keeps the earlier contract and is not treated as a named pick. Intent, when sent, is part of the idempotent request and cannot change on retry. Returns data as the complete visit detail. All legs become visible together. A required deposit/payment returns 402 PAYMENT_REQUIRED; use checkout.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Services are an ordered array of 2–8 {service_id, variant_id?}; send provider_id, concrete location_id, start_time and the planner’s expected_total_price_amount, expected_amount_due_at_booking and expected_currency. The server calculates prices, buffers and availability again. New bookings may add provider_selection_intent: "any" (provider_id "any"; the venue assigns the professional the plan priced) or "named" (a professional the venue allows clients to pick by name on every selected service; otherwise 409 NAMED_PROVIDER_NOT_SELECTABLE, 503 PROVIDER_SELECTION_UNAVAILABLE when the check cannot be read). A concrete provider_id without intent keeps the earlier contract and is not treated as a named pick. Intent, when sent, is part of the idempotent request and cannot change on retry. Freezes the accepted composition, currency, deposit amount, refund terms and payment execution. Returns operation_id, client_secret, customer_id, ephemeral_key, stripe_account_id and quote for PaymentSheet/web confirmation. Credentials are transient. already_booked=true is a successful replay. Payment covers the whole visit; unavailable fulfillment is durably refunded.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send operation_id from checkout. Returns data {status:"booked",visit}. 202 VISIT_FINALIZE_RETRY or VISIT_REFUND_PENDING remains pending; 409 PAYMENT_REQUIRES_ACTION requires returning to payment with the same checkout key. PAYMENT_CANCELLED and VISIT_PAYMENT_REFUNDED are terminal. A webhook and periodic worker reconcile payment independently of the client.
GET/api/v1/appointments/visits/{id}Bearer token
Read my complete appointment visit
Requires the member JWT and verifies client ownership independently of active membership. Legacy requests retain X-Organization-ID narrowing. Optional scope=owned ignores the active header and accepts an explicit organization_id UUID filter; merged identities remain confined to their authorized venue. Returns organization_id and an owning organization {id, slug, timezone} relation, plus the full itinerary, exact updated_at, payment_status, amount_paid_minor, frozen refund_terms, cancellation preview and rescheduled_from_visit_id/rescheduled_to_visit_id. Historical visits remain accessible when a venue removes an offering. Provider credentials and internal payment keys are never returned.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Optional reason. Cancels every leg in one transaction and returns visit fields plus cancellation_result under data. An optional venue self-service cutoff is checked against the first service after row locks; exactly the cutoff remains eligible. Paid/deposit cancellation requires the accepted venue terms to permit self-service. Refund failure remains VISIT_REFUND_PENDING and the worker retries the same refund.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Send provider_id, location_id, start_time and the planner’s expected_total_price_amount, expected_amount_due_at_booking, expected_currency. The original service/variant order is retained. An overlapping move releases old slots and confirms every new leg in the same transaction; any conflict preserves the complete original visit. A price/deposit change requires a new quote. Returns the replacement visit detail with reciprocal history pointers.
Parameters, scopes and examples
Path parameters
idstring · required
Whole appointment visit UUID
GET/api/v1/me/treatmentsBearer token
Read my treatment history
Requires the member JWT and X-Organization-ID. Returns contract_version 1, records[] (appointment, service name, provider display name, products used with quantity/unit, dates) and patch_tests[] (date, expiry, result, product). Formulas, staff notes, photos, internal ids, costs and compensation are never returned; the platform DSR export remains the portability path.
Atomically resolves all selected resources within the API-key venue, updates independent class/workshop allowance buckets, and reconciles active participants.
Applies or resets class/workshop limits and validity windows for one or many participants in the same tenant-scoped course cohort.
Parameters, scopes and examples
Required scopes
write:courses
Path parameters
courseIdstring · required
Course cohort UUID
POST/api/v1/courses/enrollment-statusAPI key
Course enrollment status for a venue site
COURSE-ONLINE-01 — read-only roster status for one course of the API-key venue: participant contact, enrollment/payment status and attendance mode (`in_person` | `online`). Optional `X-Organization-ID` must equal the key venue (403 otherwise). Filters: `attendance_mode`, `enrollment_ids` (1-100). Keyset pagination by enrollment id, 100 per page; pass `meta.next_cursor` back as `cursor` while `meta.has_more` is true. `updated_at` is the last change time (rows untouched since migration 20261020000264 report their latest lifecycle timestamp). Another venue's course is 404.
Member JWT and venue membership required. Retrieves the caller-owned linked PaymentIntent using its frozen account, customer, amount, currency, provider and regional context. No financial write, claim reset, PaymentIntent creation, or idempotency-key change. Optional plan, purchaser_type and attendance_mode must match the frozen enrollment. A successful null response means no checkout exists; read failures never authorize a new purchase.
Add a paid-claim website applicant to a managed course roster
Trusted server-to-server bridge for venue application forms. Resolves the API-key tenant, the pass type's managed course, the applicant client/membership, and an optional localized track name; then creates or annotates an active roster enrollment and books its upcoming course sessions. Self-reported paid_deposit/paid_full values are retained as claims requiring reconciliation and never fabricate or overwrite BookingBible payment ledger state. API-key only (write:members), rate-limited, Idempotency-Key required.
Pay for a course enrollment (early-bird-aware price, or the deposit when required). Requires an Idempotency-Key header and returns a Stripe PaymentIntent client_secret + customer_id + ephemeral_key + stripe_account_id for the Payment Sheet. The enrollment is created `unpaid`; on `payment_intent.succeeded` it flips to paid/deposit_paid and its sessions are booked (deduped on the payment-intent id). Gated on membership + venue legal docs. Member-JWT. COURSE-SUITE — the body additionally accepts optional `plan` (payment-plan id), `purchaser_type` (`individual`|`company`), and `company` details (name/VAT/address) for VAT-by-purchaser + debtor invoicing. A supplied plan must exactly match a currently offered server-side plan; only an omitted property uses legacy/default behavior. The GET `/api/v1/courses/{id}` course detail additionally returns a `staff` array — `[{ role, name, title_label, photo_url, show_on_landing_page }]` — for the landing-page teaching team (COURSE-SUITE-02 multi-trainer). CV3-03 — the GET detail also returns `payment_plans` (`{ plans: [{ id, kind, installment_count? }], collection_method }`, the normalized plan OPTIONS this purchase route accepts as `plan`) and, for an authenticated Bearer caller with an enrollment, `viewer_enrollment` (`{ id, enrollment_status, payment_status, payment_plan, amount_paid, total_amount, balance, installments: [{ installment_number, amount, due_date, status }] }`; the response is always `Cache-Control: private, no-store`). COURSE-O30-01 — in a Danish age-split venue a course with a 30+ price is charged by the buyer age band: under 30 (date of birth + complete VAT evidence, 0 % VAT) pays `price`, everyone else (including a missing DOB/evidence or a company) pays the 30+ `price_o30_override` with standard VAT inside it; the band is chosen before early bird, proration, offers and the deposit/instalment split. The GET detail adds `age_band_prices` (`{ under30, over30, earlyBirdApplied }` or null) and, for a signed-in caller, `viewer_age_band_status` (`under_30`|`over_30`|`missing_dob`|`evidence_required`). New error: 503 `AGE_PRICING_UNAVAILABLE` when the band cannot be verified. COURSE-ONLINE-01 — optional body `attendance_mode` (`in_person` default | `online` on a hybrid course with an online price; own price book and online VAT category; an online seat never grants a studio place); 422 `ATTENDANCE_MODE_UNAVAILABLE`, 409 `ATTENDANCE_MODE_CONFLICT`, online seats full → 409 `COURSE_FULL`; the response echoes `attendance_mode`. The GET detail adds `attendance_options: [{ mode, available, price, early_bird_price, currency, age_band_prices, viewer_price?, spots_left, vat_rate }]` and `viewer_enrollment.attendance_mode`. `/courses/{id}/sessions` also carries additive per-session `course_identifiers` (`{course_id, label, tone, course_name}`, public courses only; `[]` when none), so a workshop page can tell which programme (for example 8W or 18W) each interleaved date belongs to; the venue course list adds `kind` (`course`|`workshop`) to each item. GET detail and `/courses/{id}/sessions` also add `online_replay: { enabled, until, open }` (replays for online participants, enforced by BB). COURSE-ONLINE-O30-01 — online group instruction follows the same Danish under-30 / 30+ rule: a hybrid course or workshop with an online 30+ price (`online_price_o30_override`, optional `online_early_bird_price_o30_override`) charges an online seat / online drop-in (`POST /api/v1/workshops/{id}/occurrences/{instanceId}/purchase` with `attendance_type: online`) by the buyer band — under 30 (DOB + VAT evidence, 0 % VAT) pays `online_price`, everyone else pays the 30+ online price with the online VAT category rate inside it; without an online 30+ price the online seat keeps its single online price for every age. Additive DTO fields: `attendance_options[mode=online].age_band_prices` (`{ under30, over30, earlyBirdApplied }` for the online book, else null) and a band-aware `viewer_price`; `viewer_age_band_status` is also returned when only the online book is split; `/courses/{id}/sessions` `workshop.prices` adds `physical_age_band_prices` and `online_age_band_prices` (`{ under30, over30 }` or null). Top-level `age_band_prices` stays the in-studio book. See docs/api/NAMASTE_SITES_API.md section 17.
Receives Apple-signed App Store Server Notifications V2 (Production and Sandbox URL). Both the outer notification and nested transaction signatures are verified (x5c chain to Apple’s roots, bundle id, app id, environment). Each notificationUUID is stored and applied once: renewals extend the pass and record an App Store sale, expiry, refund and revoke end it, a reversed refund restores it. Refunds become venue refunds. Sandbox never records revenue. Returns 500 on a transient failure so Apple retries.
Parameters, scopes and examples
App Store Server Notifications V2 signed payload
Request body
{
"signedPayload": "<Apple signed notification JWS>"
}
Calendar1 documented operation
GET/api/v1/calendarBearer or API key
Unified calendar feed
List every event on the unified calendar (classes, appointments, private events, streams, blocked time, instructor unavailability, blackouts, room rentals, maintenance, staff shifts, open gym) in a date range. JWT (any staff role) returns the org feed; API key with read:calendar returns the same. Filters: room_id, staff_id, location_id, brand_id, sources (comma-separated), only_blocking.
List maintenance slots in a date range. API key with read:maintenance scope. Filters: start, end (ISO datetime), room_id, status, limit (1-200, default 50).
Create a maintenance slot. API key with write:maintenance. Body: maintenance_type (preventive | corrective | inspection | deep_clean | equipment | renovation), title, start_time, end_time, plus optional priority, room_id, equipment_id, blocks_room (default true), assigned_staff_id, vendor_name, vendor_contact, estimated_cost, notes. Idempotency-Key header honored. When blocks_room is true and a room is set, conflicts against classes / appointments / private events / streams / room rentals / other maintenance return 409 with the conflict list. Emits maintenance.scheduled.
Parameters, scopes and examples
Required scopes
write:maintenance
Maintenance creation payload
Request body
{
"maintenance_type": "deep_clean",
"title": "Quarterly studio deep clean",
"start_time": "2026-05-01T20:00:00Z",
"end_time": "2026-05-01T22:00:00Z",
"room_id": "uuid",
"priority": "normal",
"blocks_room": true
}
Staff40 documented operations
GET/api/v1/staff/scheduleBearer token
My teaching schedule
Instructor's classes. Optional scope=own|partner|all; every row includes origin venue metadata and origin.timezone so apps bucket collaboration classes in the owning venue's local day.
GET/api/v1/staff/earningsBearer token
My earnings
Compensation, tips, and commissions broken down by period and class. tips_settled_via_collaboration is additive visibility for gratuities paid on a practitioner statement and is deliberately excluded from tips_received and total.
GET/api/v1/staff/classes/{id}/rosterBearer token
Class roster
View attendee list for a class the instructor is assigned to. Returns class and booking updated_at CAS tokens, venue-local day_state, and historical_capabilities. include_historical_records=true additionally exposes terminal roster rows and requires scheduling.manage_history. Guest rows include guest_host_name when a host is linked. Each attendee also carries the additive notification_availability block ({email|sms|push: {available, reason_code, reason}}) so a staff notify picker can enable a channel and state the precise reason an unusable channel is disabled; it is null for a guest booking or a degraded read, and a null block leaves every channel disabled (fail-closed, never an unintended send). The same block additively carries booking_removal and waitlist_promote ({clients: {email|sms|push: {available, reason_code, reason, recipient_id, reachable, total, blocked, reach_label}}}). Each attendee carries online_attendance: null for in-studio bookings; for streaming bookings { state: watching | attended | not_yet | null, playback_state, first_played_at, last_heartbeat_at }, the same server-computed block as the admin check-in roster.
Check one attendee into a class the caller is assigned to. Delegates to the same check-in core as the admin check-in route. Requires staff_portal.roster.view plus booking.checkin (any class in the venue) or staff_portal.check_in.own_classes (assigned or substitute instructor only). Client notification is default-silent: only an explicit non-empty notify.channels selection delivers, and it requires notifications.send plus a per-channel availability preflight before the mutation. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED — use the historical-corrections route. A streaming (online) booking is attended automatically when its stream plays: it is refused with 409 ONLINE_ATTENDANCE_AUTO unless the body sets mark_attended_override: true (the audited "Mark attended" override). Idempotency-Key is honored.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
bookingIdstring · required
Class booking ID
Optional canonical client-notify selection (omit to stay silent); mark_attended_override for a streaming booker
Mark one attendee of an assigned class a no-show, applying the venue no-show consequence. Delegates to the same no-show core as the admin route. Requires staff_portal.roster.view plus bookings.mark_no_show or staff_portal.check_in.own_classes; an explicit user denial of bookings.mark_no_show vetoes the own-class alternative. Default-silent client notification with notifications.send authorization and a per-channel preflight before the fee-producing write. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED. Idempotency-Key is honored so a retry replays instead of charging twice.
Current status, outstanding no-show fee, venue fee amount, clip consumption, currency and per-channel notification availability for one attendee of an assigned class. Also returns booking_updated_at, can_check_in_after_cutoff, ended_class_confirmation_required, credit_applicable and no_show_forfeits_clip. Requires staff_portal.roster.view and class assignment. Read-only; a degraded availability read returns notification_availability: null rather than failing the correction sheet.
Move one attendee of an assigned class to checked_in, no_show, confirmed (undo) or removed. Delegates to the same attendance-override core as the admin route and parses the same field set. Requires staff_portal.roster.view plus the target-specific grant: booking.checkin or staff_portal.check_in.own_classes for checked_in/confirmed, bookings.mark_no_show or staff_portal.check_in.own_classes for no_show, booking.cancel_member or staff_portal.cancel.own_classes for removed. Explicit user denials of the primary action veto own-class alternatives; org-wide check-in actors must hold the specific no-show/removal grant. refund_fee is refused with REFUND_REVIEW_REQUIRED (fee refunds go through the reviewed refund path). Default-silent client notification with notifications.send authorization and a per-channel preflight before the correction. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED. UUID Idempotency-Key REQUIRED, expected_updated_at and ended_class_confirmed are part of the reviewed body; changed retry intent returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Success preserves operationId, bookingUpdatedAt, creditDelta, seatDelta and effectsStatus; pending/held effects do not undo the correction.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
bookingIdstring · required
Class booking ID
Target status plus the canonical client-notify selection
Apply check_in, no_show or undo to up to 200 selected attendees of an assigned class, one per-booking core call each. Requires staff_portal.roster.view plus the action grant (booking.checkin or staff_portal.check_in.own_classes; bookings.mark_no_show or staff_portal.check_in.own_classes for no_show). Explicit user denials of the primary action veto own-class alternatives. Results are truthful per booking: succeeded lists only bookings whose write returned success and failed[] carries each id with its own reason, including a booking whose selected notification channel is unavailable (that booking is not mutated). A selection containing a booking outside this class rejects the whole batch with BOOKING_SCOPE_MISMATCH before anything is attempted. Default-silent client notification with notifications.send authorization. Explicit successful check_in choices dispatch attendance_corrected from the booking-owning venue; failed, empty or legacy-only requests never dispatch. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED. UUID Idempotency-Key REQUIRED. Check-in skips streaming (online) bookings, whose attendance is recorded on stream playback, and lists them in an additive skipped array ({ booking_id, code: ONLINE_ATTENDANCE_AUTO, reason }) instead of failing them.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
Action, roster booking ids, and the canonical client-notify selection
Send one email or SMS to an explicit subset of an assigned class's roster through the canonical consent/suppression-aware bulk senders. Requires staff_portal.roster.view plus members.contact or staff_portal.contact.own_classes, and the can_view_client_contact_info membership toggle. Submitted booking_ids are intersected server-side with this venue's contactable roster for this class; stale, cancelled and foreign ids are dropped and counted in skipped. Caller-supplied contact data is never accepted. Subject is required for email. Empty intersection returns 422 NO_RECIPIENTS. Idempotency-Key is honored so a retry cannot fan out twice.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
Channel, roster booking ids, and message
Request body
{
"channel": "email",
"booking_ids": [
"00000000-0000-4000-8000-0000000000b1"
],
"subject": "Class update",
"message": "Hi {{first_name}} — here is an update about your class."
}
Staff JWT, staff_portal.roster.view, scheduling.manage_history and ordinary correction permissions. Enforces assigned-instructor and tenant/class/booking binding. Query operation plus status (omit for invalidate). Returns the same signed data.preview contract as the admin booking review; no attendance or financial mutation.
Assigned staff historical correction through the canonical admin class-booking engine, with additive roster/history/ordinary permissions. Idempotency-Key must be a UUID. expected_class_updated_at and expected_updated_at are compare-and-set tokens. Existing-booking reasons and legacy REWRITE are optional. GET review_token is required for explicit fee refund/waive, consumed clip return or client/instructor Email/SMS/Push selections; finances and notifications are default-silent. Extra billing/pass/send permissions are checked before mutation. Shared notification_batch_id defers instructor delivery to the canonical admin batch-finalization endpoint and produces one summary per instructor/channel. Returns exact CAS and separate financial/delivery outcomes; failed or ambiguous sends remain held.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
bookingIdstring · required
Class booking ID
Bounded historical roster correction with UUID Idempotency-Key
Provider-scoped atomic correction for a past class. The active provider must be assigned to the existing class; retrocreate must assign that provider directly, and assignment corrections must retain them. Assignment-only corrections preserve linked operational and financial records; other linked-class corrections require specialist review. Requires schedule.view_own, scheduling.manage_history, scheduling.manage, UUID Idempotency-Key, CAS evidence for existing rows, past effective_at, and typed REWRITE. Existing tenant, location and instructor erasure guards remain enforced. Notifications are always silent.
Mark or unmark a participant present for a course session ({user_id, present}). Idempotent; writes the same attendance store the web roster uses. Supports Idempotency-Key.
Send an email or SMS to course participants (audiences: enrolled, waitlisted, all, by track, by payment status, hand-picked). Requires course-manage scope; rate-limited; supports Idempotency-Key (retries never double-send).
Parameters, scopes and examples
Path parameters
idstring · required
Course ID
Message
Request body
{
"channel": "email",
"subject": "Bring a mat tomorrow",
"message": "Hi everyone — please bring your own mat to tomorrow’s session.",
"audience": {
"kind": "enrolled"
}
}
GET/api/v1/staff/availabilityBearer token
List my unavailable dates
Calling staff member's current and future unavailable dates for the selected venue. Permission: staff_portal.availability.
POST/api/v1/staff/availabilityBearer token
Set availability
Add or update unavailable dates for the calling staff member. Permission: staff_portal.availability.
Strict partial update of a current/future window using the exact updated_at token returned by GET. Caller must own the window; another instructor requires staff.edit. A stale token returns 409 STALE_TARGET. Existing or target ranges touching venue-local history fail closed until the dedicated executor is installed.
Parameters, scopes and examples
Path parameters
idstring · required
Window ID
Concurrency token plus one or more changed window fields
Sets is_active=false on a current/future window using the exact updated_at token returned by GET, after tenant and owner-or-staff.edit authorization. A stale token returns 409 STALE_TARGET. Historical ranges fail closed until the dedicated executor is installed.
Atomic, immutable-ledger correction for a past recurring availability window owned by the active staff member. Requires availability.manage_history, staff_portal.availability, UUID Idempotency-Key, expected_updated_at plus expected_is_active for existing rows, a past effective_at, and typed REWRITE. Notifications are always silent.
Parameters, scopes and examples
Path parameters
idstring · required
Availability window ID
Self-owned historical availability correction
Request body
{
"operation": "availability_window.invalidate",
"expected_updated_at": "2026-08-20T09:00:00.000Z",
"expected_is_active": true,
"history_reason": "Approved rota confirms that this window did not apply",
"history_confirmation_token": "REWRITE",
"effective_at": "2026-08-10T10:00:00.000Z",
"intent": {}
}
GET/api/v1/staff/substitute-poolBearer token
Read substitute-pool opt-in
Returns { enabled, updated_at } for the calling user's active org.
PUT/api/v1/staff/substitute-poolBearer token
Toggle substitute-pool opt-in
Set whether the calling user is available to be auto-suggested as a substitute. Body: { enabled: boolean }. Emits substitute_pool.opt_in_changed.
Parameters, scopes and examples
Opt-in state
Request body
{
"enabled": true
}
GET/api/v1/staff/appointmentsBearer token
My appointments
Cursor-paginated list of the calling provider's appointments, including venue currency and the same client name and 80-character provider-note preview shown in the web staff list. Contact details are not exposed. Query params: cursor (opaque next_cursor; legacy ISO timestamps are temporarily accepted), limit (1..100, default 25), status (one of the appointment status strings), direction (upcoming|past, default upcoming).
Parameters, scopes and examples
Query parameters
cursorstring
Opaque next_cursor returned by the previous page
limitnumber
Page size (1..100)Default: 25
statusstring
Optional status filter
directionstring
upcoming | pastDefault: upcoming
GET/api/v1/staff/appointments/{id}Bearer token
My assigned appointment detail
Provider-scoped detail with updated_at CAS evidence, server-authoritative historical review flags, privacy-gated client contact fields, and exact per-channel notification availability. The route always binds provider_id to the caller.
PATCH/api/v1/staff/appointments/{id}Bearer token
Act on my assigned appointment
Provider-scoped check_in, start, complete, no_show, cancel, or reschedule. Requires the action-specific appointments.*_own permission, exact expected_updated_at, and Idempotency-Key. Client notifications are default-silent and require explicit notification_channels plus notifications.send and server preflight. Past/terminal mutations fail closed until the appointment historical executor is installed.
Parameters, scopes and examples
Exact provider lifecycle intent and caller-rendered concurrency snapshot
Provider-scoped form of the dedicated atomic appointment-history command. Requires staff_portal.appointments, appointments.manage_history, the ordinary operation permission, a UUID Idempotency-Key, REWRITE attestation, and exact expected_updated_at plus expected_status CAS for existing rows. The existing appointment and any retrocreate or assignment target must remain assigned to the active provider. Corrections are always silent. Returns 503 HISTORICAL_EXECUTOR_UNAVAILABLE without table-call fallback until correct_appointment_historical is installed.
Parameters, scopes and examples
Required scopes
appointments.manage_history
Path parameters
idstring · required
Appointment UUID
The same closed appointment correction body as the admin route
Calling staff member's own non-instructor shifts (reception, cleaning, manager, front desk) in a date range. Use ?from=&to= ISO datetimes; defaults to next 14 days.
Parameters, scopes and examples
Query parameters
fromstring
Start ISO datetimeDefault: now
tostring
End ISO datetimeDefault: +14 days
POST/api/v1/staff/shifts/clock-inBearer token
Clock in
Clock in to an own staff shift. Allowed from 15 min before scheduled start through 30 min after. Sets status to in_progress and stamps clock_in_at. Emits shift.clock_in.
Parameters, scopes and examples
Shift to clock in to
Request body
{
"shift_id": "uuid"
}
POST/api/v1/staff/shiftsAPI key
Create a staff shift
Create a non-instructor staff shift. API-key only (write:staff). Body: start_time, end_time, optional staff_id, shift_type (regular | overtime | on_call | training | meeting), break_minutes, role_required, location_id, hourly_rate, notes. Idempotency-Key header honored. Emits shift.created (and shift.assigned if a staff_id is set).
Soft-cancel a shift (sets status=cancelled, preserves audit/payroll references). JWT (admin/manager) or API key with write:staff. Use ?reason= to attach a cancellation reason to the audit row.
Parameters, scopes and examples
Required scopes
write:staff
Path parameters
idstring · required
Shift UUID
Query parameters
reasonstring
Cancellation reason (free text)
POST/api/v1/staff/clockBearer token
Clock in or out (unified)
Unified clock-in/out endpoint. JWT only — resolves the staff member from the session token. Body: { action: "in" | "out", shift_id }. On clock-out the response includes actual_hours and total_pay. Emits shift.clock_in or shift.clock_out.
Parameters, scopes and examples
Clock action
Request body
{
"action": "in",
"shift_id": "uuid"
}
POST/api/v1/staff/shifts/clock-outBearer token
Clock out
Clock out of an in-progress staff shift. Computes actual_hours, actual_break_minutes, and total_pay (when hourly_rate is set). Returns warnings for break/EU compliance issues. Emits shift.clock_out.
Parameters, scopes and examples
Shift to clock out of
Request body
{
"shift_id": "uuid"
}
GET/api/v1/staff/time-offBearer token
My time-off requests
Latest 100 own time-off requests across all statuses.
POST/api/v1/staff/time-offBearer token
Request time off
Submit a new time-off request. Always created with status=pending. Manager approval/decline happens via the admin panel. Emits time_off.requested.
Create or update a lead for the venue. Public rate-limited (10 req/min/IP) or API-key authenticated (write:leads). One lead per (organization_id, lower(email)), race-proof: concurrent posts converge on one row, provided fields populate blanks and existing non-null values are preserved (first touch wins, including brand_id). Optional brand_id must be an active brand of the resolved organization (422 BRAND_NOT_FOUND). source is one of exit_intent, landing_page, referral, manual, import, api, website_form, newsletter, embed_form, offer_popup. An optional consent block (marketing_email / marketing_offer_email / marketing_sms / marketing_push, granted: true only, policy_version, mechanism, the exact prompt_text, locale; source_ip / user_agent honoured only from an API-key caller; without an API key only checkbox or button, else 400 CONSENT_MECHANISM_NOT_ALLOWED) is recorded through the canonical consent engine against the lead, with the prompt text kept verbatim, before lead.created fires, and echoed as consent_id; if it cannot be recorded the call answers 500 CONSENT_RECORD_FAILED, the lead is kept and lead.created is not sent. An unauthenticated caller always receives the same shape ({ email, source, accepted, consent_id }) whether or not the email was already a lead; an API-key caller receives the stored row with brand_id and created (true only when this call created the row). A 500 never carries database detail. Fires the lead_captured analytics event and emits a lead.created webhook with the full record (including brand_id) plus an attribution object (utm_*, fbclid, gclid, landing_page, referrer).
Parameters, scopes and examples
Required scopes
write:leads
Lead payload. Org resolves from API key > X-Organization-ID header > subdomain > organization_id.
List leads for the API key's organization. API key only (JWT not permitted). Requires the read:leads scope.
Parameters, scopes and examples
Required scopes
read:leads
Query parameters
searchstring
Search by email, first_name, or last_name
sourcestring
Filter by source (website_form, exit_intent, referral, etc.)
statusstring
Filter by status (new, contacted, converted, unsubscribed)
pageinteger
Page numberDefault: 1
limitinteger
Items per page (max 100)Default: 20
Events5 documented operations
POST/api/v1/eventsPublic
Track an analytics event
Record a server-side analytics event into user_events. Public rate-limited (60 req/min/IP) or API-key authenticated (write:events). For conversion event names (purchase, subscribe, refund, lead_captured) we additionally fire Meta CAPI + GA4 MP when the venue has pixel credentials configured.
Parameters, scopes and examples
Required scopes
write:events
Event payload. UTM + click-id + page URL get merged into event_properties.
Creates or updates the verified member’s RSVP for a scheduled community-event UUID through the shared web/member core. Requires active owning-venue membership, enabled community_events and an upcoming RSVP-only event. Send X-Organization-ID for the event venue and a stable Idempotency-Key; identical retries preserve the request and response. Guest limits and capacity may return waitlist. Returns data.rsvp with the effective status. The canonical SQL operation serializes capacity and commits the RSVP and replay receipt together. REQUEST_MISMATCH is 409; REQUEST_UNCONFIRMED is 503 and requires the original key/body.
Parameters, scopes and examples
Path parameters
idstring · required
Event booking id
Status + optional guest count + notes.
Request body
{
"status": "going",
"guest_count": 1,
"notes": "Bringing my partner"
}
Venue event catalog or scheduled community sessions
Without view, returns the public event-type catalog: active, website-visible types with id, name, slug, description, short_description, category, pricing_model, base_price, per_person_price, min/max_participants, default_duration_minutes, image_url, location_type, featured, is_active and the purchase_channel routing fields. view=community_sessions returns upcoming scheduled occurrences of active, website-visible community types when the venue module is enabled. Session id and slug are the booking UUID; organization_id, organization_slug and timezone identify the owning venue. Attendance includes guests. No booking contact/payment/admin fields are returned. In both modes purchase_channel follows the caller-aware `X-App-Digital-Content` rule (a `both` location is native only for `none`); the catalog cache varies on that header.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue slug
Query parameters
viewstring
community_sessions for the native scheduled-event DTO
With view=community_sessions, eventSlug must be a scheduled-session UUID owned by this venue, with the same public visibility and module gates as the session list. Returns 404 for unavailable sessions; read failure is an error, not an empty event. Without view, retains the legacy event-type slug detail and pricing tiers.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue slug
eventSlugstring · required
Event-type slug or, in community_sessions mode, scheduled-session UUID
Query parameters
viewstring
community_sessions for the native scheduled-event DTO
Create an open-gym session for a client. Validates an active pass with allow_open_gym=true and the access schedule. JWT users self-check in; API keys must include user_id.
Parameters, scopes and examples
Required scopes
write:bookings
Optional location, source, pass override, and notes.
Returns the latest version of each active required waiver for the authenticated client in the validated X-Organization-ID venue, including the exact markdown body to display and the latest version the client signed. `body_md` is canonical; `body` is the native-app compatibility alias with the same value.
Returns active public/member clubs in the selected venue plus active invite-only memberships. is_member is true only for active membership; membership_status preserves pending/invited/left/removed.
Removes the caller's own club membership. Idempotency-Key supported; leaving a club you are not in is a no-op success. Emits club.member_left (audit + webhook).
Parameters, scopes and examples
Path parameters
idstring · required
Club id
Response example
{
"data": {
"ok": true
},
"error": null
}
DELETE/api/v1/clubs/{id}/membershipBearer token
Leave a club (membership alias)
REST-shaped alias for POST /clubs/{id}/leave used by the mobile clubs contract — identical behavior (Idempotency-Key, no-op success when not a member, club.member_left emit).
Parameters, scopes and examples
Path parameters
idstring · required
Club id
Response example
{
"data": {
"ok": true
},
"error": null
}
GET/api/v1/clubs/{id}/membersBearer token
List active club members
Returns active members for a club in the selected venue. Invite-only rosters require the caller to be an active club member. Display names and avatars respect each member’s public-profile and privacy settings.
Members can suggest leading a new club when the venue has opted into suggestions. The suggestion appears in the admin review queue; this endpoint does not send email.
Parameters, scopes and examples
Club details + why the suggester wants to lead.
Request body
{
"name": "Early Birds",
"category": "running",
"description": "Morning runners group",
"why_lead": "I run every morning and want company"
}
POST/api/v1/clubs/suggestions/{id}/approveBearer or API key
Approve a club suggestion (admin)
Approves a suggestion, creates the club, auto-assigns the suggester as leader, and posts to the feed. The member sees the status in My suggestions; this endpoint does not send email.
Parameters, scopes and examples
Path parameters
idstring · required
Suggestion id
Optional reviewer notes.
Request body
{
"notes": "Looks great — approved."
}
Response example
{
"data": {
"club_id": "uuid"
},
"error": null
}
POST/api/v1/clubs/suggestions/{id}/rejectBearer or API key
Reject a club suggestion (admin)
Rejects the suggestion and saves the reason for the member to read in My suggestions; this endpoint does not send email.
Parameters, scopes and examples
Path parameters
idstring · required
Suggestion id
Rejection reason.
Request body
{
"reason": "Too niche for our community right now."
}
Returns a single chat channel summary (channel row + unread_count + last_message_at) for the authenticated org. Staff role (or chat.read scope) required.
A read with the code in the request body (codes never travel in URLs). The state of a code issued by the issue endpoint: its status, deadline (valid_until / deadline_set_at), redemption time, contact and the caller's metadata, plus in_checkout (a checkout holds it), usable and unavailable_reason (redeemed, voided, expired, cancelled, promotion_inactive or promotion_ended) and the server time. Other codes of the venue are never visible (404 CODE_NOT_FOUND). API key only with read:promo_codes or write:promo_codes, 120 requests/min per key.
Issue the next free code of a batch to one contact
Atomically hands the next unissued code of a ready, non-partner, unexported batch that the venue flagged for personal issuance (batch metadata personal_issuance = true) to one contact and activates it. Optional initial_valid_minutes (1–43200) lets an unopened code expire by itself; lead_id must belong to the same email. The same contact (case-insensitive email) always receives the same code (200, reused: true); the batch size is the budget (409 BATCH_EXHAUSTED). Redemption requires the buyer to use the same email. Caller metadata is stored verbatim and echoed in responses and promo_code.* webhooks. Emits promo_code.issued for a new code. API key only (write:promo_codes), 60 requests/min per key, Idempotency-Key required (409 IDEMPOTENCY_CONFLICT when reused for another contact or batch).
The code travels in the request body. The first call sets valid_until = now + minutes (1–1440, chosen by the caller) unless an earlier deadline already exists, and stamps deadline_set_at; every later call changes nothing and returns the stored values (applied: false), so a page refresh cannot restart the clock. Only codes issued by the issue endpoint are visible (404 CODE_NOT_FOUND otherwise); a used, voided or expired code answers 422 CODE_UNAVAILABLE. Every redemption path enforces valid_until. Emits promo_code.deadline_set when applied. API key only (write:promo_codes), 120 requests/min per key.
Parameters, scopes and examples
Required scopes
write:promo_codes
The issued code (body only) and the deadline length in minutes
Sends the brand-styled promo_code_personal email to the code's own contact (the recipient is never caller-supplied) through the canonical templated pipeline: suppression and marketing unsubscribes honoured, RFC 8058 footer, notifications_log row, sender resolved brand → venue → platform. The caller supplies the https landing URL and may supply its own subject, plain-text message and button label (rendered verbatim, escaped); Booking Bible-authored words are English. With include_code false the email carries the call to action only. When the code belongs to a lead, that lead needs an active marketing_offer_email or marketing_email consent (else 422 CONSENT_REQUIRED); at most 3 emails per code per 24 hours (429 SEND_LIMIT_REACHED); an archived stored brand falls back to the venue sender. Answers 200 with status sent or suppressed, 502 EMAIL_SEND_FAILED when the pipeline failed (retry with the same Idempotency-Key), 422 CODE_UNAVAILABLE for a dead code. Emits promo_code.sent. API key only (write:promo_codes), 30 requests/min per key, Idempotency-Key required.
Parameters, scopes and examples
Required scopes
write:promo_codes
The issued code (body only), the landing URL and optional tenant copy
Request body
{
"code": "ACME-7KQ2M9XW",
"include_code": false,
"cta_url": "https://www.acme-yoga.example/offer/t1",
"locale": "en",
"subject": "Your personal offer",
"message": "Thanks for your interest — here is your code.",
"cta_label": "See your offer"
}
Bulk Scan Session — map a single scanned barcode to a preset target
Hot-path endpoint for the rapid-mapping Bulk Scan Session. Each call maps one scanned barcode. Returns status=created (new mapping), duplicate_in_session (same barcode+target already exists), requires_confirmation (different target, resend with allow_overwrite=true to proceed), or overwritten.
Model Context Protocol server (HTTP transport, JSON-RPC 2.0, protocol 2025-03-26). Authenticate with `X-API-Key`. Methods: `initialize`, `ping`, `resources/list`, `resources/read`, `tools/list`, `tools/call`. Read-only in v1. See `/developers/mcp` for the full guide.
Returns every registered feature module with its resolved enabled/settings/source for the calling venue. Resolution honors the four-tier precedence (tenant override → group lock → venue → group default → plan → default). Mobile Business app uses this for parity with /admin/settings/features.
Parameters, scopes and examples
Response example
{
"data": [
{
"key": "leaderboards",
"label": "Leaderboards",
"description": "Member-facing leaderboards by class type, period, and metric.",
"category": "Community",
"enabled": true,
"source": "plan",
"locked_by_group": false,
"settings": {}
}
]
}
PATCH/api/v1/admin/features/[moduleKey]Bearer or API key
Update a venue-level feature toggle
Flip enabled/settings for a feature module at the venue tier. Idempotency-Key supported. Returns 400 with `Locked by group: <paths>` when the venue tries to flip a group-locked toggle or write to a group-locked dot-path in `settings`. Audit-logged + emits `feature_toggle.changed` webhook.
Parameters, scopes and examples
Required scopes
write:settings
Path parameters
moduleKeystring · required
Module key from feature_modules.key
Partial update — only the fields you want to change.
Browse partner venues available to the member across the BOOKING BIBLE network. Each entry exposes a public summary plus the exact relationship status, active partnership id, and venue-level bookable flag for the organization selected by X-Organization-ID. Member-JWT.
Book a class at a partner venue using a network-eligible pass. Resolves the legal gate against the HOST venue’s documents before booking. Requires a caller-stable `Idempotency-Key` header; exact retries return the original booking and visit. Member-JWT.
Resolve an interrupted network booking before choosing again
Uses the original three-ID booking body and original Idempotency-Key. Atomically closes an uncommitted attempt so a delayed request cannot book it, or recovers the original completed booking. Never cancels an existing booking. Member-JWT only; no mutable catalog or legal preflight can replace the database proof.
Creates a venue-to-venue Network partnership request through the context-free Network mutation core. Bearer JWT requires network.manage; API keys require write:network.
GET/api/v1/network/partnerships/{id}Bearer or API key
Read a venue Network partnership
Reads one venue-to-venue Network partnership after proving the caller belongs to either party. Bearer JWT requires network.view; API keys require read:network.
PATCH/api/v1/network/partnerships/{id}Bearer or API key
Update a venue Network partnership
Updates a venue-to-venue Network partnership through the commercial lifecycle. Bearer JWT requires network.manage. API keys with write:network may change lifecycle status, but agreement negotiation and terms revisions require a verified human JWT administrator and return 403 for API-key callers.
DELETE/api/v1/network/partnerships/{id}Bearer or API key
Terminate a venue Network partnership
Terminates immediately only when binding and notice have both elapsed; otherwise schedules termination through the locked service RPC. Bearer JWT requires network.manage; API keys require write:network.
GET/api/v1/network/partnerships/{id}/visitsBearer or API key
List visits for a venue Network partnership
Lists visit ledger rows for one venue-to-venue Network partnership after proving the caller belongs to either party. Bearer JWT requires network.view; API keys require read:network.
GET/api/v1/network/partnerships/{id}/settlementsBearer or API key
List settlements for a venue Network partnership
Lists network-only settlements for one venue-to-venue Network partnership after proving the caller belongs to either party. Bearer JWT requires network.view; API keys require read:network.
For a venue workspace, lists its professional collaborations with network.view. For a selected individual workspace, an active member may read only rows where the Bearer user is practitioner_user_id and that workspace is the receiving org; person history remains readable when teacher_settlements is off and does not require network.view. Person writes separately require active admin membership. Exact-email invitation lookup is venue-only and requires network.manage. Unknown workspace or database authority fails closed. Each row also carries `status_changed_at`, and venue-direction rows carry `reinvite_option` (`reopen` = ended or declined and Re-invite is possible, `cooldown` = declined under 30 days ago with `reinvite_available_at`, `resend` = pending, Resend invitation; null otherwise). The email lookup returns `existing_collaboration_id` + `existing_collaboration_status` for an open or active collaboration and `ended_collaboration` ({id, status, reopenable, retry_at}) for the latest ended or declined one, so clients offer Re-invite instead of a new invitation.
Creates a pending venue-to-professional collaboration with an explicit venue role and compensation model. Requires network.manage and a non-individual venue workspace. When the venue already has a collaboration with this professional (the venue-professional pair is unique regardless of status) it returns 422 `COLLABORATION_EXISTS` with details {partnership_id, status, reopenable, retry_at}: link an open collaboration, or use POST /reopen for an ended or declined one.
The signed-in practitioner may accept, decline, or terminate their own collaboration when they are an active admin of its receiving individual workspace. These person actions use the canonical lifecycle without the venue network.manage or teacher_settlements gate. A venue with network.manage may set role, pause, resume, or terminate its relationship. A person cannot keep their former venue staff membership on termination.
Ends the relationship through the canonical termination core. The signed-in practitioner may end only their own row while an active admin of its receiving individual workspace; their venue membership is suspended. A venue with network.manage may retain former staff only by explicitly setting keep_membership true.
Parameters, scopes and examples
Optional termination reason and membership handling
Re-invite a professional (reopen an ended collaboration)
COLLAB-REINVITE-01. The selected venue workspace (network.manage, enforced before any read or write) reopens its own ended (terminated) or declined professional collaboration as pending, keeping the existing role and terms, and the professional receives a new request email. A professional workspace gets 403. The body must be empty: terms are edited afterwards on the pending collaboration. Declined collaborations can be reopened 30 days after the decision; pending or active ones refuse. Requires the platform `teacher_settlements` rollout (not the directory `collaboration_requests` switch) and the professional must still own their professional workspace. Consumes one unit of the venue daily collaboration-request budget (10 per rolling day, shared with directory requests). Audits `network.collaboration_reopened`; the email outcome is reported separately as `delivery`.
COLLAB-REINVITE-01. The selected venue workspace (network.manage, enforced first) re-sends the request email for its own still-pending professional collaboration, for example when the first email never arrived. No lifecycle change. One resend per collaboration per 10 minutes; each resend consumes one unit of the venue daily collaboration-request budget. The body must be empty. Requires the `teacher_settlements` rollout. Audits `network.collaboration_invitation_resent` with the delivery outcome.
List the caller’s relationships (bidirectional — both relationships the member created and ones pointing back at them), hydrated with the linked member’s profile. Unlocks family pricing, shared booking, and pass sharing. Member-JWT, org-scoped.
Add a relationship. When `related_email` matches a member in the same venue, the relationship links to their profile and a mirror row is written so both members see it. Member-JWT. Original Idempotency-Key is required. Exact token/body/key replays the immutable accepted or closed receipt; unknown outcomes retain the original request.
Public catalog of bookable private-event types for a venue (active + shown on website). Used by the app inquiry screen. Cached, IP-throttled. `purchase_channel` derives from `location_type` (online/both web, in_studio/offsite native); with `X-App-Digital-Content: none` a `both` type reads `native`, and the cache varies on that header.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue slug
Response example
{
"data": [
{
"id": "uuid",
"name": "Private Group Yoga",
"slug": "private-group-yoga",
"tagline": "Book the studio for your team",
"category": "corporate",
"min_participants": 5,
"max_participants": 30,
"default_duration_minutes": 90,
"pricing_model": "per_person",
"base_price": 0,
"per_person_price": 250,
"currency": "DKK",
"deposit_required": true,
"deposit_amount": 1000
}
],
"error": null
}
Public detail for a single private-event type (active + shown on website). 404 for hidden/draft types. Cached, IP-throttled. `purchase_channel` follows the same caller-aware `X-App-Digital-Content` rule as the list.
Submit a private-event inquiry as the authenticated member. Validates participant count against the event type’s min/max, computes pricing, and inserts a `private_event_bookings` row with `booked_by` set; emits `private_event.inquiry_created`. Member-JWT, org from X-Organization-ID. Idempotency-Key supported.
PROMPT_11 — returns the Stripe `client_secret`, frozen `merchant_country_code`, and `stripe_account_id` (`acct_*` for a direct Connect PI, otherwise null) for the booking’s deposit/full charge so the member can initialize Stripe Elements in the exact payment context. Customer/ephemeral-key credentials are returned only when this member owns the customer frozen by the first payment operation; admin-created or another accepted booker identity receives a safe generic sheet with null customer credentials. Idempotent (reuses the frozen PaymentIntent execution created at confirmation). `{ skipped: true }` when the event type’s payment_mode is `none`. Member-JWT; ownership by contact_email.
Member approves a quoted booking and gets Payment Sheet credentials
The member’s “Approve & pay” CTA: the booker confirms a quote the venue sent (status `quoted`) and receives the same frozen PaymentIntent, `merchant_country_code`, and `stripe_account_id` (`acct_*` only for direct Connect; otherwise null). Customer/ephemeral-key credentials are returned only to the user id that owns the frozen Stripe customer; another accepted booking identity receives a generic sheet with null customer credentials. Member-JWT; ownership by `booked_by` or `contact_email`. Idempotency-Key header REQUIRED — a retry re-enters the repairable confirmation pipeline and returns the same frozen execution (no duplicate PI, account drift, or regional drift). Honors the event type’s payment_mode via the shared helper; `{ skipped: true }` when payment_mode is `none` (invoice path).
PROMPT_11 — public catalog of a venue’s active, publicly-listed private-session types for the embeddable widget (/embed/private-sessions). Cross-origin access is governed by the platform dynamic CORS allowlist (venue custom domains). Cached, IP-throttled.
List products for the authenticated venue. Business-app JWTs require pos.access; API keys require read:products. Archived products are hidden unless include_archived=true.
Parameters, scopes and examples
Required scopes
read:products
Query parameters
include_archivedboolean
Include archived products. Defaults to false.Default: false
POST/api/v1/admin/productsAPI key
Create a venue product
Create through the canonical product mutation contract. Unknown/protected fields are rejected; initial stock creates one movement.
Parameters, scopes and examples
Required scopes
write:products
GET/api/v1/admin/products/{id}Bearer or API key
Get a venue product
Get one product only when it belongs to the authenticated venue. Business-app JWTs require pos.access; API keys require read:products.
Parameters, scopes and examples
Required scopes
read:products
Path parameters
idstring · required
Product UUID
PATCH/api/v1/admin/products/{id}Bearer or API key
Update a venue product
Update mutable catalog fields through the canonical product core. Business-app JWTs require products.manage; API keys require write:products. Products and its tier-gated Point of Sale dependency must be active. stock_quantity and protected fields are rejected.
Parameters, scopes and examples
Required scopes
write:products
Path parameters
idstring · required
Product UUID
DELETE/api/v1/admin/products/{id}API key
Archive a venue product
Archive through canonical catalog semantics (is_active=false plus archived_at). Permanent deletion is separate and guarded.
Parameters, scopes and examples
Required scopes
write:products
Path parameters
idstring · required
Product UUID
GET/api/v1/venues/{slug}/product-packagesPublic
List buyable clip cards
Public catalog of active product passes (clip cards) for a venue. Cached, IP-throttled.
Authenticated read-only exact package review. Body {currency}; returns accepted_quote with tenant/member/item, entitlement, price-book version, currency, gross minor units and included VAT. Only enabled home-country catalog-tax books are supported. Draft books refuse; no payment or provider mutation occurs.
Initiate a product-pass (clip card) purchase. A nonempty Idempotency-Key header is required and defines the durable operation. Returns Customer/ephemeral-key credentials in the same frozen Stripe account as the PaymentIntent, plus `merchant_country_code` and `stripe_account_id` (`acct_*` only for direct Connect; otherwise null). A service-only pre-provider claim binds tenant, catalog, customer, regional, routing, fee, amount, and currency; `product_passes` is granted atomically on `payment_intent.succeeded`. Gated on the `products` module + venue legal docs. Member-JWT. Optional selected body {currency, acceptedQuote} must contain the exact /quote review and is frozen into the original operation; stale book/gross/tax/entitlement or unsupported fields refuse before payment. Empty legacy bodies retain catalog checkout. Selected claims require released SQL; settings activation stays closed.
Buy N paid tickets for a community event (`{id}` is the event booking id). Price = `member_price` + `guest_price` × (ticket_count − 1). Requires an Idempotency-Key header. Returns `operation_state` (payment_required, processing, completed or canceled), durable `operation_status`, and immutable ticket_count/amount_minor/minor_unit_multiplier/member_price_minor/guest_price_minor/amount/currency. Same-key replay checks the durable owned order before cached responses; succeeded payments reconcile through the existing finalizer. Completed/processing/canceled responses have no client_secret and must not open PaymentSheet. Only payment_required returns Customer/ephemeral-key credentials in the same frozen Stripe account as the PaymentIntent, plus `merchant_country_code` and `stripe_account_id` (`acct_*` only for direct Connect; otherwise null). On `payment_intent.succeeded` the ticket order flips to paid and a `going` RSVP is upserted with `guest_count = ticket_count − 1`. Gated on the `community_events` module. Member-JWT.
Inspect the contract. Build against generated truth.
This reference reads the same typed endpoint registry that generates Booking Bible’s OpenAPI 3.1 document. Start with a group, then open only the parameters and examples you need.
Route handlers are the complete deployed /api/v1 surface. Documented operations are the partner-facing contracts currently registered for OpenAPI.
Start with the contract
Authentication and versioning are explicit
Public discovery routes need no credentials. Protected routes accept a user bearer token, an organization-scoped API key, or the method documented for that operation.
Organization-scoped credentials
Create API keys under Admin → Settings → Developer. Each key is shown once, carries explicit scopes, and remains bound to its venue.
Version pinned by header
Send X-Api-Version to pin behavior. The current documented version is 2026-04-11.
Read-only brand-scoped overview, paginated people/search, client details, classes and venue-admin crossover. Requires an active staff session, venue membership and reports.view; non-admin staff also require an explicit active brand reporting assignment. Supports staff JWT or a short-lived read-only delegated brand token. Search is in the request body, never the URL. Missing sources and capped totals are explicit. Small demographic cells are suppressed. No export or messaging authority.
Parameters, scopes and examples
Required scopes
reports.view
brandId, operation overview|people|person|classes|crossover, optional personId; filters include date range, paging, search, stage, membership/pass, attribution, geography, age band, consent, activity and watch ranges, played class/type/teacher, room/start-hour/day, and correlated device/platform/browser/operating-system/app-version filters. See docs/brand-analytics-contract.md.
Rechecks source-session revocation, MFA, current role, active venue membership, report permission and explicit brand assignment. Does not grant access by email or consumer cookie.
Parameters, scopes and examples
Required scopes
reports.view
Query parameters
brandIdstring · required
Brand UUID within the authenticated venue
GET/api/v1/admin/music/accessBearer token
Check music pilot staff access
Rechecks the current staff reporting session, brand scope, management permission and confirmed pilot identity. Returns enabled only for the pilot manager; other staff receive 403.
Parameters, scopes and examples
Required scopes
reports.viewsettings.business
Query parameters
brand_idstring · required
Brand UUID
GET/api/v1/admin/musicBearer token
List private brand music library
Staff report scope and confirmed pilot identity required. Returns brand mixes, versions, assignments, future classes and short-lived private preview URLs.
Parameters, scopes and examples
Required scopes
reports.view
Query parameters
brand_idstring · required
Brand UUID
POST/api/v1/admin/musicBearer token
Manage private brand music
Pilot staff manager/admin with settings.business may begin a path-specific signed resumable upload, finalize metadata, publish, assign, unpublish, activate/discard a replacement or delete. Every mutation is audited.
Pilot staff report scope required. Separates mix selection, attempts and bounded player-observed listening; small cells are suppressed and actual speaker or cast output is unknown.
Staff JWT only, with settings.membership for venue policy or passes.manage for recurring products. Original organization and actor are server-bound. No message or payment is sent by these controls. mode=venue returns defaults and the override list; mode=catalog returns recurring fixed and Flexible memberships only. offset pages 100 items; next_offset is null at the end. Includes venue currency, IANA time_zone, civil today, sparse stored values, server-resolved inheritance and venue reminder limit. No provider configuration is returned.
Staff JWT only, with settings.membership for venue policy or passes.manage for recurring products. Original organization and actor are server-bound. No message or payment is sent by these controls. Read-only canonical resolver and reminder timeline, bounded to 30 venue-local days. Returns preview_hash bound to original actor, target, sparse value and current policy/draft context. It is not delivery or collection authority.
Parameters, scopes and examples
Required scopes
settings.membershippasses.manage
Sparse policy value; null clears a product override. Non-recurring products are refused.
Staff JWT only, with settings.membership for venue policy or passes.manage for recurring products. Original organization and actor are server-bound. No message or payment is sent by these controls. Requires original Idempotency-Key and preview_hash. Same original request replays before mutable catalog reads. SQL locks and compares the authoritative reviewed context; stale context yields immutable refused receipt. Flexible changes use draft CAS and canonical publisher; unrelated draft changes produce draft_review_required. Unknown or malformed outcomes retain original body and key.
Parameters, scopes and examples
Required scopes
settings.membershippasses.manage
Exact preview body with returned preview_hash. Per-key omission inherits for product overrides.
Staff JWT with settings.business. Venue-only max_reminders integer 0..2147483647 and expected_max_reminders are required, with an original Idempotency-Key. Zero reminders does not disable independently configured lapse/cancellation. SQL248 checks dependent custom ladders. Missing invoice row retains daily fallback; existing invoice fields and cadence are preserved.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Returns the same full-visit detail envelope as the member route, with staff-authorized cancellation preview.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Send date and optional provider_id/location_id. The route seeds the selected venue visit’s owned service composition and returns the shared candidate contract.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Optional reason and notification_channels. Returns visit fields plus cancellation_result under data. Every leg and the refund obligation are committed together.
Requires bookings.manage in the selected organization; owners and authorized staff share this route. Explicit notification_channels may select email, SMS or push only with notifications.send. Omission means no staff-triggered client notification. Staff bypass the member self-service cutoff; refund amounts still follow the accepted venue terms. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Uses the same provider/location/start/expected quote fields and atomic replacement contract as the member route. Optional notification_channels select one complete-visit reschedule notice.
Parameters, scopes and examples
Path parameters
idstring · required
Whole appointment visit UUID
GET/api/v1/admin/reports/attendanceBearer token
Attendance history report
Class-date attendance (default) or action-date audit history with venue-local and UTC timestamps, actor/status/credit detail, and separate ordinary check-in, Undo, and correction receipts. Missing legacy history is explicit and never receives a fabricated timestamp.
Parameters, scopes and examples
Required scopes
reports.classesmembers.view_insights
Query parameters
viewstring
Whether the date range selects scheduled class dates or recorded action datesDefault: class_date
Requires settings.business and a venue-owned pass_type_id query parameter. Returns the current pass source fingerprint, published choice/add-on identities and optional draft book from pass_component_drafts_v1. No payment, activation or country gate is exposed; responses are no-store.
Requires settings.business and Idempotency-Key. Accepts pass_type_id, expected_revision and a strict draft book with revision, source_pricing_version_id, source_fingerprint and component gross minor amounts by explicit ISO currency. Components cover registration fees, 30+ full pass totals, published add-on unit prices and a published allowance plus validated add-on quantities as a combined total. Source ownership, active published version, allowance rules and add-on eligibility are rechecked. Compare-and-set preserves other passes and active residence_commerce_v1; changed content requires a new revision. Exact replay is checked before changed source terms. Unknown writes retain the same body/key. Drafts never activate checkout, country gates, tax calculations or sends.
GET/api/v1/admin/residence-commerceBearer token
Read reviewed residence commerce price books
Venue-scoped versioned catalog price books and per-country checkout gates. Requires settings.business; a missing or invalid policy keeps checkout closed for configured products.
PATCH/api/v1/admin/residence-commerceBearer token
Revise a catalog price book and country gates
Optional country_drafts retain incomplete classification, place-of-supply and provider research separately from active rules; drafts do not approve checkout. Newly enabled or changed Stripe Tax markets are verified against seller Tax settings, active registration country/scheme coverage and charge-model-scoped product classification. Unchanged approvals retain their original reviewer and remain editable during provider outages. Requires settings.business and Idempotency-Key. Accepts a legacy pass_type_id or typed item identity and saves one owned catalog revision with compare-and-set, audits every country decision, and requires a new price book version when terms change. Fixed pass, product-package and service-bundle books use exact gross prices. Products, product packages, bundles, services and service_variant items support sparse exact gross currency drafts with all country gates closed; service variants are scoped through their owning service. Product packages verify both their own venue and their nonrecurring owning product venue. Known ISO prices retain exact currency minor precision. Reserved product_variant identities remain unsupported because the catalog has no product-variant entity. One-time foreign tax, configurable selections and unsupported checkout paths remain closed. Recurring foreign enabled countries require reviewed place-of-supply, registration and processor-account references.
Requires settings.business, Idempotency-Key and the current brand updated_at value. Writes a validated whole versioned override with compare-and-set and audit.
GET/api/v1/admin/sales-snapshotBearer or API key
Sales snapshot
Venue-local sales report, gated by reports.view. Optional range=today|yesterday|7d|mtd|30d|custom, basis=cash|accrual, from/to, methods, categories, brand, location and refresh=1. Existing aggregates remain unchanged; cashHeadline adds separate major-unit gross/count totals by recorded payment or gift-card currency and unresolvedCount. CashHeadline is null for accrual; clients must not relabel legacy aggregates with a current venue currency.
Revenue, bookings, attendance, 30-day active clients, and average revenue per client — per brand for the given period (default last 30 days). Uses bookings.brand_id and payments.brand_id populated by HYC_2. Returns venue-wide (unbranded) totals alongside the brand rows.
Parameters, scopes and examples
Required scopes
read:reports
GET/api/v1/admin/dashboard/todayBearer or API key
Today at a glance
Today's class timeline with booking counts, check-in status, and room assignments.
Parameters, scopes and examples
Required scopes
read:schedule
GET/api/v1/admin/scheduleBearer or API key
Admin schedule
Full schedule view with internal data: per-status booking counts, notes, cancellation reasons, updated_at concurrency tokens, fail-closed historical capabilities, additive `course_identifiers` pills (`{course_id,label,tone,course_name}`, hidden courses included) for workshop dates and, for a bounded window (from and to at most 62 days apart) or include=notification_availability, at most 300 rows, notification_availability.{cancel,edit,substitute}.{clients,instructor}.{email,sms,push} (available when the venue gate passes and at least one recipient is reachable; reach counts and blocked reasons included). Otherwise the key is omitted: read one class with GET /api/v1/admin/schedule/{id} (which also carries restore). include_historical=true requires scheduling.manage_history.
Parameters, scopes and examples
Required scopes
read:schedule
Query parameters
start_datestring
Inclusive ISO date/time lower bound
end_datestring
Inclusive ISO date/time upper bound
include_historicalstring
Include protected historical class rows; requires scheduling.manage_historyDefault: false
Staff JWT and bookings.manage required; API keys and mixed credentials are rejected. Calls the canonical availability engine with the authoritative active organization, purpose staff and strict read errors. Staff-only services and rescheduling during wind-down do not inherit public sale gates. Requires service_id and a valid YYYY-MM-DD date; accepts provider_id, variant_id, location_id and reschedule_id. A reschedule source is excluded from conflicts only after same-tenant and same-service ownership checks. Returns provider and opted-in facility slots with private no-store caching. Facility identity is provider_id null plus room_id/resource_kind room. Reschedule walks the tenant-verified original room through an internal staff-only override, including non-default rooms; caller room overrides are rejected. Read-only discovery never authorizes creation or changes engine/SQL mutation checks.
Parameters, scopes and examples
Required scopes
bookings.manage
Query parameters
service_idstring · required
Service UUID in the active organization
datestring · required
Venue-local calendar date YYYY-MM-DD
provider_idstring
Optional provider UUID
variant_idstring
Optional service variant UUID
location_idstring
Optional service location UUID
reschedule_idstring
Existing same-tenant, same-service appointment UUID to exclude from occupancy
GET/api/v1/admin/appointmentsBearer token
Venue appointment schedule
Business-app venue-wide appointment list with updated_at concurrency tokens and authoritative, fail-closed historical capabilities. Filters by ISO window, direction, status, provider, and location. Permission: bookings.manage.
Parameters, scopes and examples
Query parameters
fromstring
Inclusive ISO start time
tostring
Exclusive ISO end time
directionstring
upcoming | pastDefault: upcoming
statusstring
Appointment status
provider_idstring
Provider UUID
location_idstring
Location UUID
limitnumber
Maximum 200Default: 100
POST/api/v1/admin/appointmentsBearer token
Create an appointment for a client
Creates a tenant-bound current/future appointment for a known member or contact-complete guest through the canonical atomic appointment engine. Venue-local past dates and historical attestation fields fail closed until the dedicated executor is installed. Permission: bookings.manage. Idempotency-Key required. Client delivery is default-silent: only an explicit notification_channels selection of email, sms, and/or push can send. A non-empty selection requires notifications.send and an authoritative availability preflight before the mutation; an unavailable channel fails without creating the appointment. Omitted or empty channels and legacy notify booleans remain silent. notify.clients.channels is accepted as the per-audience equivalent; the 422 details carry {audience, channel, reason_code, unavailable_reason}; success adds notify_outcome.
Tenant-bound client, provider, service, location, payment, notes, lifecycle state, updated_at concurrency token, and authoritative fail-closed historical capabilities for the Business app. Additive historical_correction_eligibility returns candidate/reason_code/reason from a bounded private tenant-scoped row read. Definite SQL dependencies suppress corrections without exposing payment or operation identities. A candidate still requires locked SQL checks; global capability is not record eligibility. Permission: bookings.manage.
Parameters, scopes and examples
Path parameters
idstring · required
Appointment UUID
PATCH/api/v1/admin/appointments/{id}Bearer token
Operate an appointment
Atomic ordinary check-in, start, complete, no-show, cancel, or reschedule with the exact expected_updated_at token returned by GET. A stale token returns 409 STALE_TARGET without mutation. Each action is checked against its canonical permission. Past/terminal appointments, past reschedule targets, and historical attestation fields fail closed until the dedicated executor is installed. Idempotency-Key required. No-show, cancel, and reschedule are default-silent and accept an explicit notification_channels selection of email, sms, and/or push; a non-empty selection requires notifications.send and an authoritative availability preflight before mutation. An unavailable channel fails without changing the appointment. Omitted or empty channels and legacy notify booleans remain silent. Check-in, start, and complete are non-client-contact actions and reject notification_channels. notify.clients.channels is accepted as the per-audience equivalent (mixing both shapes returns 422 MIXED_NOTIFY_SHAPES); an unavailable channel returns 422 APPOINTMENT_NOTIFICATION_CHANNEL_UNAVAILABLE with {audience, channel, reason_code, unavailable_reason}; success adds notify_outcome. GET responses add notification_availability_by_action {cancel, no_show, reschedule} in the staff availability contract.
Parameters, scopes and examples
Path parameters
idstring · required
Appointment UUID
Discriminated appointment action with the opaque updated_at token returned by the appointment detail
Dedicated, atomic appointment-history command contract. Requires a UUID Idempotency-Key, appointments.manage_history plus the ordinary operation permission, REWRITE attestation, and a past effective_at. Existing-row operations require the exact expected_updated_at and expected_status returned by the appointment detail; retrocreate accepts only the closed non-financial appointment intent. Corrections are always silent and reject notification controls. The route returns 503 HISTORICAL_EXECUTOR_UNAVAILABLE without table-call fallback until the separately reviewed correct_appointment_historical database RPC is installed.
Parameters, scopes and examples
Required scopes
appointments.manage_history
Path parameters
idstring · required
Appointment UUID
Closed appointment correction; history_confirmation_token must be REWRITE
Tenant-bound class and appointment reviews for the Business app. Supports source, visibility, rating, and pagination filters. Anonymous reviewer identity is never returned. Permission: feedback.view.
Publishes or unpublishes one tenant-bound class or appointment review. Permission: feedback.manage. Idempotency-Key required.
Parameters, scopes and examples
Path parameters
sourceTypestring · required
class | appointment
idstring · required
Review UUID
GET/api/v1/admin/feedback-settingsBearer or API key
Read internal feedback and review cadence settings
JWT permission feedback.configure, or a venue-bound API key with write:settings. Mixed credentials are rejected. Reads fail unavailable on settings query errors and do not seed configuration. Returns the public feedback settings DTO, including first_completed_visit, repeat_every_completed_visits, visit_scope and their legacy prompt-prefixed mirrors. No raw organization settings are returned.
Parameters, scopes and examples
Required scopes
write:settings
PATCH/api/v1/admin/feedback-settingsBearer token
Configure internal feedback and review cadence
JWT permission feedback.configure only; API keys and mixed credentials are rejected. Idempotency-Key is bound to the organization, actor, operation and validated body. This saves configuration only, never sends a review request. Unknown saves return 503 SAVE_OUTCOME_UNKNOWN with phase:write and saved:unknown and retain the claim. A confirmed save whose reload fails returns saved:true, phase:postcommit, reload_failed:true; refresh, do not treat this as rollback. First completed visit N and repeat interval M must be saved together. N and non-null M are integers from 1 to 50; null M means first only. Explicit prompt_channels: [] disables internal prompt channels. Omitted fields retain existing configuration.
Parameters, scopes and examples
Required scopes
feedback.configure
Validated feedback-settings patch; aliases first_completed_visit, repeat_every_completed_visits and visit_scope are accepted.
GET/api/v1/admin/external-review-settingsBearer or API key
Read public review-request configuration
JWT permission feedback.configure, or a venue-bound API key with write:settings. Mixed credentials are rejected. Reads fail unavailable on settings query errors and do not seed configuration. Returns public review sites, configured email/SMS channels and independent completed-visit cadence. A clicked public review link is not proof that a review was submitted.
JWT permission feedback.configure only; API keys and mixed credentials are rejected. Idempotency-Key is bound to the organization, actor, operation and validated body. This saves configuration only, never sends a review request. Unknown saves return 503 SAVE_OUTCOME_UNKNOWN with phase:write and saved:unknown and retain the claim. A confirmed save whose reload fails returns saved:true, phase:postcommit, reload_failed:true; refresh, do not treat this as rollback. Public review requests use configured email/SMS channels and existing consent, suppression and delivery gates. N and M are independent from internal feedback. suppress_after_public_review means stop after the tracked public link was opened, not verified review completion.
Parameters, scopes and examples
Required scopes
feedback.configure
Public settings patch; first_completed_visit and repeat_every_completed_visits are paired. No automatic delivery or tenant-specific seeding.
JWT permission feedback.configure only. Read-only preview of saved configuration and recent completed-visit candidates. It uses the canonical visit counter and cadence decisions, but does not prove delivery reachability, consent, quiet hours or source overrides. preview_kind is cadence_configuration and delivery_verified is false. Legacy eligible means cadence qualification only. No messages, prompts or settings are created.
Parameters, scopes and examples
Required scopes
feedback.configure
Choose internal or public saved configuration; unsaved form drafts are not evaluated.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Idempotency-Key is required and bound to organization, actor, operation, resource and validated body. Web and REST share the same allowlisted writer, tenant-reference checks, plan/franchise limits, audit and cache invalidation. Uses the existing web archive core: deactivates the service without deleting existing appointments, retains the franchise lock, and emits the canonical service.deleted audit/webhook event. Returns {ok:true,write_warnings?}, not a service row. Unknown writes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim; replay the exact key or reload authoritative state. This endpoint does not notify customers.
Parameters, scopes and examples
Required scopes
bookings.manage
Required empty JSON object. Tenant, actor, service identity and notification flags cannot be supplied in the body.
Request body
{}
GET/api/v1/admin/servicesBearer token
List the venue service catalog
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Returns explicit catalog fields, excluding payout configuration. Read failure is unavailable, not a successful empty catalog. No customer appointments are returned.
Parameters, scopes and examples
Required scopes
bookings.manage
Query parameters
include_inactivestring
Include inactive services when exactly true.Default: false
POST/api/v1/admin/servicesBearer token
Create a service in the venue catalog
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Idempotency-Key is required and bound to organization, actor, operation, resource and validated body. Web and REST share the same allowlisted writer, tenant-reference checks, plan/franchise limits, audit and cache invalidation. Prices are decimal MAJOR currency units, not integer minor units. Currency must match authoritative venue currency. The route refuses body tenant IDs, unknown columns and arbitrary image URLs. Confirmed saves return the service DTO and optional write_warnings. Unknown transport/database outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim: refresh authoritative state or retry the exact operation/key, never blindly create again. Known prewrite refusals may release the claim. A failed acknowledgement cannot turn a known save into rollback.
Parameters, scopes and examples
Required scopes
bookings.manage
Validated catalog create input. Native service image, variants and provider-hours authoring are separate companion work, not implied by this endpoint.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Foreign or missing service IDs return not found; query failures remain unavailable. The catalog DTO does not contain provider payout configuration.
Parameters, scopes and examples
Required scopes
bookings.manage
PATCH/api/v1/admin/services/{id}Bearer token
Edit or deactivate a venue service
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Idempotency-Key is required and bound to organization, actor, operation, resource and validated body. Web and REST share the same allowlisted writer, tenant-reference checks, plan/franchise limits, audit and cache invalidation. Empty patches are rejected before claiming a key. Unrelated edits retain stored service currency; monetary edits validate the resulting stored-plus-patch deposit configuration. is_active:false deactivates, without deleting existing appointments. Reactivation rechecks the plan limit. Confirmed saves return the service DTO and optional write_warnings. Unknown transport/database outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim: refresh authoritative state or retry the exact operation/key, never blindly create again. Known prewrite refusals may release the claim. A failed acknowledgement cannot turn a known save into rollback.
Parameters, scopes and examples
Required scopes
bookings.manage
Only changed catalog fields; no tenant reassignment, arbitrary image URL or direct booking-mode grant.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Service ownership is checked before storage access. Strict JPG, PNG or WebP metadata, positive integer file_size up to 5 MiB. Returns {uploadUrl,token,path,maxBytes}; PUT the file bytes to uploadUrl, then call finalize. Paths have an immutable venue/service/generation prefix. Existing lazy bucket provisioning remains part of this authorized source operation; this does not mean hosted storage has been provisioned. This request does not attach an image or contact clients.
Parameters, scopes and examples
Required scopes
bookings.manage
Only declared content type and byte size; no tenant IDs or arbitrary URL.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Service ownership is checked before storage access. Idempotency-Key is required and scoped to actor, venue, service, operation and body. A confirmed save may include write_warnings for secondary failures. Unknown outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim; reload authoritative state or retry the exact key, never blindly repeat with a new key. Accepts only the original path minted for this venue and service. Validates bytes and minimum 1200 by 675 dimensions, generates immutable 1600/800/400 WebP images, and compares both previous image URL and JSON media before attaching them. Returns {imageUrl,write_warnings?}. Cleanup never lists/deletes a service prefix or removes current-generation heroes. Storage conflicts are unknown, not proof that prior bytes match. No customer notifications.
Staff JWT with bookings.manage in the authorized venue. API keys and mixed credentials are rejected. Service ownership is checked before storage access. Idempotency-Key is required and scoped to actor, venue, service, operation and body. A confirmed save may include write_warnings for secondary failures. Unknown outcomes return 409 UNKNOWN_WRITE_OUTCOME and retain the claim; reload authoritative state or retry the exact key, never blindly repeat with a new key. No request body. Returns {ok:true,write_warnings?}. Clears only the image and hero media keys with a compare-and-set; preserves sibling media and current appointment history. Only exact owned former hero paths are eligible for cleanup. Arbitrary external images and peer uploads are never deleted. No customer notifications.
Parameters, scopes and examples
Required scopes
bookings.manage
GET/api/v1/admin/booking-offeringBearer token
Read booking products and eligible offering changes
User JWT and settings.business required; API keys and mixed credentials are rejected. Returns contract_version:1, organization_id, mode, product lifecycle/status/sales flags and both internal transition choices with availability, reason and requiredPlanKeys where relevant. Uses the same authority as web Business settings. Read failure is 503, not an empty or class-default configuration. No prices, raw settings or client identities are returned.
Parameters, scopes and examples
Required scopes
settings.business
PATCH/api/v1/admin/booking-offeringBearer token
Change an eligible venue booking offering
User JWT and settings.business required. Strict mode-only body; Idempotency-Key binds the selected organization, actor and validated body. Uses the existing atomic offering RPC, commercial access gates and wind-down/history retention; it never changes a paid plan, charges a customer or archives a product. Success returns contract_version:1, organization_id and mode, with refresh_failed:true if independent surface refresh failed after save. Errors add write_state:not_written or unknown; PLAN_QUOTE_REQUIRED includes required_plan_keys without inventing prices. Preserve the same logical key on unconfirmed outcomes and reload before a new intent. An acknowledgement failure never replaces a known save response. Separate paid-plan preview/acceptance and native authoring companions remain required before full parity is claimed.
Parameters, scopes and examples
Required scopes
settings.business
Required mode: classes, appointments or both. Internal classes preserves grandfathered configurations; public commerce still has two model names. No organization, actor, price, tier or raw settings fields.
User JWT and bookings.manage required; API keys and mixed credentials are rejected. Evaluates the canonical appointment readiness facts for the selected organization without writing. Returns contract_version:1, organization_id, evaluated status ready|incomplete, missing labels, evaluated_at and current booking_mode. persisted_onboarding is separate onboarding provenance and is never the evaluated status; pending and seed_errors are not live facts. Read failure is 503 QUERY_FAILED, not fabricated ready/incomplete. business_type does not determine readiness. Native must bind this exact contract; this is not a claim that native screens are complete.
Parameters, scopes and examples
Required scopes
bookings.manage
GET/api/v1/admin/appointment-policiesBearer token
Read effective appointment booking policies
User JWT and settings.business required; API keys and mixed credentials are rejected. Lifecycle uses resolveAppointmentSettingsLifecycleGate, not canVenueManageAppointmentSettings: active, wind_down and read_only authorize; archived and unknown/malformed status refuse; a missing appointments product row may use booking_mode plus service count; a product-domain query failure is 503 QUERY_FAILED and never a legacy fallback. Returns contract_version:1, organization_id and the allowlisted effective configuration: slot_interval_minutes, payment_at_booking_mode with derived require_payment_at_booking, self_service_cutoff_hours plus malformed flag, grouped_visits venue settings, and row_present. Does not expose platform kill switches, raw settings, deposits, checkout or POS. Read failure is 503, not invented defaults presented as a query success. Lifecycle refusal is 403 APPOINTMENT_SETTINGS_UNAVAILABLE.
User JWT and settings.business required, plus resolveAppointmentSettingsLifecycleGate (active/wind_down/read_only; archived and unknown/malformed refuse; product query failure is not a booking_mode fallback). Strict allowlisted body for slot_interval_minutes, payment_at_booking_mode, self_service_cutoff_hours and grouped_visits. grouped_visits refund_terms require confirmed:true; platform kill switches are not writable. Idempotency-Key binds selected organization, actor, operation appointment_policies.patch and the validated body. Writes use compare-and-set merge of the shared appointments settings row so unrelated keys survive; an absent-row unique conflict retries. Success returns the GET DTO, with refresh_failed:true if independent cache/audit follow-up failed after save. Errors add write_state:not_written or unknown. Preserve the same logical key on unconfirmed outcomes and reload before a new intent. An acknowledgement failure never replaces a known save response. Native authoring companions remain required before full parity is claimed.
Parameters, scopes and examples
Required scopes
settings.business
At least one allowlisted field. payment_at_booking_mode is venue, online or client_choice. self_service_cutoff_hours may be null to clear. grouped_visits requires enabled, max_services_per_visit, refund_terms and confirmed. No organization, actor, require_payment_at_booking, raw settings, deposit, checkout or plan fields.
Uses the same tenant-bound variant deletion core as the web admin. Prefer PATCH is_active:false to keep the variant record; deletion retains existing database reference restrictions and may be refused. Requires bookings.manage and JWT; API keys/mixed credentials are rejected. Idempotency-Key binds tenant, actor, parent service and variant. Unknown writes retain the request key: reload and review before a new intent. No appointment or payment mutation is performed by this handler.
Creates a duration/price option on the parent service. Price inherits the parent service currency and is decimal major units (49.50 stays 49.50). No organization_id, payout, intake, or waiver fields. Idempotency-Key required. Permission: bookings.manage. JWT only.
Partial update of one variant that belongs to the parent service. Empty PATCH is 400. Prefer is_active false over delete. Idempotency-Key required. Permission: bookings.manage. JWT only.
Optional eligible_only=true returns only active assignments with an active service-delivering membership of the authenticated venue; a failed eligibility read returns 503. Default reads retain inactive assignments for administration. Public assignment projection for one tenant service: names, photos, duration/price overrides, primary/sort/active. No bio, contact, compensation, or payout. Empty array means none; 503 is a failed read. Permission: bookings.manage. JWT only.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
idstring · required
Service UUID
Query parameters
eligible_onlystring
true for eligible booking choices; false or omitted retains setup assignments
Upserts an active delivering membership onto the service. Instructor/service_provider primary role or additive delivering capability required. Generic staff/reception is 403. custom_price_amount is decimal major units in the parent service currency; null inherits. Idempotency-Key required and bound to tenant, actor, target and validated body. Permission: bookings.manage. JWT only. Team capacity uses the canonical plan authority; its preflight and upsert are not an atomic capacity reservation.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
idstring · required
Service UUID
providerIdstring · required
Staff profile UUID
Optional duration/price overrides, primary, sort, and active.
Partial update of an existing assignment. Empty PATCH is 400. Missing assignment is 404. Idempotency-Key required. Permission: bookings.manage. JWT only.
Removes the future offering link. Historical appointments are preserved. Inactive memberships may be unassigned. Idempotency-Key required. Permission: bookings.manage. JWT only.
Staff-detail assignment pane. staff.view plus venue membership; catalog edit is not required to read names. Empty array means none assigned. Permission: staff.view. JWT only.
Parameters, scopes and examples
Required scopes
staff.view
Path parameters
staffIdstring · required
Staff profile UUID
GET/api/v1/admin/roomsBearer token
List venue rooms and stations
Explicit room setup projection, including inactive rooms. Requires settings.business. JWT only; API keys and mixed credentials are refused. Empty list is distinct from a failed read.
Parameters, scopes and examples
Required scopes
settings.business
POST/api/v1/admin/roomsBearer token
Create a room or station
Shared web/native room core. Explicit location must belong to the venue; an omitted location is inferred only when exactly one active location exists. Floor-plan URLs require the separate upload/finalize flow. Does not create locations or change paid tiers. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
settings.business
Room name, positive capacity and supported room configuration.
One room scoped to the authenticated venue. No client, treatment, payroll or booking records. Missing room is 404; failed read is not an empty room.
Parameters, scopes and examples
Required scopes
settings.business
Path parameters
idstring · required
Room UUID in the authenticated organization
PATCH/api/v1/admin/rooms/{id}Bearer token
Update room setup
Nonempty supported room fields, including is_active for retirement. Capacity reduction retains the existing future-class cap projection; ROOM_CAPACITY_PARTIAL includes saved:true and the saved room when that secondary projection fails. No class booking or checkout change. floor_plan_url accepts only null to clear its reference; new URLs require upload/finalize. Clearing does not delete storage objects. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
settings.business
Path parameters
idstring · required
Room UUID in the authenticated organization
Supported partial room configuration.
Request body
{
"is_active": false
}
GET/api/v1/admin/locationsBearer token
List location setup references
Limited authoring DTO for selecting a room location and editing existing media/hours. Either settings.business or locations.manage permits this read. Does not create/delete locations, set primary location or expose billing settings.
Parameters, scopes and examples
Required scopes
settings.businesslocations.manage
GET/api/v1/admin/locations/{id}Bearer token
Read location media and hours
Limited authoring DTO for one tenant location. Either settings.business or locations.manage permits this read. No location billing or client records.
Parameters, scopes and examples
Required scopes
settings.businesslocations.manage
Path parameters
idstring · required
Location UUID in the authenticated organization
PATCH/api/v1/admin/locations/{id}Bearer token
Update location media and opening hours
Requires locations.manage. A media/hours patch supports image_url, gallery_urls and opening_hours. Media permits deliberate external URLs or null removal; storage-object URLs must use finalize. Alternatively send only remove_gallery_url to remove one exact saved gallery reference, including finalized uploads, while preserving the other images. Removal uses a tenant-scoped JSONB compare-and-swap, is a no-op if already absent, returns GALLERY_CONFLICT on concurrent change, and never deletes storage objects. Do not combine removal with other fields. Hours contain mon through sun, each null (closed) or open/close HH:mm with open before close; null clears hours. Not a location provisioning or billing API. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
locations.manage
Path parameters
idstring · required
Location UUID in the authenticated organization
Nonempty media/hours patch, or the single remove_gallery_url field.
Checks tenant room ownership before minting a signed venue-assets upload. JPEG/PNG/WebP only, at most 5 MiB. Returns upload_url, token, path, public_url and max_bytes. Upload authorization alone does not store the URL on the room.
Parameters, scopes and examples
Required scopes
settings.business
Path parameters
idstring · required
Room UUID in the authenticated organization
Declared format and size; finalize independently checks bytes.
Exact organization/room object path, ownership and downloaded size/magic-byte validation before storing floor_plan_url. Returns the explicit room DTO. No arbitrary file or foreign resource path. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Requires locations.manage and tenant location ownership. kind is hero or gallery. JPEG/PNG/WebP up to 5 MiB. Returns signed upload fields, max_bytes and kind; does not yet change location media.
Requires locations.manage. Exact owned path and downloaded byte checks precede persistence. Gallery append uses JSONB compare-and-swap: duplicate path is unchanged, capacity 20 refuses, malformed data refuses, concurrent change returns GALLERY_CONFLICT without dropping another image. Returns the limited location DTO. Idempotency-Key is required and binds actor, organization, target and validated intent. UNKNOWN_WRITE_OUTCOME retains that key; reload and review before creating a new intent.
Parameters, scopes and examples
Required scopes
locations.manage
Path parameters
idstring · required
Location UUID in the authenticated organization
GET/api/v1/admin/providers/{id}/hoursBearer token
List provider working hours
Tenant-local working-hours rows for one delivering staff member. Empty array means none; 503 UNAVAILABLE is a failed read. include_inactive=true includes deactivated rows. Permission: bookings.manage. JWT only.
Creates a venue-local hours row. Times are clock values; effective_from defaults to the venue calendar date. Overlap/duplicate rows are refused. Idempotency-Key required. Permission: bookings.manage. JWT only.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
idstring · required
Staff profile UUID
Working-hours create. Location must belong to this venue when set.
Partial update of one hours row owned by the provider in this venue. Empty PATCH is 400. Idempotency-Key required. Permission: bookings.manage. JWT only.
Soft-deactivates the hours row. Historical appointments are preserved. Inactive memberships may be cleaned up. Idempotency-Key required. Permission: bookings.manage. JWT only.
Business bearer-JWT read for an individual professional whose active organization remains their own workspace. The server re-proves the active practitioner partnership, active host membership, primary/substitute assignment and relationship-scoped roster access. It returns genuine class and booking updated_at CAS tokens, venue-local day_state, operation-filtered historical_capabilities, the authoritative client_contact_visibility result, the fail-closed client_pass_visibility (visible|hidden) set by the host venue per collaborator, and only operational booking/pass details including the frozen Flexible selection, remaining allowance and expiry warnings. When client_pass_visibility is hidden every attendee pass is null and pass-derived warnings are omitted. Contact values use the shared none/masked/full redactor. Terminal cancelled rows require include_historical_records=true plus the host scheduling.manage_history and matching ordinary action grant. No unrestricted profile, account credit, unrelated passes or financial history is exposed.
Parameters, scopes and examples
Required scopes
schedule.view_own
Path parameters
classIdstring · required
Assigned host class UUID
include_historical_recordsboolean
Include supported terminal roster records when host history access permits
Business bearer-JWT contract for an individual professional whose active organization remains their own workspace. The server derives the host venue and proves the exact active practitioner partnership, active host membership, primary/substitute class assignment, a past scheduled/completed class, and scheduling.manage_history plus the operation's ordinary host permission. Only existing-booking attendance, no-show, cancellation, and invalidation corrections are accepted; home-tenant and generic admin-booking execution are never reused. A UUID Idempotency-Key, typed REWRITE confirmation, reason, effective_at, genuine expected_updated_at booking CAS, and genuine expected_class_updated_at class CAS are mandatory. Success returns the database-read updated_at and class_updated_at tokens; stale state returns 409, while failed post-commit token readback returns 503 and requires an exact retry with the same key. Delivery defaults silent; an explicit Email/SMS/Push selection first requires the host's notifications.send permission and then fails 422 before mutation because historical delivery is unsupported. Executor: 20261020000009.
Parameters, scopes and examples
Required scopes
scheduling.manage_history
Path parameters
classIdstring · required
Assigned host class UUID
bookingIdstring · required
Host class booking UUID
One existing partner-roster correction. The host organization is relationship-derived and cannot be selected in the body.
Returns venue-owned purchase and promotion catalog selections, including retired items used by historical rules, plus the categories supported by the venue capabilities. Appointment-only and mixed venues use the same catalog authority. The authenticated venue supplies the scope. No customer history or targeting authority is returned. Permission: marketing.flash_sales.
Returns consent, preference, contact and suppression-aware email/SMS reach for a venue-scoped marketing audience. audience_filter.eligibility accepts shared V1 commerce include/exclude rules for current pass state, completed purchases and prior promotion use, including inclusive custom dates in the venue timezone. Valid rules return policy code COMMERCE_AUDIENCE and are evaluated server-side; unresolved facts produce an unavailable matched count. Unknown or malformed rules are refused without being dropped. Health, attendance and fitness-behaviour sources are not accepted. Permission: marketing.flash_sales.
Returns canonical refundable headroom, currency_decimal_places, payer receipt contacts, receipt_channels availability for Email/SMS/Push with explicit disabled reasons, original card brand/last4, venue refund destinations, and eligibility. Notification availability uses the same proven recipient and preference checks as the refund workflow. Native clients must use this response instead of deriving refund options locally. Staff refund notification choices start empty; only explicit channels request contact.
Idempotency-Key required. Body uses major units: { amount, reason? (staff-only), client_receipt_comment? (client-visible), destination_id? (original or venue method id), method_reference?, guest_booking_id? }. Returns the immutable review fields and full-refund confirmation phrase. While the payment has an open critical refund recovery case (processor_result_ambiguous, refund_state_mismatch or venue_funds_restore_required) the review is refused with 409 REFUND_RECOVERY_CASE_OPEN; resolve or re-issue on the web payment detail first.
Recent published flash sales with promo code, lifecycle_status, linked campaign, and delivery outcomes for the Business app. Includes email_cta_destination {type:"offer"} or {type:"custom",url:string}; legacy rows default to offer. Private draft_input is never returned; drafts use /admin/marketing/flash-sales/drafts. Permission: marketing.flash_sales.
Creates a venue-scoped promo and public offer, optionally queuing a consent-gated email/SMS campaign. Idempotency-Key required. Permission: marketing.flash_sales; non-empty notification_channels also requires notifications.send before business mutation and X-BookingBible-Confirm-Delivery: QUEUE. Optional X-BookingBible-Reviewed-Recipient-Count compares reviewed reach with the fresh consent-safe audience before mutation. Optional email_cta_destination is {type:"offer"} (also the omission default) or {type:"custom",url:string}: an absolute HTTPS URL of at most 2048 characters without credentials, control characters or backslashes. Invalid input returns 400 VALIDATION_ERROR. The destination changes only the email button; SMS and public purchase links remain canonical venue offer links. Generated announcement copy does not reveal the internal coupon. This setting never selects a delivery channel or authorizes sending.
Parameters, scopes and examples
Flash-sale fields plus optional email-only button destination. Existing notification and confirmation controls still apply.
Deactivates the sale and its linked promo code. Idempotency-Key required. Permission: marketing.flash_sales.
Parameters, scopes and examples
Path parameters
idstring · required
Flash sale UUID
GET/api/v1/admin/marketing/campaignsBearer token
Recent campaign outcomes
Venue-scoped campaign lifecycle and delivery metrics for the Business app. Campaign type is email, sms, both (email + SMS only), or push. Push uses subject as title and sent_count as recipient acceptance count, not device count or proof of delivery. Reading history and saving unscheduled drafts require marketing.campaigns, not notifications.send. Scheduled creation, queueing, sending, and actual test delivery additionally require notifications.send in the canonical campaign engine; no destination or logo setting grants delivery authority.
Idempotency-Key required and used as the operation key. Resolves {id} in the authenticated organization, runs UTC flashSaleDuplicatePrefill, and inserts a deterministic draft UUID derived from org/actor/source/action/key. Unique conflict replays the matching org/actor/draft row without overwrite. Never mutates the source, creates a promo, publishes, or sends. The copy is a standalone sale; web admin "+ Add run" (a draft in the same run series, migration 109) is not part of this endpoint and its series fields are not returned. Permission: marketing.flash_sales.
Returns at most 50 organization-scoped draft rows (id, name, window, products, edit_revision). Raw draft_input, promo codes and campaigns are omitted. Permission: marketing.flash_sales.
Returns validated FlashSaleDraftWire plus edit_revision for a private draft in the authenticated organization. Invalid stored input fails closed. Permission: marketing.flash_sales.
Idempotency-Key required. Save-only full FlashSaleInput round-trip including presentation and mixed family. Requires expected_revision compare-and-set; stale revisions return 409 DRAFT_CHANGED. Not publication. Permission: marketing.flash_sales.
Idempotency-Key required. Converts the same draft row to published through publish_flash_sale_v1. Body is FlashSaleDraftWire plus expected_revision. Returns draft_transition {status:published, draft_id, published_sale_id}. Same key/fingerprint replays the durable receipt. A new key against the same draft returns 409 DRAFT_ALREADY_PUBLISHED. Non-none notification_channels require notifications.send and X-BookingBible-Confirm-Delivery: QUEUE before the RPC. Optional replace-the-running-sale: a 422 FLASH_SALE_OVERLAP may include error.details.replaceable_sale; repeating the request with X-BookingBible-Replace-Sale-Id and X-BookingBible-Replace-Sale-Revision (both or neither) ends that sale and publishes in one transaction through publish_flash_sale_v2, carrying its promo claims into max_redemptions and adding replacement to the response; other overlaps still fail. Permission: marketing.flash_sales.
Parameters, scopes and examples
Required scopes
marketing.flash_sales
Path parameters
draftIdstring · required
Draft UUID
GET/api/v1/admin/gift-cardsBearer token
List venue gift cards
Returns the venue gift-card ledger for the Business app, including remaining balance and module-off historical rights. Optional code query looks up one card. New issue/options remain module-gated. Permission: gift_cards.sell.
Parameters, scopes and examples
Required scopes
gift_cards.sell
Query parameters
codestring
Exact gift-card code for a balance lookup
POST/api/v1/admin/gift-cardsBearer token
Issue a desk gift card
Creates or recovers one original venue gift card. Delivery is default-silent: omitted send_notification never delivers. send_notification=true requires notifications.send plus a recipient. Idempotency-Key required; NEW issues require bb-gift-issue-v1-<UUID v4>. Already-bound originals recover with their exact original key/body; unbound legacy keys require review and never mint. Success adds purchase_preserved=true and operation_id to id/code/amount/notification_sent/warning. 409 IDEMPOTENCY_KEY_REUSE_MISMATCH, GIFT_CARD_ISSUE_PROCESSING or GIFT_CARD_ISSUE_REVIEW_REQUIRED retain the original operation; never retry with a fresh key. HTTP cache TTL is not issuance authority. Permission: gift_cards.sell.
Explicit, default-silent staff delivery of an already-paid gift card. channels chooses Email/SMS/Push; omitted or empty sends nothing. Recipient overrides freeze with the delivery and do not change purchase fields. Optional predecessors email/sms UUIDs identify an explicitly confirmed successor to a known terminal delivery; omitted means the one initial channel delivery. Changed Idempotency-Key values cannot fork either identity. Per-channel accepted, delivered, pending, needs_review, unavailable or failed results preserve the completed purchase; accepted is not a delivery receipt. Ambiguous provider outcomes remain held and HTTP claims are not released. Push reports unavailable without a durable recipient binding. Requires gift_cards.sell, members.contact, and notifications.send. Idempotency-Key required.
Parameters, scopes and examples
Required scopes
gift_cards.sellmembers.contactnotifications.send
Path parameters
idstring · required
Gift card UUID
Explicit post-sale gift delivery; optional predecessors map email/sms to terminal delivery UUIDs for a separately confirmed resend
Delivery is default-silent. send_notification=true sends the inbox reply and requires notifications.send; otherwise the body is stored as an internal staff note. Idempotency-Key required. Permission: bookings.manage.
{
"body": "We can do Thursday at 10.",
"send_notification": false
}
GET/api/v1/admin/services/bundlesBearer token
List combo packages
Phone-useful combo-package list with price and item count. Full bundle builder stays on web admin. Permission: bookings.manage.
Parameters, scopes and examples
Required scopes
bookings.manage
GET/api/v1/admin/staffBearer token
List staff
Venue-membership staff directory: name, role, membership_status and capabilities; is_active is true only for active membership. Includes additive service-delivering collaborators, but returns email/phone as null when the profile belongs to another home venue. Assignment selectors must use active status and actual delivery capabilities, never the staff label alone. No payroll or commission data. Permission: staff.view.
Parameters, scopes and examples
Required scopes
staff.view
GET/api/v1/admin/sites/statusBearer token
Website status snapshot
Returns the org-scoped venue website summary for the Business app: site identity (incl. brand/location coverage), draft/published versions, preview URL, publish-readiness blockers/warnings, connected custom-domain verification/SSL state, and the additive per-site `entitlement` block (kind paid|trial|expired|legacy_preview|none, can_write_draft, can_publish, trial_expires_at, trial_remaining_ms — S12b). Permission: sites.view.
Parameters, scopes and examples
Required scopes
sites.view
POST/api/v1/admin/sites/publishBearer token
Publish the venue website
Publishes one tenant-bound website through the canonical publish core. Requires sites.publish plus a stable Idempotency-Key. Returns readiness blockers when the draft is not yet publishable; older mobile builds may ignore additive warnings.
Parameters, scopes and examples
Required scopes
sites.publish
Tenant-scoped website publish request
Request body
{
"site_id": "00000000-0000-4000-8000-000000000001",
"note": "Published after mobile review"
}
POST/api/v1/admin/sites/ai/chatBearer token
Talk to the website builder AI
Business-app SSE transport for the org-scoped website builder assistant. Requires sites.manage. Returns the mobile AI event vocabulary over Server-Sent Events and adds `site_patch` events so clients can refresh status mid-turn.
Parameters, scopes and examples
Required scopes
sites.manage
Tenant-bound site-builder message
Request body
{
"site_id": "00000000-0000-4000-8000-000000000001",
"conversation_id": null,
"message": "Make the homepage warmer and highlight workshops.",
"attachment_ids": [
"00000000-0000-4000-8000-000000000002"
]
}
POST/api/v1/admin/sites/attachmentsBearer token
Upload a website-builder attachment
Multipart upload for the Business website builder chat. Requires sites.manage. The file is stored through the shared AI attachment pipeline and later referenced by `attachment_ids` on the site chat route.
Parameters, scopes and examples
Required scopes
sites.manage
DELETE/api/v1/admin/sites/attachmentsBearer token
Delete a pending website-builder attachment
Deletes one tenant-owned, not-yet-bound website-builder attachment before it is sent in chat. Requires sites.manage.
Returns the frozen current allowance, published recurring choices, pricing version, and pending renewal change for an organization-owned issued pass. Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID
GET/api/v1/admin/gift-cards/{id}/sendBearer token
Gift-card delivery availability
Read-only per-channel availability and current deliveries (delivery_id, status, retryable) for a paid gift card, including module-off historical rights. Provider accepted is distinct from delivered; needs_review cannot be blindly retried. Push is unavailable until a gift has a durable recipient-account/device binding; the purchaser is never substituted. Requires gift_cards.sell, members.contact, and notifications.send.
Preview or schedule a client Flexible membership change
Previews or schedules a quantity/unlimited change for renewal 1–24 against an unexpired quote. Organization ownership is enforced and the new entitlement applies only after the target renewal invoice is paid. Permission: passes.manage.
Creates, replaces, or removes the one automatic clips-empty offer for a limited recurring pass or still-valid class pack. The target may be hidden from public catalogs. Permission: passes.manage.
Read-only desk catalog of published, active, in-window flash sales with at least one available promo. Returns organization_id plus id, name, applicable_pass_type_ids, ends_at, rule_label. Promo codes are never returned. Permission: pos.access with venue-wide location access. Does not require marketing.flash_sales. Final eligibility stays on pass-preview/sale.
List recently ended flash sales for a staff exception
STAFF-PROMO-WINDOW-EXCEPTION-01. Same { organization_id, sales[] } shape as GET /admin/pos/flash-sales (id, name, applicable_pass_type_ids, ends_at, rule_label; never a promo code), listing published, still-active sales that ended by time in the last 90 days whose code is active and below its total caps. Deactivated sales are never listed. Permission: pos.access with venue-wide location access AND marketing.flash_sales in the same venue (403 otherwise). An ended sale is sold only with promo_window_exception { kind: flash_sale_after_end, reason } on POST /admin/pos/sale or /admin/memberships; final eligibility and caps stay on those calls.
Redeem one expired partner/batch code for a client (staff exception)
STAFF-PROMO-WINDOW-EXCEPTION-01. Staff JWT with pos.access OR pos.sell, venue-wide location access AND marketing.campaigns in the same venue. Body: { member_id, code, reason } (reason 5-500 characters, kept in the audit log). Waives ONLY the code’s own expiry and its campaign’s redeem-by deadline (expired in the last 90 days); a voided, redeemed, reserved, staff-expired or inactive code, a cancelled/paused campaign, per-client and total limits and customer eligibility still refuse (422). A pass-granting code is redeemed through the canonical atomic grant claim and returns { outcome: granted, pass_id, code_label }; replays for the same code and client converge. A code that unlocks a flash sale returns { outcome: sale_required, code_label, pass_type_ids } and is sold with promo_window_exception { kind: late_code_redemption, reason } on POST /admin/pos/sale or /admin/memberships. Expired discount campaign codes (422 LATE_DISCOUNT_CODE_UNSUPPORTED) and plain venue promo codes without a campaign (422 LATE_CODE_NOT_CAMPAIGN) are refused. Records a 30-minute database exception and an audit_log row; customer self-service stays refused.
Parameters, scopes and examples
Required scopes
pos.accessmarketing.campaigns
The client, the code as typed, and the staff reason
Request body
{
"member_id": "uuid",
"code": "DOWNTOWN-ABC123",
"reason": "Client was away when the code expired"
}
Staff JWT only. Requires pos.access, products.manage_inventory and venue-wide location access. Returns transaction_id, receipt line_names, and evidence-backed items with product_id, current catalog product_name, sold_quantity, returned_quantity and returnable_quantity. Bundle constituents come from original sold movements, never current bundle definitions. Foreign/missing sales return the same 404. No monetary refund or sale-status dependency.
Staff JWT, pos.access plus products.manage_inventory, venue-wide location access. Required Idempotency-Key (8-160 ASCII letters/digits/colon/underscore/hyphen). Body {items:[{product_id,quantity}],reason}; unique products, positive integer quantities, 1-500 character reason. Atomically persists physical return, stock batch and audit. The full reason is private to the canonical return; stock movements use a fixed operational label and audit records only reason_present plus operation identifiers. Replays bind organization, actor, transaction, normalized items and reason. Returns return_id, transaction_id, stock_batch_id, items, reason, created_at, replayed; clients must match that receipt to the submitted transaction/items/reason before clearing the attempt. POS_RETURN_STALE (409) requires refreshing quantities; POS_RETURN_KEY_CONFLICT (409) rejects changed intent; POS_RETURN_UNAVAILABLE (503) retries the identical key/body. Inventory only: no refund, credit, receipt total, commission, entitlement or notification changes.
Parameters, scopes and examples
Required scopes
pos.accessproducts.manage_inventory
GET/api/v1/admin/pos/favoritesBearer token
List ranked quick-sale favorites
Returns ranked quick-sale tiles (admin pins first, then rolling-90-day volume, cap 8), the saved pin list, and the active pass/product catalog used to rank them. Uses getQuickSaleFavoritesForOrg. Permission: pos.access or pos.sell with venue-wide staff location access. Does not require settings.business.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/favoritesBearer token
Save admin/manager quick-sale pins
Replaces organizations.settings.pos.favorites through savePosFavoritePinsForOrg and updateOrgSettingsWithCas. A failed or missing settings read does not write. Unrelated settings and other pos keys are preserved. Body is { pinned: [{ kind: pass_type|product, id }] } (max 24). Returns the same ranked payload as GET. Permission: pos.manage (admin/manager). Does not rewrite settings.business.
Parameters, scopes and examples
Required scopes
pos.manage
GET/api/v1/admin/pos/pass-typesBearer token
List the staff POS pass catalog
Returns every active venue pass type for authenticated POS staff, including pass types intentionally hidden from public consumer catalogs, plus pricing_mode, published flexible_pricing_config, binding_tiers, vat_config/effective_vat, is_recurring, shop_visibility, category and price_amount. Permission: pos.access with venue-wide staff location access.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/servicesBearer token
List the staff POS service catalog
Returns active appointment services, their variants and active linked providers for authenticated POS staff. Class-only venues receive an empty list. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/perksBearer token
List verified course perks for a POS client
Returns the selected venue member’s currently available product perk entitlements. Tenant membership is revalidated and entitlement reads fail closed. Claims are supported only on product/bundle-only sales; pass, service and mixed service carts are refused before collection, regardless of tender. Direct product payments commit perks atomically with sale effects. Permission: pos.access or pos.sell.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/pass-previewBearer token
Preview an authoritative standard pass total
Runs the canonical pass sale pricing, fee, discount and VAT engine without collection or writes. Optional additive flash_sale_id is resolved by the existing sale core; do not send promo_code with it. Backdated windows additionally require passes.manage. Permission: pos.access or pos.sell.
Recover the original selected-currency product sale
Financial recovery by { operation_key }. Requires pos.access or pos.sell and venue-wide location access; the original immutable sold_by_staff_id must match the authenticated actor. Returns unresolved for missing, ambiguous, malformed, canceled, quarantined or inconsistent evidence; absence never permits a replacement collection. Pending replies include the frozen original currency, amount_minor, total, subtotal, vat_amount, member_id, items and product_price_book_versions. Completed replies additionally include validated transaction_id, payment_id and payment_status after matching both scoped ledger records to the original operation. For a bound or succeeded operation, retrieves the exact frozen Stripe intent on its original account. Only verified provider success can atomically create or adopt the original payment and POS transaction and complete the frozen items and stock through the canonical completion RPC. Never creates or confirms a provider intent or sends a receipt. Quarantined operations require manual reconciliation. Only completed recovery may release a browser marker. POST keeps original keys out of URLs; responses are no-store.
Discover qualified currencies for an ordinary product basket
Read-only discovery by { member_id, items } for ordinary product quantities, without an initial currency or payment key. Requires pos.access or pos.sell, venue-wide location access and a scoped member. Returns { available_currencies: string[] }, the exact-price intersection of active product books for verified home-country catalog tax, qualified precision, owned nonrecurring products, quantity limits and available SQL recovery. Unsupported variants, benefits and commissions remain refused. Empty choices never authorize venue-currency fallback. Responses are no-store. Each chosen currency still requires its own signed catalog-preview and exact original sale/finalize body. Discovery creates no payment operation.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/catalog-previewBearer token
Preview an authoritative POS basket total
Quotes any multi-line register basket that POST /admin/pos/sale completes as one sale: products and bundles (product engine) or services with products (service engine). Runs canonical catalog repricing, course-perk resolution (product/bundle baskets), discount composition and per-line VAT without collection or writes, and returns { subtotal, vatAmount, total, discountAmount, currency }. Optional product_currency selects exact gross product books for an authenticated member with home-country evidence and ordinary product-only lines; the response additionally returns product_price_book_versions and product_quote, a signed proof of actor, member, residence evidence, exact items and inclusive tax provenance. Send the exact product_quote, those versions, that currency and total as expected_total to the sale endpoint; preserve them across retries and SCA finalize. Commission/benefit/foreign-country refusals are 422 PRODUCT_PRICE_BOOK_UNAVAILABLE. Pass lines return 422 PRODUCTS_REQUIRED (use pass-preview); bundles with services return 422 MIXED_CART_NOT_SUPPORTED; products.max_quantity_per_order returns 422 PRODUCT_QUANTITY_LIMIT with details; a register-access denial returns 403. Permission: pos.access or pos.sell.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/bundlesBearer token
List the staff POS product-bundle catalog
Returns active product bundles for authenticated POS staff. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/gift-cardsBearer token
Sell a gift card through the register
Creates a POS gift-card sale through posSellGiftCard (payment + pos_transactions). Silent by default. Permission: pos.sell. Idempotency-Key required.
Returns the same short-lived server quote used by desk Flexible sales. Selection uses passSelectionSchema ({kind:quantity|unlimited} or {kind:option, optionId}). Permission: pos.access or pos.sell.
Parameters, scopes and examples
Required scopes
pos.access
GET/api/v1/admin/pos/eodBearer token
Load end-of-day reconciliation inputs
Returns original tender legs, recorded refund allocations, account-scoped Stripe settlements and the close record for a venue-local ledger date. Unverified methods cannot establish a balanced day. Refunds use their recorded creation date; this is not a fiscal Z report. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/eod/closeBearer token
Close the register for a business date
Recalculates and stores the current end-of-day reconciliation per organization and business date. A later close replaces that date’s stored record; historical close revisions are not yet retained. Unverified lines remain unbalanced. Permission: pos.sell.
Parameters, scopes and examples
Required scopes
pos.sell
GET/api/v1/admin/pos/kioskBearer token
Read kiosk register configuration
Returns kiosk enablement, receipt mode and idle timeout without staff PIN secrets. Permission: pos.access.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/kiosk/pinBearer token
Validate a kiosk staff PIN
Checks a staff PIN against the venue kiosk configuration. Permission: pos.access.
Get the Move-to-Flexible options for a client membership
FLEX-RETIRE-01 — ADMIN-ONLY. Returns the source fixed-price membership, its next renewal date (the only possible effective date), the sellable Flexible pass types with their published allowance ranges, and any pending switch. Never offered to a member. Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID (the fixed-price membership being retired)
Preview or schedule a Move to Flexible at next renewal
action=preview returns the ordinary desk-mint review (pricing, timeline, saved card, review fingerprint, Flexible quote) anchored on the old renewal date with the registration fee waived. action=schedule echoes the quote + review fingerprint + a caller-owned attempt id: it mints the Flexible membership pending_activation on that date, schedules the old subscription to end at its current period end, and links the two on the membership operation. Idempotent per attempt; a second pending switch is refused 409 SWITCH_PENDING. Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID (the fixed-price membership being retired)
Reversible until the effective date: undoes the old subscription’s scheduled cancellation first, then voids the pending Flexible pass (nothing was charged). Permission: passes.manage.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Issued pass UUID (the fixed-price membership being retired)
Business-app stream-control DTO for one tenant-bound class instance. Returns source options without provider live-stream ids, stream keys, RTMP URLs, SRT URLs, or playback URLs; action availability includes exact disabled reason codes. Visible to venue schedule/check-in readers and assigned staff roster readers. Source/go-live writes repeat module/settings gates; end remains available for safe live shutdown.
Sets or clears the occurrence-level stream source before the class goes live. Requires class.create, active streaming module/entitlement, a streamable class, an active RTMP/SRT source with an attached provider stream, tenant binding, lifecycle CAS status=scheduled, UUID Idempotency-Key, and no assigned-instructor owner-toggle block. The operation is silent; notification fields are rejected.
Starts a class stream from an existing class provider stream or an active selected RTMP/SRT source. Requires class.create, active streaming entitlement/module, streaming settings enabled, streamable class, venue-local class date, tenant binding, lifecycle CAS status=scheduled, provider-adapter enablement, UUID Idempotency-Key, and no assigned-instructor owner-toggle block. Provider enablement is compensated when the DB transition fails or loses a race unless the winner uses the same stream. The route does not mint new one-time provider streams or expose source credentials; it is operationally silent and rejects notification fields.
Ends a live class stream. Requires class.create, tenant binding, lifecycle CAS status=live, and UUID Idempotency-Key. The class completion commits before the shared-stream provider disable guard runs; the response reports provider_stop as disabled, skipped_shared, failed, or not_applicable. The route records streaming usage after the class completion commit. It is operationally silent and rejects notification fields.
Retries only the provider disable step after a class has already completed; it never re-completes the class or repeats lifecycle effects. Requires class.create, tenant binding, UUID Idempotency-Key, and an attached provider stream. The shared-stream guard is organization-scoped and no blocking occurrence identifier is returned. This route is operationally silent and rejects notification fields.
PHONE-PUBLISH-01: mints an ephemeral, class-scoped provider live stream plus a hashed single-use claim token, bound to one organization, one class occurrence, and the preparing user, with a short server-enforced TTL. Requires class.create OR the assigned instructor when the venue enables instructor go-live, active streaming module/entitlement, streamable class, venue-local class date, the phone_publisher_sessions kill switch, tenant binding, and a UUID Idempotency-Key. Returns non-secret session state and the one-time claim token; NEVER ingest URLs, stream keys, or provider resource ids. One active session per class; supersession refuses while another device is actively publishing with a fresh heartbeat. On a live class paused by the revoke of the previous phone session (or a session prepared to resume it), prepare is allowed and REUSES that provider stream instead of minting one; a live class that is not paused, paused by the operator, or paused on a venue encoder stream answers 409 ALREADY_LIVE.
PHONE-PUBLISH-01: atomically consumes the single-use claim token and returns short-lived RTMPS ingest material exactly once (Cache-Control: private, no-store). Only the session creator may claim. Deliberately NOT idempotency-cached — a duplicate claim returns CLAIM_ALREADY_USED and the recovery path is revoke + prepare a new session; the old secret is never re-displayed. No ingest material is ever stored server-side.
PHONE-PUBLISH-01: transitions the claimed session to publishing and returns the authoritative session/provider/class status snapshot. Never marks the class live — only the provider active webhook does. Owner-bound, tenant-bound, UUID Idempotency-Key required.
PHONE-PUBLISH-01: periodic liveness touch returning session state, provider connection status (ingest fields stripped), and class lifecycle status. When the provider confirms an active input and the class is still scheduled inside the phone-publisher window, the snapshot reconciles the class to live (CAS; the cron sweep is the backstop). Owner-bound and naturally idempotent, so no Idempotency-Key is required. A stale heartbeat makes a publishing session eligible for takeover by another authorized device.
PHONE-PUBLISH-01: ends the active phone publisher session, completes the class when this session took it live (usage recorded after the completion commit), and records a DURABLE provider-cleanup outcome — a failed teardown is surfaced in GET state and retried, never hidden. Requires class.create or the permitted assigned instructor, tenant binding, and a UUID Idempotency-Key.
PHONE-PUBLISH-01: revokes a session whose device was lost or reinstalled or should no longer publish; the old secret is never re-displayed and a fresh session must be prepared and claimed. When the stream of the revoked session is the one that took the class live, the class is PAUSED (`stream_paused_at`, outcome `class_paused: true`) rather than completed, so a following prepare resumes on the same provider stream (viewers keep their playback id). A self-revoke keeps that stream enabled (`provider_stop.status: skipped_paused`); a take-over/admin revoke disables it (kicks the publisher) and the resume re-enables it. Only `…/end` completes the class. Requires class.create or the permitted assigned instructor, tenant binding, and a UUID Idempotency-Key.
PHONE-PUBLISH-01: explicitly retries a failed or pending provider teardown for a terminal publisher session. Requires class.create or the permitted assigned instructor, tenant binding, and a UUID Idempotency-Key.
Parameters, scopes and examples
Required scopes
class.create
Path parameters
classInstanceIdstring · required
Class instance UUID
PATCH/api/v1/admin/schedule/{id}Bearer or API key
Edit class instance
Update start/end time, instructor, class type, capacity, or room on a single class instance. Physical and online capacity use a class-row lock and current counts; optional expected_updated_at rejects a stale edit. Concurrent count/version conflicts return 409; preliminary capacity validation may return 422 CAPACITY_BELOW_BOOKED. Class-type changes recheck retained teachers against the new type and brand. Rooms must belong to the occurrence venue/location and fit the resulting capacity. Notifications default silent: omitted controls and legacy notify_attendees never send. notify.{clients,instructor}.channels (or the older notify.{audience,channels}) sends the branded schedule-change email/SMS/push on time/instructor/room changes: clients get class_schedule_changed, instructors class_schedule_changed_instructor. A chosen plan requires notifications.send; a channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason} before the edit; mixing shapes returns 422 MIXED_NOTIFY_SHAPES. Success adds notify_outcome (per audience × chosen channel {sent, skipped, failed, pending, reasons}) or null. Idempotency-Key honored; audit_log carries per-field from/to diffs.
Correct a past class instance with an audit record
Atomic, immutable-ledger correction for a past class. Requires a UUID Idempotency-Key, scheduling.manage_history plus scheduling.manage, expected_updated_at and expected_status for an existing row, a past effective_at, and typed REWRITE attestation. Assignment-only corrections preserve linked roster, financial, course, workshop, import and streaming records; all other corrections on linked classes require specialist review. Existing tenant, location and instructor erasure guards remain enforced. Cancellation-state corrections additionally require class.cancel. Notifications are always silent.
Parameters, scopes and examples
Required scopes
scheduling.manage_history
Path parameters
idstring · required
Class instance ID
Bounded historical class-instance correction
Request body
{
"operation": "class_instance.correct_timing",
"expected_updated_at": "2026-08-20T09:00:00.000Z",
"expected_status": "completed",
"history_reason": "Signed instructor log confirms the recorded class time",
"history_confirmation_token": "REWRITE",
"effective_at": "2026-08-20T10:00:00.000Z",
"intent": {
"startTime": "2026-08-20T08:00:00.000Z",
"endTime": "2026-08-20T09:00:00.000Z"
}
}
Atomic, immutable-ledger correction for a past recurring availability window. Requires a UUID Idempotency-Key, availability.manage_history, staff_portal.availability, staff.edit for another staff member, expected_updated_at plus expected_is_active for existing rows, a past effective_at, and typed REWRITE attestation. The staff path and tenant-owned row pin ownership. Notifications are always silent.
Applies explicit venue-local start/end time, room, instructor, capacity, or class type to 1–50 tenant-owned classes with per-row conflict/failure results. Notifications default silent; notify.{clients,instructor}.channels (or the older notify.{audience,channels}) requires notifications.send, is checked against the selection before any write (422 NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason}) and sends only after each successful write. Success adds notify_outcome merged across classes, or null. Idempotency-Key required.
Cancels 1–50 tenant-owned classes in one transaction: statuses, consumed credit returns, counters, audit and durable effects commit together. A refusal returns 409 CANCELLATION_CONFLICT with no partial business mutation. Success retains succeeded/succeeded_count/failed and adds committed, operation_id and effects_status (completed, pending or held). Optional expected_versions maps IDs to row versions. Every target requires current accessible-location scope. Parsed intent and active organization scope the key; changed intent or venue returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. A positive cache entry resumes durable effects. An unconfirmed commit returns 503 CANCELLATION_COMMIT_UNCONFIRMED with operation_id, committed=unknown and retry_same_operation=true in error.details; preserve the reviewed body and Idempotency-Key. Notifications default silent: omission and legacy notify_attendees never send. Explicit notify audience plus channel requires notifications.send; clients and instructors support Email/SMS/Push. notify.{clients,instructor}.channels gives each audience its own channels (an instructor-only notice needs no client channel); the legacy notify.{audience,channels} shape gives the instructor the client channels minus Push, and its Push needs the client audience (422). Mixing both shapes returns 422. A chosen channel that reaches nobody in the selection returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE with {audience, channel, reason_code, gate_code, unavailable_reason} before any class changes. Success adds notify_outcome: per audience × chosen channel {sent, skipped, failed, pending, reasons[{code, count, label}]}. Idempotency-Key required.
POST/api/v1/admin/schedule/{id}/cancelBearer or API key
Cancel class
Atomically cancel a class and its bookings, return only actually consumed eligible credits, reset counters, and record audit plus durable delivery obligations. Optional expected_updated_at protects the reviewed version. A committed result adds operation_id and effects_status; pending/held delivery is not a failed cancellation. Retrying the same Idempotency-Key replays the operation. Notifications default silent: omission and legacy notify_attendees never send. Explicit notify.{audience,channels} or notify.{clients,instructor}.channels requires notifications.send; channel choices AND with recipient preferences. Clients and instructors support Email/SMS/Push. The per-audience shape gives the instructor its own channels (instructor-only is valid); the legacy shape gives the instructor the client channels minus Push, and its Push needs the client audience (422). Mixing both shapes returns 422. A chosen channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE with {audience, channel, reason_code, gate_code, unavailable_reason} before the class changes; success adds notify_outcome (per audience × chosen channel {sent, skipped, failed, pending, reasons}). Past classes return 409 HISTORICAL_CORRECTION_REQUIRED and must use the dedicated /api/v1/admin/schedule/{id}/historical-corrections endpoint with reason, REWRITE attestation, history permission and compare-and-set evidence. Idempotency-Key required. Unconfirmed commit returns 503 CANCELLATION_COMMIT_UNCONFIRMED with operation_id, committed=unknown and retry_same_operation=true in error.details. Keep the same reviewed body and key; never replace them after an uncertain outcome. Current notification and location authority are checked before replay. Keys are bound to parsed intent and active organization; mismatches return 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Durable replay resumes effects.
Requires scheduling.manage and Idempotency-Key. Accepts expected_updated_at, restore_booking_ids, confirm_conflicts and optional reason. auto_rebook_clients defaults false; when explicitly true it selects eligible cancellation-snapshot IDs for review. Selected rebooking also requires bookings.manage. Old cancelled rows remain immutable; canonical eligibility creates new linked bookings with confirmed, waitlisted, ineligible or pending outcomes. One rejected client does not roll back activation. Notifications start silent. notify.{clients,instructor}.channels (or the older notify.{clients,instructor} booleans with notify.channels) requires notifications.send and is checked before mutation: a channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason}. The instructor (class_restored_instructor) is told with the restore; clients (class_uncancelled) are told when their rebooking resolves — rebooked, waitlist place restored, or "book again" — through the durable staff notification outbox. Success includes committed, operationId, bookings, effectsStatus and notify_outcome (per audience × chosen channel {sent, skipped, failed, pending, reasons}); stale or historical rows return 409. Lost atomic acknowledgements return 503 REACTIVATION_COMMIT_UNCONFIRMED with operation_id, committed=unknown and retry_same_operation=true in error.details. Explicit selected booking IDs replay with the default auto_rebook_clients=false. Keep the same reviewed body and Idempotency-Key until the receipt resolves. Current location, selected-client booking and notification authority are checked before replay; a replay with a different notification plan is refused. Parsed intent and active organization scope the key; mismatches return 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Positive cached success resumes the durable restoration operation.
Parameters, scopes and examples
Required scopes
write:schedule
Path parameters
idstring · required
Class instance ID
Reviewed restoration with an optional per-audience notification plan
POST/api/v1/admin/schedule/{id}/substituteBearer or API key
Assign substitute
Replace instructor for a class. Validates no scheduling conflicts across locations. Notifications default silent. notify.{clients,instructor}.channels requires notifications.send: clients get class_schedule_changed and the instructor audience gets staff_assignment (the new substitute, and the teacher they replace) on every chosen channel (Email/SMS/Push). The older notify.{audience,channels} shape keeps its meaning (clients Push, substitute Email; other combinations 422). A channel that reaches nobody returns 422 NOTIFICATION_CHANNEL_UNAVAILABLE before the change. Success adds notify_outcome or null. Idempotency-Key is required and binds the caller, venue, class location and exact body. Replay rechecks current scheduling, location and selected notification authority. Concurrent requests are held while the scoped claim is retained; mismatched reuse returns 409. Unconfirmed outcomes return 503 SUBSTITUTE_COMMIT_UNCONFIRMED and require review without a new key. Definite scheduling conflicts remain 409 SCHEDULE_CONFLICT and permit a reviewed override. Expiring claims do not guarantee permanent deduplication.
Parameters, scopes and examples
Required scopes
write:schedule
Path parameters
idstring · required
Class instance ID
GET/api/v1/admin/checkinBearer or API key
Venue-local check-in day strip
The venue-local day's classes for the native staff check-in screen (Business app): per-class check-in/waitlist counts, room/instructor, and the day-navigation gates (today vs. read-only past/future). Defaults `date` to the venue-local today when omitted; optional `location_id` (query param or X-Location-ID header) narrows to one location. Each class carries additive `course_identifiers` (`{course_id, label, tone, course_name}`, hidden courses included; `[]` when none).
Parameters, scopes and examples
Required scopes
read:bookings
Query parameters
datestring
Venue-local date, YYYY-MM-DD. Defaults to the venue-local today.
location_idstring
Restrict results to one location. Also accepted as the X-Location-ID header.
GET/api/v1/admin/checkin/{classInstanceId}Bearer or API key
Attendee list
Class roster with member details, pass info, native course_access covering this booking, add-ons, included services, check-in status, and class/booking updated_at concurrency tokens. Whole-class cancellations retain the preserved roster and each attendee’s previous status; ordinary client cancellations remain excluded. Streaming bookers (attendance_type online) carry online_attendance { state: watching | attended | not_yet | null, playback_state, first_played_at, last_heartbeat_at } and are attended automatically when their stream plays; summary adds whole-class in_studio { total, confirmed, checked_in, no_show, waitlisted } and online { total, not_yet, watching, attended, no_show, waitlisted } counts. Every response returns a cursor (the database read time): pass it back as since= for a delta read that returns only attendees whose booking or stream session changed after since minus a 2-second overlap (merge by id; re-sends are idempotent), removed_ids for bookings that left the roster, delta: true and the whole-class summary. Poll the delta every few seconds while the screen is visible and do a full read on open, pull-to-refresh and periodically. Each row carries additive notification_availability.{booking_removal, waitlist_promote}.clients — per channel {available, reason_code, reason, recipient_id, reachable, total, blocked, reach_label}.
Parameters, scopes and examples
Required scopes
read:bookings
Path parameters
classInstanceIdstring · required
Class instance ID
Query parameters
include_historical_recordsstring
Include protected cancelled/late-cancelled roster rows; requires scheduling.manage_historyDefault: false
sincestring
The previous response cursor (ISO timestamp). Returns only changed attendees plus removed_ids; omit for the full roster.
Send a bulk email or SMS to a selected subset of one class instance. Submitted booking_ids are intersected server-side with the organization's active or whole-class-preserved roster; ordinary cancellations and stale/foreign ids are dropped and counted as skipped. Caller-supplied contact data is never accepted. Uses the canonical consent/suppression-aware bulk senders and requires the can_view_client_contact_info membership toggle. Idempotency-Key is honored.
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
classInstanceIdstring · required
Class instance UUID
Channel, roster booking ids, and message
Request body
{
"channel": "email",
"booking_ids": [
"00000000-0000-4000-8000-0000000000b1"
],
"subject": "Class update",
"message": "Hi {{first_name}} — here is an update about your class."
}
POST/api/v1/admin/checkin/{classInstanceId}/{bookingId}Bearer or API key
Check in member
Check a member into class through the canonical attendance core. Validates current tenant/location scope, class timing and late-arrival authority; after-cutoff requires booking.checkin.after_cutoff and explicit confirmation. Idempotency-Key is required for the reviewed attempt, and the committed receipt reports operation identity/effects. Notification is silent unless an explicit supported channel selection is authorized and preflighted. A streaming (online) booking is attended automatically when its stream plays: a staff check-in of one is refused with 409 ONLINE_ATTENDANCE_AUTO unless the body sets mark_attended_override: true (the audited "Mark attended" override, not subject to the late-arrival cutoff). The receipt adds attendance_type and online_attendance_override.
POST/api/v1/admin/checkin/{classInstanceId}/{bookingId}/noshowBearer or API key
Mark no-show
Mark member as no-show through the canonical attendance core. Validates current tenant/location scope and timing; no-show fee/clip effects are derived server-side and require the specific no-show grant. Idempotency-Key and the reviewed booking version protect retries. Notification is silent unless explicit channels pass notifications.send and recipient/event preflight.
Parameters, scopes and examples
Required scopes
write:checkin
Path parameters
classInstanceIdstring · required
Class instance ID
bookingIdstring · required
Booking ID
GET/api/v1/admin/membersBearer or API key
List members
Membership-driven client list with exact pre-pagination status and pass filtering. Native course grants count as covering entitlement: those clients are status=active (not no_pass) and each row may carry additive active_course_access { course_name } | null. Search by name, email, phone or venue client ID; optionally filter by tag, active pass type, or canonical pass family. pass_type_id and pass_family combine with AND semantics. Every successful response, including zero-match pages, includes meta.pass_type_options and meta.pass_family_options. Options expose distinct active-client counts across the full authenticated venue before pagination; pass types include current active templates plus archived templates still held by active clients, including types hidden from public pricing and course/workshop-managed types.
Parameters, scopes and examples
Required scopes
read:members
Query parameters
searchstring
Search by name, email, or phone
statusstring
Client status: active, inactive, new, or no_pass
tagstring
Filter by member tag
pass_type_idstring
Filter by active pass type
pass_familystring
Filter by pass family: recurring, class_pack, time_based, or intro_offer
Register an active client or send a pending client invitation. Enforces the venue plan limit, requires members.edit and an Idempotency-Key, and records a PII-safe audit event.
Full member profile: passes with course-fulfillment provenance and an additive, optional `provenance` field per pass (who sold it, how, when, plus the sale/activation/money facts of the one sale-provenance rule — gated on the caller's own payments.view, null otherwise, same rule as stripe_subscription_id/recent_payments), canonical native course_access, recent bookings/payments, tags, scores, credits, referrals, client_display_id, plus server-authoritative total_bookings and last_visit_at. The additive root-level date_format is the venue's display preference (DD/MM/YYYY, YYYY-MM-DD, DD.MM.YYYY or DD-MM-YYYY); profile.date_of_birth remains a canonical YYYY-MM-DD civil date for writes. Also returns per-channel notification_availability (email/SMS/push with exact unavailable reasons), contact_details_visibility {email,phone} (independent member.view_email / member.view_phone AND the membership contact toggle; contact_details_visible remains email AND phone for older app builds), and payments_visible. A 409 PROFILE_MERGED is returned when this profile was merged away in this venue, with primary_user_id of the survivor. Contact disclosure is fail-closed on PII_AUDIT_FAILED.
Soft-deactivates only the active membership at the selected venue; it never deletes the shared profile or changes memberships at other venues. Delivery is silent by default. An explicit notify object may select Email, SMS, and/or Push, which requires notifications.send and an availability preflight before the deactivation commits. A post-commit delivery failure is returned separately as notification_failure and never restores access. Protected Admin/Finance memberships retain their shared lifecycle authorization checks. Idempotency-Key required. Permission: members.delete.
Parameters, scopes and examples
Required scopes
members.delete
Path parameters
idstring · required
Member user ID
Deactivation reason and optional explicit client delivery channels
Tenant-scoped pass history with an exact total and opaque keyset cursor. Returns up to 100 records per page and never exposes processor subscription identifiers.
Deterministically merges live bookings and imported historical visits. Historical rows carry record_source=migration_history and read_only=true. The total is exact across both stores. Live rows with a recorded course consumption carry payment_status=course and course_coverage { course_name, state }; released course usage retains its returned state. Course coverage describes the recorded booking entitlement, not tuition payment. Every row also carries source, payment_status, class_instance.local_date and a permission-agnostic actions block (cancel, remove_waitlist, change_pass, correct_attendance, check_in, mark_no_show — all false on an imported row). meta.venue_today is the venue-local date the today gates were measured against; meta.stats (first page only, absent when after is sent) holds exact counts over the entire filtered set. Live rows carry additive notification_availability.{booking_removal, waitlist_promote}.clients in the staff availability contract.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
Query parameters
limitinteger
Items per page (1–100)Default: 25
afterstring
Opaque next_cursor from the previous page
pass_idstring
Only bookings funded by this pass
cycle_startstring
Venue-local YYYY-MM-DD start of a pass usage cycle (inclusive)
cycle_endstring
Venue-local YYYY-MM-DD end of a pass usage cycle (exclusive)
scopestring
upcoming = class start after now and the seat still held, ordered soonest first, no imported history; past = the exact complement, newest first. Omitted returns the merged view.
fromstring
Venue-local YYYY-MM-DD, inclusive, on the class start (imported rows compare on visit_date)
tostring
Venue-local YYYY-MM-DD, inclusive; converted to the next local midnight so evening classes stay in range
statusstring
Exact match on live rows; imported rows match on their normalised status, so confirmed, waitlisted and pending_payment never match one
class_type_idstring
Only classes of this class type (excludes imported history)
instructor_idstring
Only classes whose PRIMARY instructor is this person — a substitute does not match (excludes imported history)
location_idstring
Only classes at this location (excludes imported history)
brand_idstring
Only classes whose class type belongs to this brand (excludes imported history)
Tenant-scoped payments with exact total, refunds, safe card display, invoice linkage, and explicit receipt capabilities. Each refund has additive receipt_available=true only for a succeeded operation claim on a venue payment; pending, legacy non-claim and Apple merchant-of-record rows cannot offer the venue refund PDF. Additive receipt.bulk_email {available,reason_code,reason} describes eligibility for the existing bulk email route, including non-POS payments, independently of POS-only single-send capabilities. Bulk execution still requires members.contact and notifications.send and rechecks delivery policy. Older clients may ignore this field; newer clients fail closed on a malformed present field and retain the conservative single-email fallback when it is absent. Processor IDs, client secrets, and raw receipt URLs are never returned.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
Query parameters
limitinteger
Items per page (1–100)Default: 25
afterstring
Opaque next_cursor from the previous page
statusstring
Only payments with this status. Anything outside the list is a 400. Omitted = every status.
fromstring
Venue-local YYYY-MM-DD, inclusive. Applied to the page AND the total, so meta.total is the filtered total.
tostring
Venue-local YYYY-MM-DD, inclusive. from after to, or a day no calendar has, is a 400.
Re-sends through the canonical POS sender to the member contact stored on the server. Only same-venue POS-backed receipt payments are eligible; arbitrary recipients and payment retry are not supported. Idempotency-Key is required and atomically bound to the actor, venue, member, payment and selected channel. Retry the same request after response loss. A concurrent request returns IDEMPOTENCY_IN_PROGRESS; changed intent returns IDEMPOTENCY_KEY_REUSE_MISMATCH. RECEIPT_DELIVERY_UNCONFIRMED retains the uncertain outcome without another provider send. sent:true confirms provider acceptance, not delivery to the client.
Parameters, scopes and examples
Required scopes
notifications.send
Path parameters
idstring · required
Active member ID
paymentIdstring · required
Same-venue payment ID
Receipt delivery channel advertised by the payment receipt capability
Request body
{
"method": "email"
}
GET/api/v1/admin/paymentsBearer token
Org-wide recent sales
Business-app contract C6: the venue's recent payments (all statuses), newest first, mirroring the web sales drawer rows — plain-language method label, card label, money bucket (captured | recorded | internal), and the drawer's refund-offer rule (`refundable` = settled non-guest rows with money remaining). Amounts are integer minor units (øre). Each item also carries `sale_provenance` {sale, activation, money}: who made the sale and when (staff at the desk, the client online, …), when its pass starts, and how THIS payment was collected (at the desk, online, charged automatically when a scheduled pass started, a renewal, …) — `sold_by`/`channel` read the sale, never the payment. Cursor-paginated (opaque keyset cursor), limit ≤ 50. Range resolves in the venue's timezone.
Business-app contract C7: executes a claimed review through canonical processRefund. Body { review_intent_id, confirmation?, amount?, reason?, client_receipt_comment?, destination_id?, method_reference?, notify_client?, receipt_channels?: ('email'|'sms'|'push')[] }. Destination and notes must match the immutable review. Omitted or empty channels and a legacy boolean alone remain silent. Explicit channels require notifications.send before new refund admission; an unavailable channel returns 422 REFUND_CHANNEL_UNAVAILABLE with channel and reason_code. Recover an admitted operation with its original review and Idempotency-Key. A new operation is refused with 409 REFUND_RECOVERY_CASE_OPEN while the payment has an open critical refund recovery case (the same rule as the review). Success includes refund_id, refund_status, a printable bearer-authenticated PDF URL, and per-channel outcomes; pending approval or provider processing is not a settled refund or proof of notification delivery.
Parameters, scopes and examples
Required scopes
billing.refunds.same_daybilling.refunds.full
Path parameters
idstring · required
Payment UUID
Refund details (amount in MAJOR units)
Request body
{
"review_intent_id": "00000000-0000-4000-8000-000000000000",
"amount": 199,
"reason": "Client requested the refund",
"client_receipt_comment": "We hope to see you again soon.",
"destination_id": "original",
"notify_client": true,
"receipt_channels": [
"email",
"push"
]
}
GET/api/v1/admin/refunds/{id}/receiptBearer token
Download canonical refund receipt PDF
Bearer-authenticated, tenant-scoped, no-store PDF used by native print/share. Contains only the optional client receipt comment; the staff-only internal reason is never rendered.
Business-app contract C8: send a bulk email or SMS to participants of one class instance. Recipients are resolved server-side — the submitted booking_ids are intersected with the class's ACTIVE roster (confirmed/waitlisted/checked_in); stale ids are dropped and counted as skipped, and caller-supplied contact info is never accepted. Delegates to the same senders/consent semantics as the web check-in bulk bar (templates admin_bulk_email / admin_bulk_sms). Requires the TV-D can_view_client_contact_info membership toggle. Returns { sent, skipped }.
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
classInstanceIdstring · required
Class instance UUID
Channel, roster booking ids, and the message
Request body
{
"channel": "email",
"booking_ids": [
"00000000-0000-4000-8000-0000000000b1"
],
"subject": "Tonight’s class moves to Room 2",
"message": "Hi {{first_name}} — we moved tonight’s class to Room 2. See you there!"
}
Business-app parity: the eligible one-class products for paid guest spots. Uses the same guest visitor permission and catalog core as the web check-in screen.
Requires booking.checkin, current can_add_client_to_class and assigned-location access. Idempotency-Key (maximum 200 characters) and immutable full guest intent are required. Every retry reads the canonical receipt after current authorization, including after midnight; cached HTTP success never overrides current booking status. Silent: no payment or client notification.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance UUID
Freeze these guest fields together with Idempotency-Key until the operation is completed or definitely refused.
Business-app parity: add 1–20 guest spots as payment-link, paid-at-desk, or comp/free bookings. Payment-link delivery supports email, SMS, or both. Uses the same context-free core as web; Idempotency-Key required.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance UUID
Guest contact, count, payment mode, product, and delivery channels
Business-app contract C1: the venue's failed payments (payments.status='failed') in the trailing window (default 30 days), newest first, cap 100. Each row carries client + linked pass context, a plain-language method label, and `retryable` per the same pure decider the web Retry button uses. Amounts are integer minor units (øre).
Parameters, scopes and examples
Required scopes
members.view_insights
Query parameters
daysinteger
Trailing window in days (1–365)Default: 30
member_idstring
Client-account parity P3 — only this client’s failed payments (public display id or UUID). `count` is then that client’s count. An id that is not an active client of this venue answers 404.
Business-app contract C2: re-collect a failed payment through the canonical retry core (PaymentIntent confirm or off-session invoice pay on the SC2-resolved Connect account, driving handleInvoicePaid). The body may be empty for the provider default, or contain payment_method_id selected from the exact failed invoice/PaymentIntent customer wallet. A provider-proven legacy card_ default is accepted and retried as the customer default without an unsupported override. Idempotency-Key is required, atomically claimed before the provider charge, and bound to the venue, payment, and selected payment_method_id; simultaneous reuse cannot double-charge and reuse with another card returns 409. Returns status succeeded | requires_action | failed with a plain-language message.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
paymentIdstring · required
Failed payment UUID
Optional exact saved card override; omit the body to use the provider default.
Record an external settlement for a failed renewal
Business-app contract C3: the failed recurring-renewal invoice was paid through another channel (cash, bank transfer, MobilePay, external card terminal, other). Settles the Stripe invoice out-of-band so the canonical recovery reactivates the pass, attributing the recovered payments row to the real method. amount is integer minor units (øre). Idempotency-Key required.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
paymentIdstring · required
Failed payment UUID
Settlement details
Request body
{
"method": "bank_transfer",
"amount": 79900,
"paid_at": "2026-07-31",
"note": "Paid via bank transfer, ref 1234",
"notify_client": true
}
Business-app contract C4: comp the failed recurring-renewal cycle — the client keeps the period, 0 revenue is recorded (the recovered payments row is forced to 'comped' amount 0). Reason required. Idempotency-Key required.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
paymentIdstring · required
Failed payment UUID
Waive details
Request body
{
"reason": "Goodwill — studio closure week",
"notify_client": true
}
Business-app contract C5: venue-imposed suspension (distinct from the member freeze) — blocks bookings until unsuspended. Client delivery is silent by default and accepts only notify.audience.clients=true plus an explicit Email/SMS/Push selection; legacy notify_client/notify_channels inputs remain silent. A non-empty selection requires notifications.send, and an unavailable selected channel returns PASS_NOTIFICATION_CHANNEL_UNAVAILABLE before the pass changes. Idempotency-Key (UUID) required; replay returns the stored response and the stable delivery reference prevents re-sending. Delivery failure after the pass write is reported as notification.sent=false and never rolls the suspension back.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Optional reason and explicit, default-silent client notification choice
Business-app contract C5: lift a venue-imposed suspension. Body is optional and silent by default. Client delivery requires notify.audience.clients=true, an explicit Email/SMS/Push selection, and notifications.send; legacy booleans/arrays remain silent. An unavailable selected channel returns PASS_NOTIFICATION_CHANNEL_UNAVAILABLE before the pass changes. Idempotency-Key (UUID) required; replay never re-sends. A post-write delivery failure returns notification.sent=false without rolling the pass change back. 422 NOT_SUSPENDED for a pass that is not suspended.
Business-app contract C5 (PASS-REACTIVATE-01): flip an expired/cancelled NON-recurring pass back to active; a run-out window requires new_end_date ≥ venue-local today. Client delivery is silent by default and requires notify.audience.clients=true, explicit Email/SMS/Push channels, and notifications.send; legacy notification inputs remain silent. Unavailable channels reject before mutation. Idempotency-Key (UUID) required and replay never re-sends; delivery failure after the write returns notification.sent=false without rollback. Recurring memberships are refused (422 RECURRING_UNSUPPORTED) — restart via a real re-mint. Audit pass_reactivated + reverse_payload.
Review or recover the original later-booking cancellation plan
Requires bookings.manage and booking.cancel_member. Idempotency-Key is the original pass-change key; request is its exact version:1 body including reviewed expected_old_pass_id and expected_updated_at. mode:read returns null only if no plan or parent receipt exists. mode:prepare freezes the server-selected children without cancelling anything. The strict plan retains each original child key, reviewed class time/location, quiet cancellation terms and confirmed partial results in server order. A terminal original parent is returned before new planning. 503 BOOKING_PASS_CONFLICT_UNRESOLVED retains the original; it never proves absence.
Parameters, scopes and examples
Required scopes
bookings.managebooking.cancel_member
Path parameters
bookingIdstring · required
Original reassigned booking
Read or prepare with the unchanged parent request and original Idempotency-Key.
Recover, apply or close one reviewed later-booking cancellation
Requires bookings.manage, booking.cancel_member and canonical location/attendance authority. Send mode:read|apply|close and the exact server-frozen child request, with its original Idempotency-Key. Apply follows the original server order and returns only a correlated cancelled receipt. Close returns that committed receipt or closed_without_cancel; it never reverses a cancellation. Null is permitted only on read and is not closure. Changed facts, ALREADY_CANCELLED and 503 BOOKING_PASS_CONFLICT_UNRESOLVED retain the original. Notifications stay empty; shared usage remains retained. Retry the original parent only after every child has its own cancelled receipt.
Parameters, scopes and examples
Required scopes
bookings.managebooking.cancel_member
Path parameters
planIdstring · required
Frozen plan ID
childIdstring · required
Frozen child ID
mode and the exact request returned for this child; preserve all timestamps, status, class location and quiet terms.
Find the shared usage attached to a reviewed booking
Read-only. Requires passes.manage, bookings.manage and booking location access. The exact original expected_pass_id and expected_updated_at must still match. Returns null for funding with no share, or booking_id, pass_id, share_id and booking_updated_at. This only opens the explicit usage review; it never allocates usage, cancels a booking or releases an unresolved pass-change request.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
bookingIdstring · required
Reviewed booking ID
The original reviewed funding and booking version.
Current attendance state for one booking plus what a correction would do: current_status, any attendance fee and its refund payment, the venue no-show fee, whether a clip was consumed, and per-channel notification_availability (email/SMS/push with the exact unavailable reason) so the client-notify picker can disable what cannot be delivered. The read also returns booking_updated_at, can_check_in_after_cutoff, ended_class_confirmation_required, credit_applicable and no_show_forfeits_clip. can_refund_fee is always false here — refunds live in Billing.
Flip a booking between checked in, no-show, booked and removed for today or a past day (a future class returns 422 FUTURE_ATTENDANCE, a cancelled class 422 CLASS_CANCELLED). Marking a no-show additionally requires bookings.mark_no_show and removing a visit requires booking.cancel_member. Client delivery is silent by default: only an explicit notify object with a non-empty channel set sends, which requires notifications.send and a per-channel availability preflight before the correction (422 ATTENDANCE_NOTIFICATION_CHANNEL_UNAVAILABLE, 502 ATTENDANCE_NOTIFICATION_PREFLIGHT_FAILED). The legacy notify_client boolean is accepted but never delivers. refund_fee returns 409 REFUND_REVIEW_REQUIRED — refund the charged fee from its protected payment detail first. Idempotency-Key required and is bound to the actor, venue and parsed body; expected_updated_at and ended_class_confirmed are forwarded to the canonical compare-and-swap core. A changed retry returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Success is committed when returned with operationId, bookingUpdatedAt, creditDelta, seatDelta and effectsStatus; pending/held delivery remains a committed correction.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance ID
bookingIdstring · required
Booking ID
Target attendance state, fee/clip choices, expected_updated_at, ended_class_confirmed when required, and explicit client delivery channels
The client's passes that could fund this booking, each with why it is or is not eligible (clips remaining, end date, who shared it, whether it is the current pass, and whether using it would shift the pass start date). Includes booking:{booking_id,pass_id,updated_at}; retain that reviewed snapshot with the chosen pass and original request key. Eligibility comes from the booking engine itself.
Atomically change the pass funding a booking. Idempotency-Key (1–200 characters, no control characters) and the exact original body are retained for every retry. Reviewed expected_old_pass_id (including null) and expected_updated_at are required for protected POS passes; both reviewed fields must be supplied together or both omitted; omission remains distinct from null. Success includes success:true, operation_id, event_id, booking_id, previous_pass_id, new_pass_id, pass_id (legacy alias), booking_updated_at, old_credit_returned, new_credit_consumed, replayed, idempotency_key and the original version:1 request. Internal activation proof is not exposed. Shift conflicts require separate explicit resolution. Busy or malformed/unknown outcomes return 503 booking_reassign_unresolved; no error or lookup miss proves the original did not commit. Use the close endpoint before replacing a retained request.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
bookingIdstring · required
Booking ID
The chosen pass and exact reviewed booking snapshot; retain omitted fields as omitted on legacy retries.
Resolve an original pass-change attempt before replacing it
Requires bookings.manage and the same original Idempotency-Key and body as reassignment. Returns the original committed success receipt, or a strictly correlated success:false,error:booking_reassign_closed receipt with operation_id,booking_id,new_pass_id,request,replayed,idempotency_key. Only this closed receipt allows replacement; 409 conflicts, 422 refusals, missing results and 503 uncertainty retain the original. Closing never cancels or reverses a committed change and sends no notification.
Parameters, scopes and examples
Required scopes
bookings.manage
Path parameters
bookingIdstring · required
Original booking ID
Exact original reassignment body, with no refreshed expectations.
The venue catalogue the Business app builds the Visits filter sheet from: active class types (with their brand), this venue’s instructors, active locations and active brands. Served under the same grant as the list these choices filter, so a staff member who can see every booking can always load the sheet. Every list is org-scoped.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
GET/api/v1/admin/schedule/{id}Bearer or API key
Admin schedule item
One class instance in the GET /api/v1/admin/schedule item shape (per-status counts, concurrency token, instructor/substitute, historical_capabilities) plus notification_availability.{cancel,edit,substitute,restore}.{clients,instructor}.{email,sms,push}. Use this for a class detail instead of reading the whole schedule. Each channel entry is {available, reason_code, gate_code, reason, recipient_id, reachable, total, blocked, reach_label}: reason_code is the app vocabulary (venue_capability, unsupported, no_contact, invalid_contact, user_opted_out, no_registered_device, no_recipients, availability_error) and gate_code the precise gate. A channel is available when the venue gate passes and at least one recipient is reachable. Callers without class.cancel or notifications.send get every channel unavailable (availability_error). A protected historical class is 404 without historical read access. Requires schedule.view_all and current location access.
Parameters, scopes and examples
Required scopes
read:schedule
Path parameters
idstring · required
Class instance ID
POST/api/v1/admin/schedule/notification-availabilityBearer or API key
Notification channels for a class selection
Read-only. Returns {action, can_notify, clients, instructor} for 1–200 tenant-owned classes and one action (cancel, edit, substitute, restore). A channel is available when the venue gate passes and at least one recipient across the selection is reachable; counts and blocked reasons add up across classes. Clients start silent; the instructor picker may pre-select Email and Push when available. Restore reports unsupported until restore notifications ship. Requires schedule.view_all; can_notify reflects notifications.send, and callers with neither class.cancel nor notifications.send get every channel unavailable (availability_error). Foreign classes return 422 CLASS_SCOPE_MISMATCH.
Requires members.contact. Sends one templated request (profile_update_request) on the explicitly chosen channel asking the client to add a missing detail or confirm/update an existing one. The message is written as the client's acquisition brand of this venue (else the venue): branded email layout with a button, brand-named SMS and push. It links to the member profile opened at that detail (`/profile?field=<field>` on the brand/venue host; push data carries `type`, `field`, `url` path and `organization_id`). An address request to a member the age-based VAT exemption applies to also asks them to confirm it as their home address there. Venue-scoped, audited (member_profile_update_requested), one request per client and field per 24 hours. 404 CLIENT_NOT_FOUND outside the venue; 422 PROFILE_REQUEST_FAILED with a staff-readable reason (no email/phone on file, email blocked or not sent, no app device, already asked).
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
idstring · required
Client profile id
The detail to ask about and the one channel to send on.
Read-only payment snapshots retained during a brand split or transfer. These records are separate from the live payment ledger and do not support refunds, receipt resends, invoice actions or revenue totals. Each row retains its original amount in major currency units, ISO currency, source status, occurrence time and snapshot time. Archive IDs are not payment IDs. Pagination sorts by original occurred_at and archive ID; reuse the exact opaque cursor with the same venue and member. Invalid pagination returns 400; unavailable or unverified history returns 503 rather than an empty history. Requires members.view_insights and active membership in the authenticated venue.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active client ID in the current venue.
Query parameters
limitnumber
Page size: 1–100, default 25.
afterstring
Opaque next_cursor from the same archive collection and scope.
Removes one saved card from an active client of this venue. Requires members.contact and an Idempotency-Key (≤128 chars, bound to the venue, the operator, this client and this card; reuse for another card returns 409). The card must be on the client’s provider-proven wallet — an unknown id and another client’s id answer the same 404 PAYMENT_METHOD_NOT_FOUND, never an existence oracle. A shared platform-wallet card owned by another home venue answers 403 WALLET_FORBIDDEN. If the card was the default, the profile column and the exact Stripe customer default are cleared first (rolled back if the processor refuses) and only then is the card detached; a processor refusal answers 502 STRIPE_DETACH_FAILED. Audited; the client is never contacted.
Makes the named saved card the client’s default for future off-session charges. Requires members.contact and an Idempotency-Key. Only a card can be a default — a non-card method answers 422 NOT_A_CARD. 404 PAYMENT_METHOD_NOT_FOUND for an id not on the proven wallet, 403 WALLET_FORBIDDEN for another home venue’s shared wallet, 502 STRIPE_UPDATE_FAILED when the processor refuses (the local column write is rolled back). Setting the card that is already the default is a 200 no-op with no audit row. The client is never contacted.
Clears the client’s default card so nothing is charged off-session without a fresh choice. Requires members.contact and an Idempotency-Key. The path names the card the operator believes is current: if it is NOT the client’s default any more the request is refused with 409 NOT_DEFAULT (carrying the real default) rather than clearing a different card. 403 WALLET_FORBIDDEN and 502 STRIPE_UPDATE_FAILED as above. The client is never contacted.
Parameters, scopes and examples
Required scopes
members.contact
Path parameters
idstring · required
Active member ID
pmIdstring · required
The card the operator believes is the current default
Emails a payment receipt for each selected payment of this venue’s active client, including non-POS rows the single-payment receipt/send route skips. Requires members.contact plus notifications.send on this request, and an Idempotency-Key. Body is { payment_ids: uuid[] } (1…100). Each item reports sent, deduped, ineligible, or failed. Preserve the original member, payment set and key when recovering an interrupted request; do not start another batch merely because its response was lost. The client is contacted by email only; omission of the key is 400, a foreign client is 404.
What the desk charge form needs before it can be shown: the venue currency, the display VAT rate (decimal) and its label, and the venue’s accounting categories. Requires pos.sell. An empty categories list means the venue has not set any up — the app disables the form and points at Settings rather than charging into a required column.
Charges an ad-hoc amount to the client’s saved card off-session (a phone payment, a fee) and records the sale. Requires pos.sell and an Idempotency-Key. amount_minor is an integer in minor units and is VAT-inclusive — exactly what the card is charged. The card’s proven Stripe customer/account is what the PaymentIntent is created on and confirmed on. 201 on success with the new payment id, the receipt reference and the card label. 422 CHARGE_NOT_CHARGEABLE with reason payments_disabled | not_on_wallet | below_minimum | connect_not_active means nothing was attempted and the same key may be reused. 402 CHARGE_REQUIRES_ACTION means the card needs the client’s own authentication — the app then offers the secure card link. 402 CHARGE_DECLINED and 500 CHARGE_FAILED keep the key. set_as_default applies the card as the default afterwards, best effort; default_updated is null when it was not requested. No receipt is sent — the app offers the receipt route afterwards with the returned payment_id.
Parameters, scopes and examples
Required scopes
pos.sell
Path parameters
idstring · required
Active member ID
The card, the VAT-inclusive amount in minor units, and the bookkeeping fields
The client’s open no-show and late-cancel debt, newest first. A declined charge stays on the list — pending and failed both mean money is still owed. Requires passes.manage, the same grant the web read uses; collecting or waiving needs billing.refunds.full. Amounts are integer minor units beside their currency, and each row carries the class name and start time when the linked booking still has them.
Collects an open no-show or late-cancel fee from the client’s card. Requires billing.refunds.full and an Idempotency-Key. The fee is loaded scoped to this venue (404 otherwise) and must still be open — a charged, waived or refunded fee answers 422 FEE_NOT_UNPAID with its status before anything is reserved. A declined card answers 422 FEE_CHARGE_DECLINED and the debt stays on the client. Every other non-charged outcome answers 422 FEE_NOT_CHARGEABLE with a plain-language message and details.collection: not_chargeable (no card on file, or card payments not ready — nothing attempted), in_progress (another charge of this fee is running), ambiguous (the provider result is not confirmed; the fee stays protected and is replayed with the same provider key), captured_unrecorded (the card was charged and the receipt is still being recorded — never charged again) or reconciliation_required (an earlier charge needs payment review). The fee is claimed in the database before the card is charged, so the same key may be reused safely. The client is never contacted.
Records that an open fee was collected outside the card processor — cash, bank transfer, MobilePay, an external card terminal, or other. Requires billing.refunds.full and an Idempotency-Key. There is no amount: a fee is always settled for its own amount. The venue-scoped load and the 422 FEE_NOT_UNPAID check run before anything is reserved. The underlying write is atomic and idempotent on a re-run. A refusal answers 422 FEE_SETTLE_REJECTED with the reason. The client is never contacted.
Parameters, scopes and examples
Required scopes
billing.refunds.full
Path parameters
feeIdstring · required
Cancellation fee ID
How the money was collected, and an optional internal note
Request body
{
"method": "bank_transfer",
"note": "Paid at the desk, ref 1234"
}
Forgives an open no-show or late-cancel fee: no money is collected and no revenue is recorded. Requires billing.refunds.full and an Idempotency-Key. The venue-scoped load and the 422 FEE_NOT_UNPAID check run before anything is reserved. reason is optional and is recorded in the audit trail only. While a card charge of this fee is running or awaits payment review, the waiver is refused with 409 FEE_PAYMENT_REVIEW_REQUIRED; a fee charged or settled meanwhile answers 422 FEE_NOT_UNPAID; a write that could not be confirmed answers 503 OPERATION_FAILED. In all three nothing is written and the same key may be retried. The client is never contacted.
Parameters, scopes and examples
Required scopes
billing.refunds.full
Path parameters
feeIdstring · required
Cancellation fee ID
Optional internal reason recorded in the audit trail
The saved cards a retry of this exact failed payment may use, default first. The wallet is read live from the failed invoice or PaymentIntent’s own Stripe customer on the account that payment was made on, so the picker can never offer a card the retry would then fail to charge. Requires passes.manage. 400 INVALID_PAYMENT_ID for a malformed id, 404 NOT_FOUND for another venue’s payment, and 422 RETRY_OPTIONS_UNAVAILABLE when the payment is not failed, has no client wallet, or has no processor customer.
Mints a one-time, seven-day link the customer can use to pay back a refund that already completed and should not have. Authority is the same one the web action uses: a venue owner or finance staff member who also holds billing.refunds.full (403 FORBIDDEN otherwise). Idempotency-Key required. The refund is loaded scoped to this venue (404 otherwise) and must be succeeded; 422 REPAYMENT_LINK_UNAVAILABLE carries reason not_succeeded | already_repaid | activated_link_exists | attribution_review_required | payer_unresolved | no_email. Nothing is sent: the platform never contacts the customer here — the operator shares the returned link themselves, exactly as on the web.
The stat tiles and revenue breakdown for a client’s Billing tab: what they owe, how much of that is overdue, their account credit (which may be negative), their gift-card balance, their lifetime spend and their spend so far this venue-local month, plus their spend split by accounting category. Requires payments.view. Every amount is an integer in minor units. Lifetime and month-to-date are exact server-side sums, not a sample of recent rows; totals_truncated is true only for the rare client whose succeeded-payment history exceeds the 50,000-row walk, and the two sums are then the newest 50,000 payments rather than the exact figure. Saved cards are deliberately not included here — read them from the payment-methods route, which resolves the account the cards actually live on.
A client’s recurring product subscriptions (lockers, rentals), newest first, with the price in minor units, the billing interval, the next billing date and any scheduled cancellation date. actions.cancel is true only while the subscription is active, pending or past due — the same rule the web section applies. Requires members.view_insights. The app shows the section only when the list is non-empty.
Stops a recurring product subscription. mode period_end lets the client keep what they already paid for; mode now ends it immediately. Requires products.manage and an Idempotency-Key. The subscription is loaded bound to BOTH this venue and this client (404 otherwise) and must still be cancellable — anything else answers 422 SUBSCRIPTION_NOT_CANCELLABLE with its status before anything is reserved. A processor refusal answers 502 STRIPE_UPDATE_FAILED and the local row is unchanged. Cancelling cannot be undone. The client is never contacted.
Permission passes.manage. Requires an Idempotency-Key and a recurring past_due or suspended pass with an outstanding failed subscription payment. A future venue-local grace day up to 90 days ahead restores a suspended pass to past_due while keeping its debt and original failure anchor. Silent; no client notification is sent.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Venue-local inclusive grace day and staff reason
Request body
{
"grace_until": "2026-10-15",
"reason": "Approved by the venue manager"
}
Client-account parity P1 (A9): push a pass's validity end date out, running the same core the web pass card, the client-list bulk extend and the AI assistant use (audit pass_extended carries the previous end date and the client, so the change stays manually reversible). A staff extension keeps original_end_date from the first extension and never adds to extension_count, which counts only the client's own self-extensions. Permission: passes.manage. Idempotency-Key required; a replay returns the stored response. Client delivery is silent by default and requires notify.audience.clients=true plus an explicit Email/SMS/Push selection AND notifications.send on the same request; an unavailable selected channel returns PASS_NOTIFICATION_CHANNEL_UNAVAILABLE before the pass changes. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'extend' } when the pass cannot be extended (a pass needs an end date); 422 INVALID_RANGE for an end before the start and HARD_END_EXCEEDED for one past the pass type's hard end; 422 NO_END_DATE / AT_HARD_END when the pass has no end date or already runs to (or past) its hard end; 409 PASS_CHANGED when the pass changed while it was being extended (nothing written — retry).
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
New end date and the explicit, default-silent client notification choice
Client-account parity P1 (A8): add or remove clips on a clip card. A zero delta is refused with 422 INVALID_DELTA, and the resulting balance floors at 0 (the web rule — staff can zero a card, never owe it). Permission: passes.manage. Idempotency-Key required; a replay returns the stored response. Client delivery is silent by default and requires an explicit channel selection plus notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'adjust_clips' } when the pass has no finite clip balance.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Signed clip delta, optional reason, and the client notification choice
Client-account parity P1 (A10): either move the start and/or end of an activated pass's validity window — the "transfer the activation date" case for manually sold or mis-dated passes — OR, for a pass that has not activated yet (on_first_use/client_chooses_start, no start_date), set how long it stays valid once it does (PASS-VALIDITY-LENGTH-01). The two modes are mutually exclusive on one request: provide at least one of new_start_date, new_end_date or new_duration_days for the DATE mode (an exact new_end_date always wins over a duration), or provide BOTH new_validity_value and new_validity_unit ('days'|'weeks'|'months', bounds 1-1,095 days/1-156 weeks/1-36 months) for the LENGTH mode — never mix the two families on the same call. 422 HARD_END_EXCEEDED when a date-mode end is past the pass type's absolute end date, 422 INVALID_RANGE for every other rejected date-mode window, 422 INVALID_LENGTH for a rejected length (an already-activated pass, a non-on_first_use/client_chooses_start type, an out-of-bounds value, a Flexible-pricing-option pass whose length was frozen at purchase, or a race where the pass activated between read and write) — each with the plain-language message the app shows verbatim. The response reports already_expired when a date-mode window ends before venue-local today (allowed — backdating a correction is legitimate); length mode never sets dates directly, so already_expired is always false for it. This never touches bookings. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'adjust_dates' }.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Date mode: new window (dates and/or duration). Length mode (unactivated passes only): new_validity_value + new_validity_unit. Optional reason, notification choice.
Request body
{
"new_start_date": "2026-02-01",
"new_duration_days": 90,
"reason": "Sold in January, first class in February",
"notify": {
"audience": {
"clients": true
},
"channels": {
"email": true,
"sms": false,
"push": false
}
}
}
Client-account parity P1 (A11): flip auto_renew, syncing Stripe cancel_at_period_end on the subscription's OWN account (a connected-account subscription is always scoped to passes.stripe_account_id), then write the admin-attributed audit row pass.auto_renew_toggled_by_admin. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default and requires an explicit channel selection plus notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'toggle_auto_renew' }.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Desired auto-renew state and the client notification choice
Client-account parity P1 (A21): the venue's non-recurring pass types in name order, minus this pass's current type and minus retired native-managed course/workshop passes — the exact list the web pass editor offers. Read-only: no Idempotency-Key, 30 requests / 60 s per operator. Permission: passes.manage. 404 for a pass that is not this venue's. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'convert_type' } for a recurring or already-ended pass.
Client-account parity P1 (A21): convert a NON-recurring pass (clip card / time pass) to another of the venue's non-recurring types. Credit-balance aware — the unused share of a price drop is issued as account credit and reported as credit_issued_major, so an upgrade never surprise-charges mid-pass. 422 TARGET_RECURRING when the source or the target is a recurring membership (convert those from the subscription page), 422 TARGET_NOT_FOUND for an unknown target or a retired course/workshop pass. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'convert_type' }.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Target pass type and the client notification choice
Client-account parity P1 (A22): move a pass to another client of the SAME venue, with both-ends safety — the pass must belong to this venue and the recipient must hold an ACTIVE membership here (422 RECIPIENT_NOT_CLIENT); a recipient who already owns the pass is refused with 422 RECIPIENT_IS_OWNER, and a recurring membership with 422 RECURRING_UNSUPPORTED (manage those from the subscription actions). A reason is required and recorded on the audit row. Both ends are notified through the selected channels — the new owner AND the previous one — so BOTH clear the per-channel preflight before the write. Find recipient_id with GET /admin/members?search=. Permission: passes.manage. Idempotency-Key required. Client delivery is silent by default and requires notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'transfer' } for anything but an active non-recurring pass.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Recipient profile id, required reason, and the client notification choice
Client-account parity P1 (A20): hide an ENDED pass from the client profile without rewriting its lifecycle — history is preserved, the profile is decluttered. Only a terminated, expired or cancelled pass may be archived (422 ARCHIVE_NOT_TERMINAL: end or terminate it first); a pass that is already archived is an idempotent success no-op. Permission: passes.manage. Idempotency-Key required. This route NEVER notifies the client (the web never does): a notify body is accepted and ignored, and notification_summary always reports no channels. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'archive' }.
Client-account parity P1 (A20): bring an archived pass back into the client profile. A pass that is not archived is an idempotent success no-op. Permission: passes.manage. Idempotency-Key required. This route NEVER notifies the client (the web never does): a notify body is accepted and ignored. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'unarchive' }.
Client-account parity P1 (A23): cancel a non-recurring pass immediately, with no refund — the pass editor's "Cancel now", server-side. Cancellation is TERMINAL by design: there is deliberately no undo, and the audit row carries everything needed to reconstruct state. Refunds are NOT part of this call — review the exact payment from the client's Billing tab. early_termination_fee reports the fee this cancellation determined applies (null once the binding period is over); this route never charges it. Permission: passes.manage. Idempotency-Key required. The branded cancellation notice is silent by default and requires an explicit channel selection plus notifications.send. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'cancel_now' }; 422 RECURRING_UNSUPPORTED if a recurring membership reaches the core (terminate those from the subscription actions).
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Optional reason and the explicit, default-silent client notification choice
Client-account parity P1 (A4): the authoritative Stripe-cycle preview behind the freeze panel. POST carries the two dates but the call is READ-ONLY — it changes nothing, so it takes NO Idempotency-Key and is rate-limited as a read (30 requests / 60 s per operator). preview is null when the pass has no Stripe subscription and there is nothing financial to review. The freeze itself recalculates inside the idempotent financial engine, so this review is advisory: invalidate it whenever either date changes, and echo preview.previewToken as expected_preview_token on POST /admin/members/{id}/membership/pause (422 PREVIEW_STALE when it no longer matches). Permission: passes.manage. 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'freeze' } when the pass cannot be frozen.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Proposed freeze window (pause_end must be after pause_start)
Client-account parity P1 (A5): whether the pass's Stripe subscription is ACTUALLY paused, and whether that can be proven against the processor. Drives the "acknowledge unproven pause" confirmation on POST /admin/members/{id}/membership/resume, which accepts resume_from and acknowledge_unproven_pause. Read-only: no Idempotency-Key, 30 requests / 60 s per operator. A pass with no Stripe subscription answers hasStripePause=false, proven=true. Permission: passes.manage. 404 for a pass that is not this venue's; 422 PASS_ACTION_NOT_ELIGIBLE { capability: 'resume' } when the pass is not in a resumable state.
Client-account parity P1 (A13): returns the pass's current renewal collection method. Capability gate: billing_mode (422 PASS_ACTION_NOT_ELIGIBLE otherwise). 404 NOT_FOUND for an unknown or other-venue pass.
Switch a membership between auto-charge and venue-collected
Client-account parity P1 (A13): choose how FUTURE renewals are collected. 'external' flips the Stripe subscription to collection_method='send_invoice' (Stripe stops auto-charging but keeps raising cycle invoices, and dunning skips the pass); 'auto_charge' restores automatic card collection. The Stripe update is scoped to the pass's own connected account. Capability: billing_mode. Idempotency-Key required. Client delivery is silent by default and accepts only notify.audience.clients=true plus an explicit Email/SMS/Push selection, preflighted on the membership_admin_changed event before the pass changes. Errors: 422 BILLING_MODE_UNCHANGED when the pass already uses that method, 422 NO_CLIENT, 422 STRIPE_NOT_CONFIGURED, 500 STRIPE_UPDATE_FAILED / UPDATE_FAILED. Audit: pass.billing_mode_changed.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Target billing mode and an explicit, default-silent client notification choice
Client-account parity P1 (A14, EXTERNAL-MINT-01): record that this cycle of an externally billed membership was collected at the venue. Settles the subscription's OLDEST OPEN Stripe invoice through the canonical rails — recordManualSettlement for the attribution intent, then paid_out_of_band — so the invoice.paid recovery writes the payments row, rolls the period, resets clips and sends the receipt. Scoped to the pass's own Stripe account. Capability: billing_mode, and the pass must be billing_mode='external' (422 NOT_EXTERNALLY_BILLED). Idempotency-Key required. Never notifies. Errors: 422 NO_OPEN_INVOICE, 422 NO_SUBSCRIPTION, 422 STRIPE_NOT_CONFIGURED, 502 SETTLEMENT_FAILED / OPERATION_FAILED. Audit: payment.settled_externally (irreversible).
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
How the venue collected the cycle
Request body
{
"method": "bank_transfer",
"note": "Paid at the desk, ref 4471"
}
Client-account parity P1 (A15): what is outstanding on a failed recurring renewal — amount, when it failed, how overdue it is, whether booking is suspended, any pending late fee, original payment identity/status, bank failure reason, individual grace date, subscription-pinned card and exact payment-wallet choices. Amounts remain major units; unknown provider/wallet reads are explicit. Deliberately NOT capability-gated: a pass with nothing outstanding returns recovery=null so the card can hide itself, and 404 NOT_FOUND (unknown or other-venue pass) is the only refusal.
Client-account parity P1 (A15): settle the outstanding renewal off-session on the client's saved card, as the venue. Success uses the canonical invoice.paid finaliser and original payment receipt; deliberate termination prevents reactivation and older invoices cannot roll the current cycle backwards. Capability: renewal_recovery. Idempotency-Key required. Optional payment_id pins the original local debt; optional payment_method_id selects an owned saved card. Legacy empty body remains accepted. Returns additive receipt {operation_id,payment_id,status,settled}; 409 PAYMENT_PENDING retains an uncertain original operation. Retry the same charge endpoint with its original key/body to reconcile; durable SQL ownership survives the outer HTTP reservation. Keep the original key/body and reconcile that payment; never substitute a later debt. Never notifies directly. Errors: 402 RENEWAL_REQUIRES_ACTION when the card needs the CLIENT to confirm (the app then offers the payment link — the client secret is never on this wire), 422 RENEWAL_CHARGE_FAILED with the processor's plain-language reason and additive details.reason (the machine RenewalPayFailureCode); a receipt-less 422 whose reason is PAYMENT_NOT_COLLECTABLE, CARD_PAYMENTS_UNAVAILABLE, CARD_NOT_AVAILABLE or NO_PAYMENT_METHOD was refused before any provider call (sent only while the request owns no durable operation, else 409 PAYMENT_PENDING) and releases the original attempt, while any other or missing reason stays unresolved. Replaying the original key/body after the exact payment settled returns the ordinary paid shape (receipt omitted when no operation was admitted) instead of NOTHING_OUTSTANDING.
Client-account parity P1 (A15): email and/or SMS the no-login pay link for the outstanding renewal (deduped once per pass, failure and day). The notification IS the mechanism, so notify is REQUIRED: notify.audience.clients=true plus at least one of channels.email / channels.sms, else 400 CHANNEL_REQUIRED. Push is ignored. notifications.send is re-checked on this request and each channel is preflighted on the renewal_payment_link event before anything is sent (422 PASS_NOTIFICATION_CHANNEL_UNAVAILABLE / 502 PASS_NOTIFICATION_PREFLIGHT_FAILED). Capability: renewal_recovery. Idempotency-Key required. 422 RENEWAL_PAYMENT_LINK_FAILED when the client has no reachable address or the send fails. Audit: membership.renewal_payment_link_sent.
Client-account parity P1 (A15): delete the late-fee invoice item dunning attached to this failure, while it is still pending. Once the fee lands on a finalized invoice there is nothing to delete and the venue resolves it through the normal refund path — that case answers 422 NO_PENDING_LATE_FEE, checked before the write. Capability: renewal_recovery. Idempotency-Key required; empty body. Never notifies. 422 WAIVE_LATE_FEE_FAILED when Stripe refuses. Audit: membership.late_fee_waived.
Client-account parity P1 (A19): delete a membership SETUP row that never became a membership — only when Stripe proves the subscription is dead and nothing links to it. Real history is never deleted. expected_updated_at is the optimistic-concurrency token (409 STALE when the row moved). The Idempotency-Key IS the cleanup operation key and MUST be a UUID (400 IDEMPOTENCY_KEY_INVALID); the atomic RPC records it, so a retry whose response was lost replays the committed removal instead of a false 404. Capability: cleanup_failed_setup. Never notifies. Errors: 404 NOT_FOUND, 422 HAS_HISTORY / HAS_LIFECYCLE_HISTORY / PROCESSOR_UNKNOWN / NOT_RECURRING / NOT_SAFE / NOT_REMOVABLE with the web's messages. Audit: membership.setup_artifact_removed, or membership.setup_artifact_removal_denied on a refusal.
Client-account parity P1 (A16): the whole Share-on-the-pass state for one pass — its shares (with the recipient's display name, monthly cap and classes used this month), its pending invites, and the sharer-slot budget from pass_types.max_sharers. Capability: share (a pass type that allows no sharers is 422 PASS_ACTION_NOT_ELIGIBLE). 404 NOT_FOUND for an unknown or other-venue pass.
Client-account parity P1 (A16): add one sharer on the owner's behalf. Provide exactly one of recipient_user_id or email; both or neither is 400 VALIDATION_ERROR. An email resolves to a unique active venue member or a pending invitation. One SQL transaction commits and retains that original decision, even if the email or membership later changes. Original Idempotency-Key and body are required. Existing unexpired pre236 HTTP responses are preserved first; new requests never acquire a second HTTP mutation claim. SQL validates staff authority, venue, active pass, sharing rights and slots. Results include a strict shared or invited receipt before optional current pass projection. No token or notification send is returned. 403 PASS_SHARE_FORBIDDEN, 409 PASS_SHARE_REQUEST_CONFLICT and 503 PASS_SHARE_UNRESOLVED retain the original request; never replace its key after an uncertain response.
Parameters, scopes and examples
Required scopes
passes.manage
Path parameters
passIdstring · required
Pass UUID
Exactly one of recipient_user_id or email. Optional monthly cap applies to recipient UUID grants; the legacy email branch ignores it.
Client-account parity P1 (A16): revoke access using the original pass, share and Idempotency-Key. An empty body resolves the retained share binding before mutable lookup; an explicit recipient_user_id and optional expected_usage_revision preserve the reviewed request. SQL validates staff authority and venue. Existing unexpired pre236 HTTP outcomes are preserved first. Strict revoked or closed proof precedes optional pass projection; a closed result does not claim revocation. 403 forbidden, 409 original-request conflict and 503 unknown outcomes retain the same request. Never notifies.
Client-account parity P1 (A16): cancel a share invite before its token is redeemed, freeing the sharer slot it was holding. Only a still-pending invite can be cancelled — one already accepted or revoked answers 422 INVITE_NOT_PENDING. Pass-scoped: an invite on another pass or in another venue is the same 404 NOT_FOUND. Capability: share. Idempotency-Key required (no body; the invite id rides the request fingerprint). Never notifies. Audit: pass_share_invite_cancelled.
Requires passes.manage AND bookings.manage. Service SQL validates the pass/share tenant and canonical booking location permissions. Returns a bounded page with explicit evidence, generation, revision, aggregate counter, attributed amount and unresolved remainder. after_booking_id UUID cursor; limit 1–200, default 100. Preview is read-only and never changes a counter or sends notifications. Reject an inconsistent snapshot; do not infer unlisted history. HTTP 503 means evidence is unavailable.
Requires passes.manage AND bookings.manage and canonical SQL booking location authority. Idempotency-Key (trimmed, control-free, 1–200 characters) and exact original version 1 body are required. One key cannot change actor, organization, pass/share or body. Allocations contain 1–200 unique booking UUIDs, exact reviewed timestamps/pass IDs and contribution 0 or 1; reason is trimmed, 1–1000 characters. Original replay precedes mutable review facts. Applied allocates retained remainder without increasing the aggregate; closed seals that original request without allocating. Both terminal receipts echo the original key/request and immutable pass/share. A null read only means no receipt was found, never permission to discard an uncertain request. 409 conflict, 403 refusal and 503 busy/stale/unknown do not authorize replacement; retain original body/key and read or explicitly close it. Nothing is sent.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
passIdstring · required
Original pass UUID
shareIdstring · required
Original share UUID belonging to this pass
Exact reviewed version 1 request, retained before the first submission
Requires passes.manage AND bookings.manage and canonical SQL booking location authority. Idempotency-Key (trimmed, control-free, 1–200 characters) and exact original version 1 body are required. One key cannot change actor, organization, pass/share or body. Allocations contain 1–200 unique booking UUIDs, exact reviewed timestamps/pass IDs and contribution 0 or 1; reason is trimmed, 1–1000 characters. Original replay precedes mutable review facts. Applied allocates retained remainder without increasing the aggregate; closed seals that original request without allocating. Both terminal receipts echo the original key/request and immutable pass/share. A null read only means no receipt was found, never permission to discard an uncertain request. 409 conflict, 403 refusal and 503 busy/stale/unknown do not authorize replacement; retain original body/key and read or explicitly close it. Nothing is sent.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
passIdstring · required
Original pass UUID
shareIdstring · required
Original share UUID belonging to this pass
Exact reviewed version 1 request, retained before the first submission
Requires passes.manage AND bookings.manage and canonical SQL booking location authority. Idempotency-Key (trimmed, control-free, 1–200 characters) and exact original version 1 body are required. One key cannot change actor, organization, pass/share or body. Allocations contain 1–200 unique booking UUIDs, exact reviewed timestamps/pass IDs and contribution 0 or 1; reason is trimmed, 1–1000 characters. Original replay precedes mutable review facts. Applied allocates retained remainder without increasing the aggregate; closed seals that original request without allocating. Both terminal receipts echo the original key/request and immutable pass/share. A null read only means no receipt was found, never permission to discard an uncertain request. 409 conflict, 403 refusal and 503 busy/stale/unknown do not authorize replacement; retain original body/key and read or explicitly close it. Nothing is sent.
Parameters, scopes and examples
Required scopes
passes.managebookings.manage
Path parameters
passIdstring · required
Original pass UUID
shareIdstring · required
Original share UUID belonging to this pass
Exact reviewed version 1 request, retained before the first submission
Requires passes.manage. Retain actor, owning organization, pass UUID, original key and exact versioned request before submitting. The submitted actor is an assertion checked against authenticated authority. The request authority must be staff. Grant uses shareId:null; revoke retains the reviewed share UUID and recipient UUID. Idempotency-Key must equal operationKey. SQL enforces tenant, location, rights and usage atomically. Read returns immutable proof or null; null and errors do not authorize a replacement request. Close seals an unapplied request and returns kind:closed; an existing committed receipt wins. No notifications are sent. HTTP403 is denied scope,409 is conflicting original input,503 is unresolved. Preserve the original request for read/retry/close.
Requires passes.manage. Retain actor, owning organization, pass UUID, original key and exact versioned request before submitting. The submitted actor is an assertion checked against authenticated authority. The request authority must be staff. Grant uses shareId:null; revoke retains the reviewed share UUID and recipient UUID. Idempotency-Key must equal operationKey. SQL enforces tenant, location, rights and usage atomically. Read returns immutable proof or null; null and errors do not authorize a replacement request. Close seals an unapplied request and returns kind:closed; an existing committed receipt wins. No notifications are sent. HTTP403 is denied scope,409 is conflicting original input,503 is unresolved. Preserve the original request for read/retry/close.
Requires passes.manage. Retain actor, owning organization, pass UUID, original key and exact versioned request before submitting. The submitted actor is an assertion checked against authenticated authority. The request authority must be staff. Grant uses shareId:null; revoke retains the reviewed share UUID and recipient UUID. Idempotency-Key must equal operationKey. SQL enforces tenant, location, rights and usage atomically. Read returns immutable proof or null; null and errors do not authorize a replacement request. Close seals an unapplied request and returns kind:closed; an existing committed receipt wins. No notifications are sent. HTTP403 is denied scope,409 is conflicting original input,503 is unresolved. Preserve the original request for read/retry/close.
Accepts a Bearer JWT with `loyalty_price.grant` OR `passes.manage`. Unlike every other capability-gated pass read, a pass whose type offers no loyalty price and whose client holds no by-hand grant answers `{ context: null }` — never a 422.
Accepts a Bearer JWT with `loyalty_price.grant` (no API-key credential — this is a desk/termination-flow action). Requires an Idempotency-Key (≤128 chars). Issues the comeback offer with `origin: save_accepted` AND applies it immediately (`accepted_via: staff_save`), mirroring the web `acceptSaveOfferAtTermination`; notify is fixed silent. The app then continues its own termination flow.
Parameters, scopes and examples
Required scopes
loyalty_price.grant
Path parameters
idstring · required
Client id
The pass being saved and the win-back template to apply.
POST/api/v1/admin/loyalty-price/revokeBearer or API key
Take away a client’s by-hand loyalty price
Accepts exactly one credential: Bearer JWT with `loyalty_price.grant`, or an API key bound to the venue with `write:passes`. Requires a UUID `Idempotency-Key`, mirroring `loyalty-price/grant`. Writes through the context-free `revokeLoyaltyPriceCore` (the ONE writer).
Parameters, scopes and examples
Required scopes
write:passes
user_id + a short reason. Optional pass_id also clears a loyalty override on that pass.
Request body
{
"user_id": "uuid",
"reason": "Client asked to go back to the standard rate"
}
Accepts exactly one credential: Bearer JWT with `passes.manage`, or an API key bound to the venue with `write:passes`. Requires an Idempotency-Key (≤128 chars). Default-silent client notification — a non-empty channel selection requires `notifications.send` re-checked on the same request (JWT only; an API-key caller can never notify) plus a per-channel preflight before the mutation — refused as 422 `PASS_NOTIFICATION_CHANNEL_UNAVAILABLE` / 502 `PASS_NOTIFICATION_PREFLIGHT_FAILED`, the same codes every P1 pass route answers.
Parameters, scopes and examples
Required scopes
write:passes
pass_id + the agreed amount + a locked_until date (venue-local, must be after today) + a reason.
DELETE/api/v1/admin/rates/lock/{passId}Bearer or API key
Remove a pass rate lock
Accepts exactly one credential: Bearer JWT with `passes.manage`, or an API key bound to the venue with `write:passes`. Requires an Idempotency-Key (≤128 chars). Default-silent client notification — a non-empty channel selection requires `notifications.send` re-checked on the same request (JWT only; an API-key caller can never notify) plus a per-channel preflight before the mutation — refused as 422 `PASS_NOTIFICATION_CHANNEL_UNAVAILABLE` / 502 `PASS_NOTIFICATION_PREFLIGHT_FAILED`, the same codes every P1 pass route answers.
GET/api/v1/admin/rates/context/{passId}Bearer or API key
Get a pass’s full rate context
Accepts a Bearer JWT with `rates.view`, falling back to `passes.manage` on a 403 (mirrors the web `getPassRateContext`), or an API key bound to the venue with `read:passes`.
Client-account parity P4 (A27): the membership’s real billing history from the processor — the current cycle, whether it is set to cancel, the last twelve invoices with their paid/failed state and hosted links, the next payment, and the card on file. Requires passes.manage (not billing.manage — this mirrors the web editor’s own gate). Capability gate: subscription_timeline, so a non-recurring pass, an unfinished setup and a pass with no live subscription all answer 422 PASS_ACTION_NOT_ELIGIBLE. Every processor call is scoped to the pass’s own connected account. 502 STRIPE_ERROR when the processor is unreachable or unconfigured; 502 INCOMPLETE_PERIOD when it returns a billing period the app cannot render — retry. Nothing is written and the client is never contacted.
Client-account parity P4 (A29): shifts the membership’s next charge to a chosen date, with no proration — the canonical processor mechanism, scoped to the pass’s own connected account. Requires billing.manage, an Idempotency-Key and capability subscription_timeline. new_date is YYYY-MM-DD and must be in the future and within one year; anything else answers 422 INVALID_BILLING_DATE with the exact reason. 422 NO_STRIPE_SUBSCRIPTION when the membership has no recurring billing to shift. 502 STRIPE_ERROR frees the key for a corrected retry — the processor refused before anything moved. 500 DB_ERROR KEEPS the key, because the anchor already moved and a retry must not shift it twice.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
The new date, plus an optional explicit client-notify selection
Client-account parity P4 (A30): changes what the membership costs from its next payment onward. A new processor price is created on the SAME product and interval and swapped onto the subscription item with no proration; per-period overrides still win for the periods they cover. Requires billing.manage, an Idempotency-Key and capability subscription_timeline. amount_minor is an integer in the venue’s minor units and must be greater than zero. 422 NO_BILLABLE_ITEM when the subscription has no item to reprice. A 502 STRIPE_ERROR frees the key when the new price was never created, and KEEPS it when the price exists but the item swap failed — a retry must not create a second price. 500 DB_ERROR keeps the key (the processor already changed).
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
The new recurring amount in minor units, plus an optional notify selection
Read a membership’s payment overrides and upcoming periods
Client-account parity P4 (A28): the SERVER-computed preview the web override panel renders, so the app never re-derives billing math. `preview` is the next `periods` billing windows (default 6, 1…36, anything else is 400 VALIDATION_ERROR) with each window marked as the base price or as a covering override, matched exactly as the invoice interceptor matches them. `overrides` is every saved row, ascending by start date; rows whose id appears in no preview period are the panel’s "Other overrides". A row with applied_payment_id set has already charged a real invoice and is locked (no edit, no delete). billing_active is false for a parked or migrated membership with no live subscription — overrides still save, they just stay staged until billing resumes, and billing_inactive_reason is the exact banner text. Requires billing.manage and capability subscription_overrides.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
Query parameters
periodsinteger
How many upcoming billing periods to preview (1…36)Default: 6
Client-account parity P4 (A28): sets an agreed price for upcoming membership payments. mode "periods" expands the membership’s real billing interval forward from its next payment date and writes one row per period; mode "range" writes one row covering an explicit window. amount_minor 0 is a comped period — nothing is charged. Requires billing.manage, an Idempotency-Key and capability subscription_overrides. 422 NO_UPCOMING_BILLING when "periods" is used on a membership with no next payment date; 400 INVALID_RANGE when the range ends before it starts; 500 DB_ERROR frees the key because nothing was written. `created` is how many rows were saved.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
Either the next N periods or one explicit window, plus an optional notify selection
Client-account parity P4 (A28): changes one not-yet-charged override’s amount and window in place. Requires billing.manage, an Idempotency-Key (bound to this pass AND this override, so a key cannot be replayed against another row) and capability subscription_overrides. The override is resolved only with both the venue and THIS pass, so an unknown id, another venue’s row and another pass’s row all answer the same 404 NOT_FOUND. 422 OVERRIDE_APPLIED when the row already charged a payment — it can no longer be edited. 400 INVALID_RANGE when the window ends before it starts. The answer is the row as stored, not the request echoed back.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
overrideIdstring · required
Payment override UUID
The corrected amount and window, plus an optional notify selection
Client-account parity P4 (A28): removes a not-yet-charged override so that period returns to the normal price. Requires billing.manage, an Idempotency-Key (bound to this pass and this override) and capability subscription_overrides. Same 404 NOT_FOUND rule as the PATCH. 422 OVERRIDE_APPLIED when the row already charged a payment — it cannot be removed retroactively. The body may be empty; send one only to choose notification channels.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
overrideIdstring · required
Payment override UUID
No fields required; send a notify selection only if the client should hear
Client-account parity P4 (A28): the panel’s multi-select writes. action "set" gives every selected period the same agreed amount — periods that already have an override are updated, base periods get a new row, and if the update fails the rows just inserted are removed again so the batch leaves nothing behind (which is why 500 DB_ERROR frees the key). action "restore" deletes the selected overrides so those periods return to the normal price, and is offered only when every selected period IS an override. Requires billing.manage, an Idempotency-Key and capability subscription_overrides. At most six periods or ids per call. 400 INVALID_RANGE for duplicate or inverted periods; 409 OVERRIDE_CONFLICT when a selected row no longer exists or a new period already has one — refresh and try again; 422 OVERRIDE_APPLIED when a selected row already charged a payment.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
Either the selected periods and one amount, or the override ids to restore
List the membership types this membership can convert to
Client-account parity P4 (A31): the venue’s other live recurring membership types, name-ordered, with each one’s price in the venue’s minor units and its billing cadence. The membership’s own type is excluded, and so are archived types — the change engine refuses those anyway, so offering one would be a dead end. Requires billing.manage and capability change_plan (a non-recurring, terminal or unfinished membership answers 422 PASS_ACTION_NOT_ELIGIBLE).
Client-account parity P4 (A31): quotes the change through the canonical membership-change engine and returns its object verbatim — the same shape GET /api/v1/admin/memberships/change already serves, including the signed, short-lived `quote` the confirm call must hand back. READ-ONLY: it changes nothing, needs NO Idempotency-Key, and runs on the read rate limit. Requires billing.manage and capability change_plan. This editor never offers a custom price, so the quote’s override_amount_minor is always null. Engine refusals map exactly as the memberships/change route maps them (404 / 403 / 409 / 422 / 402 / 503).
Client-account parity P4 (A31): applies the change the preview quoted. Requires billing.manage, an Idempotency-Key and capability change_plan, and the exact signed quote from the preview — its override_amount_minor MUST be null. Both this route and POST /api/v1/admin/memberships/change converge on the same durable quote fingerprint, so a change started on one cannot double-apply on the other. `replayed` is true when a repeated confirmation converged on an already-applied change; the client is not told twice. 409 QUOTE_STALE and the 422s free the key so the app can re-preview; 402 CHARGE_FAILED, 503 PARTIAL_APPLY, 503 CHANGE_IN_PROGRESS and 503 DATABASE_ERROR KEEP it, because the engine may already have moved money or claimed the quote.
Parameters, scopes and examples
Required scopes
billing.manage
Path parameters
passIdstring · required
Pass UUID
The target type, the signed quote from the preview, and an optional notify selection
Business POS read for an active venue client. Returns sanitized card references only (brand, last4, expiry, default, expired, chargeable); imported display-only cards are explicitly non-chargeable. Requires pos.access (register) OR members.contact (client record, the web admin gate). Never returns customer IDs, processor metadata, full card data, or client secrets.
Business POS card-setup operation for an active venue client. Requires an in-person consent attestation and Idempotency-Key. Returns the SetupIntent client secret, exact Stripe account namespace, legal merchant country, and frozen regional revision for native Payment Sheet.
Update the member phone number after an explicit staff confirmation. Requires members.edit and writes an audit record.
Parameters, scopes and examples
Required scopes
write:members
Path parameters
idstring · required
Member user ID
Supported member profile fields
Request body
{
"phone": "+4512345678"
}
POST/api/v1/admin/members/{id}/creditsBearer or API key
Issue account credit
Grant account credit to an active member (positive manual adjustment) on the same atomic, organization-scoped ledger path as the web action. The response balance is the canonical venue-available balance; profiles.credit_balance is maintained only as an account-wide compatibility cache. Currency must equal the venue currency (422 CURRENCY_MISMATCH). Client delivery is silent by default and requires an explicit canonical Email/SMS/Push selection plus notifications.send; membership and unavailable selected channels are rejected before the balance changes, and legacy booleans remain silent. Idempotency-Key is required (1–255 characters): an identical retry returns the original transaction and balance, while reuse for a different semantic request returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH.
Venue-wide duplicate suggestions for the clients-list merge wizard. Matches are never merged automatically. Requires members.merge plus members.contact and the acting membership’s full client-contact visibility.
Ranked duplicate candidates for the current client in this venue, excluding dismissed pairs. Requires members.merge plus members.contact and the acting membership’s full client-contact visibility.
Dismiss one candidate pair for this venue. Body: { candidate_user_id }. Idempotency-Key required; concurrent reuse is serialized before mutation. Requires members.merge plus members.contact and the acting membership’s full client-contact visibility, matching candidate review.
Fail-closed, venue-scoped merge preview with explicit transfer, retained-identity, indirect invoice descendant, and unsupported-data blocker counts. Immutable brand payment history is counted through a service-only scoped RPC and blocks merging the source profile when populated. Unknown or unreadable ownership is never reported as zero. Body: { primary_user_id, secondary_user_id }. The path id must be one of those two. Requires members.merge plus members.contact and full membership contact visibility. Merge is notification-silent.
Parameters, scopes and examples
Required scopes
members.mergemembers.contact
Path parameters
idstring · required
Primary or secondary member ID
POST/api/v1/admin/members/{id}/mergeBearer token
Merge duplicate client profiles
Merges the secondary profile into the primary survivor using the transactional admin_merge_venue_profiles guard. A populated original-subject brand payment archive blocks the merge, including when the preview is stale. Unsupported data, concurrent setup operations, provider-wallet/subscription ownership changes, and material row conflicts roll back without deactivating the source; retained identity/audit data remains explicit. confirm_token must be the literal MERGE. Notification-silent. Idempotency-Key is bound to venue, path member, and canonical body, with an atomic pre-mutation claim that serializes concurrent reuse. Requires members.merge plus members.contact and full membership contact visibility.
Parameters, scopes and examples
Required scopes
members.mergemembers.contact
Path parameters
idstring · required
Primary or secondary member ID
Survivor, merged-away profile, field choices, and MERGE confirmation
Issues one frozen-term comeback promise per client/template/settlement day, including after that promise closes. Delivery is silent by default; an explicit notify audience and channel set plus notifications.send is required to contact the client. Every selected channel is preflighted before the offer is created and the exact selection only narrows delivery—an SMS-only request can never fall back to email. The response reports actual delivery, and crash-safe deterministic provider/channel dedup permits a silent existing offer to be notified without double-send. Idempotency-Key is bound to venue, path member, and canonical body and atomically claimed before mutation. Permission: loyalty_price.grant.
Mark an open offer as declined (they said no) or revoked (withdrawn). Idempotency-Key required and atomically claimed before mutation. Permission: loyalty_price.grant.
Client-record invoices for this member. Drafts are excluded. Each row includes status, totals, and a share_url for the public invoice page. Permission: invoices.view. Org-wide invoice management remains /admin/invoices.
Venue-scoped family/partner/guest relationships for this client. Permission: members.view_insights. Related email fields are returned only when the caller also holds members.contact and full membership contact visibility; otherwise they are null.
Parameters, scopes and examples
Required scopes
members.view_insights
Path parameters
idstring · required
Active member ID
POST/api/v1/admin/members/{id}/tagsBearer token
Add a client tag
Add a manual member tag. Idempotency-Key required and atomically claimed before mutation. Permission: members.edit.
Parameters, scopes and examples
Required scopes
members.edit
Path parameters
idstring · required
Active member ID
DELETE/api/v1/admin/members/{id}/tagsBearer token
Remove a client tag
Remove a member tag. The tag travels in the query string (?tag=). Permission: members.edit.
Parameters, scopes and examples
Required scopes
members.edit
Path parameters
idstring · required
Active member ID
Query parameters
tagstring · required
Tag to remove
GET/api/v1/admin/bookingsBearer or API key
All bookings
Venue-wide booking list with filtering by date, status, class, member, and location.
Parameters, scopes and examples
Required scopes
read:bookings
Query parameters
fromstring
Start date (YYYY-MM-DD)
tostring
End date (YYYY-MM-DD)
statusstring
Filter by booking status
class_instance_idstring
Filter by class instance
user_idstring
Filter by member
location_idstring
Filter by location
POST/api/v1/admin/bookingsBearer or API key
Book for member
Create a confirmed or waitlisted booking through canonical pass, course, network and allowance eligibility. Requires a caller-stable Idempotency-Key bound to the full request, including notes and notification choices. A 503 BOOKING_COMMIT_UNCONFIRMED retains the same key and exact body; a recovered committed receipt never repeats notification dispatch or notes writes. Narrow staff timing/capacity exceptions do not waive funding. An authorized after-cutoff attempt returns 422 BOOKING_AFTER_CUTOFF_CONFIRMATION_REQUIRED before mutation; confirm with after_cutoff_confirmed and a new intent key. Same-day ended classes also require ended_class_confirmed. Client delivery is silent by default and requires an explicit Email/SMS/Push selection plus notifications.send; unavailable selected channels are rejected before booking/pass/count effects.
POST/api/v1/admin/bookings/waitlistBearer or API key
Add member to waitlist
Manually place a member on a class's waitlist at the queue tail. Always creates a waitlisted booking (never auto-confirms). Client delivery is default-silent and requires both an explicit client audience/channel selection and notifications.send; unavailable selected channels are rejected before queue effects. Current actor/location/body authority precedes org-and-intent-bound replay; ADD requires active venue membership. Atomic queue changes do not consume seats or credits. Responses include operation_id, replayed and effects_status (completed, pending or held); a committed change remains successful if follow-up work fails.
DELETE/api/v1/admin/bookings/waitlist/{bookingId}Bearer or API key
Remove member from waitlist
Remove a waitlisted booking. Waitlisted rows only (409 on a confirmed booking); never triggers auto-promotion. Idempotent. Client delivery is default-silent and requires both an explicit client audience/channel selection and notifications.send; unavailable selected channels are rejected before queue effects. Current actor/location/body authority precedes org-and-intent-bound replay; ADD requires active venue membership. Atomic queue changes do not consume seats or credits. Responses include operation_id, replayed and effects_status (completed, pending or held); a committed change remains successful if follow-up work fails.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
bookingIdstring · required
Waitlisted booking ID
Optional reason and explicit client notification channels. Omit notify to stay silent.
POST/api/v1/admin/bookings/{bookingId}/cancelBearer or API key
Cancel a confirmed booking
Cancel a member's confirmed booking on their behalf (admin cancel semantics — may charge late fees + restore clips per policy; NOT the fee-free lapsed-booking path). Decrements booked_count, writes audit + booking.cancelled webhook, and issues a 30s undo ticket. Client delivery is silent by default: notify.clients.channels (or the older notify.{audience,channels}) requires notifications.send and sends the booking_cancelled_client notice with real Email, SMS and Push content on exactly the chosen channels; an unavailable chosen channel returns 422 BOOKING_CANCELLATION_NOTIFICATION_CHANNEL_UNAVAILABLE {audience, channel, reason_code, unavailable_reason} before any mutation; legacy notify_client remains silent. `notified` is true only when a chosen channel was sent or queued; `notify_outcome` reports per chosen channel {sent, skipped, failed, pending, reasons} (null when nothing was chosen). Idempotent via Idempotency-Key. Returns 409 ALREADY_CANCELLED on a cancelled booking, 409 BOOKING_CHANGED when the booking changed during the cancel (retry after refresh), and 409 ON_WAITLIST for a waitlisted row (use the waitlist remove endpoint). The success body carries `warning` (string or null): set when the booking was removed but its clip could not be returned to the pass.
Human staff JWT plus history and ordinary operation permissions. Returns data.preview with current booking/class CAS, authoritative fee/payment/refund ledger availability, consumed credit, per-audience notification availability and a ten-minute signed reviewToken bound to actor, venue, booking and target. Query operation and status (omit status for invalidate). Does not correct attendance or move money.
Requires human JWT, scheduling.manage_history and notifications.send. Finalizes the actor-owned notification_batch_id after individual corrections settle. One durable summary per instructor and selected channel includes only committed records. Repeated finalization cannot resend a claimed delivery; finalized batches reject new records. Returns effects, recordCount and recipientCount.
Parameters, scopes and examples
Required scopes
scheduling.manage_historynotifications.send
Stable UUID shared by reviewed corrections in this bulk operation
Human staff JWT and additive history and ordinary operation permissions. Idempotency-Key must be a UUID. expected_class_updated_at is a compare-and-set token; expected_updated_at is a compare-and-set token for existing bookings. Existing-booking reasons are optional and the explicit confirmation button suffices; legacy REWRITE remains accepted. GET review_token binds authoritative fee/refund and credit facts before any selected financial or notification action. financial.fee keep/refund/waive and financial.clip keep/return default to keep; explicit refund/waive requires billing.refunds.full, clip return passes.manage, and notifications require notifications.send. Charged fee badges never override succeeded refund ledger evidence. Money stays on the canonical reviewed refund engine; atomic clip/waive changes retain historical and financial audit. Email, SMS, and Push are available only through explicit notify.audience clients/instructors and channel selections; the default is silent. notification_batch_id consolidates bulk instructor notices on the batch-finalization endpoint. Returns attendance result plus per-effect held/pending/completed outcomes; no claim of full financial/delivery success from attendance alone. If the reviewed database executor is unavailable, the route fails closed with HISTORICAL_EXECUTOR_UNAVAILABLE. Retrocreate retains mandatory reason/REWRITE and has no financial or notification effects.
Parameters, scopes and examples
Required scopes
scheduling.manage_history
Path parameters
bookingIdstring · required
Class booking ID
One closed class-booking correction. Every operation requires expected_class_updated_at; existing-booking operations also require expected_updated_at. Existing-booking history_reason and legacy history_confirmation_token are optional; reviewed effects require review_token. Retrocreate still requires REWRITE.
Bearer-JWT, passes.manage-scoped server-authoritative preview for a recurring membership. Resolves the selected client and saved card in the active venue, validates the venue-local start-date policy, and returns canonical buyer-specific gross pricing, registration fee/waiver, due-today amount, access date, first charge, next renewal, card label, contract/terms summary, and — when an operator discount schedule is requested — the resolved discount_schedule block with the agreed amount, the number of discounted periods and the first full-price charge date. A Flexible recurring membership additionally requires exactly one selection (selection_kind=quantity plus quantity, selection_kind=unlimited, or selection_kind=option plus option_id) and returns flexible_selection plus a short-lived flexible_quote bound to that selection, member, venue, active immutable pricing version, regional tax facts, and full minor-unit breakdown. Create must echo that flexible_quote. Sale/preview Flexible option identity on POST /admin/pos/sale uses camelCase optionId; membership uses top-level option_id. Optional additive flash_sale_id maps to the existing core flashSaleId and is exclusive of promo_code. Optional additive promo_window_exception { kind: flash_sale_after_end | late_code_redemption, reason } prices an ended flash_sale_id (requires marketing.flash_sales) or an expired promo_code (requires marketing.campaigns) exactly as in-window; preview records nothing. Manual registration-fee concessions, free periods, and discount schedules are unavailable for Flexible memberships; only the shared recurring Flexible Flash Sale promo is accepted. Returns review {version:1,fingerprint}; create must echo it as expected_review_version and expected_review_fingerprint. saved_payment_method is null for external tender; contract_and_terms.delivery_availability is optional. Contract delivery defaults to false. This endpoint never mutates or charges.
Parameters, scopes and examples
Required scopes
passes.manage
Recurring membership options with exactly one of saved_payment_method_id (card-collected) or external_tender_method (venue-collected renewals, billing_mode=external). Optional registration_fee_discount applies a per-sale percentage discount to the one-time registration fee; legacy waive_registration_fee remains accepted. Optional discount_schedule sets an operator-agreed price for the first period, a fixed number of periods, or for as long as the membership runs; discount_reason stores the staff rationale with review, Stripe metadata, audit, and rate records.
Bearer-JWT, passes.manage-scoped recurring membership creation through the canonical subscription checkout core. Contract delivery is default-silent; explicit send_contract_and_terms=true additionally requires notifications.send before mutation. Echo expected_review_version and expected_review_fingerprint from preview.review; a mismatch returns STALE_REVIEW (409), requiring a fresh review. For an ambiguous transport or in-progress retry, retain the exact request body and Idempotency-Key. Idempotency-Key is required. Re-resolves pricing, dates, saved-card ownership, Stripe locality, VAT/age band, concessions, and legal delivery before mutation. Optional additive flash_sale_id maps to the existing core flashSaleId and is exclusive of promo_code. Optional additive promo_window_exception { kind, reason } (same permissions as preview) records an audited 30-minute database exception that waives only the sale end / code expiry; caps, per-client limits, eligibility and price are unchanged, refusals are 422, and it is part of the idempotency fingerprint. Flexible memberships require the exact unexpired flexible_quote returned by preview together with the same selection; the server rebuilds and byte-compares its versioned material and never accepts a client-authored amount. A stale, expired, or mismatched quote is refused before checkout. Returns the exact preview plus pass/subscription ids, payment status, and contract-delivery result. Off-session declines remain 402. First-invoice SCA returns 202 with client_secret/payment_intent_id for in-register confirmation, then POST /admin/memberships/finalize. Succeeded first charges also write a pos_transactions receipt row (transaction_id/payment_id). CARD_DECLINED_SETUP_REMOVED (402) proves a linked declined setup passed processor cleanup and atomic removal; a client may release only its first confirmed refusal. CARD_DECLINED, SCA_REQUIRED, unknown errors, later refusals after response loss and finalize failures do not grant fresh-operation authority.
Parameters, scopes and examples
Required scopes
passes.manage
The same options accepted by preview, plus the exact review version/fingerprint and Flexible quote returned by that review
Finalize an in-register membership card confirmation
Bearer-JWT, passes.manage-scoped completion after a 202 requires_action membership create. Verifies the PaymentIntent succeeded, completes mint/activation through the canonical invoice-paid path, and writes the pos_transactions receipt row only for that PaymentIntent’s succeeded payments row in this venue/member with a correlated membership pass_id. Missing pass_id/pass projection or invoice-handler failure is 409 PAYMENT_PROCESSING and does not cache a created membership. Duplicate finalize replays only a locally correlated payment/pass/receipt. Idempotency-Key required. Body: { payment_intent_id }. Does not bounce staff to member checkout.
Bearer-JWT, passes.manage-scoped option list for an organization-owned pass. Each option is buyer-priced by the canonical membership-change quote engine; a failed target is reported separately and cannot hide valid sibling options. Query: pass_id.
Parameters, scopes and examples
Required scopes
passes.manage
GET/api/v1/admin/memberships/changeBearer token
Preview a client membership change
Bearer-JWT, passes.manage-scoped server quote. Query: pass_id, target_pass_type_id and optional override_price_major. Returns exact charge, credit, effective date, next renewal and a short-lived signed quote binding; it never mutates or charges.
Parameters, scopes and examples
Required scopes
passes.manage
POST/api/v1/admin/memberships/changeBearer token
Apply a client membership change
Bearer-JWT, passes.manage-scoped confirmation through the canonical membership-change core. Requires Idempotency-Key and the exact signed quote returned by preview; foreign-venue passes resolve as not found and client-supplied prices are not accepted.
POST/api/v1/admin/pos/pass-mobilepay/prepareBearer or API key
Prepare original MobilePay pass sale
Requires pos.access OR pos.sell and venue-wide location access. Send the exact original POS sale body with its original Idempotency-Key. One payable pass only; configured built-in MobilePay collection rules remain authoritative. Zero-total sales use the ordinary sale endpoint. Freezes the complete sale before provider preparation; no receipt/link is sent. Retain the encrypted original body/key before calling. Response supplies the canonical transformed-request fingerprint; never hash the snake-case body for recovery. Readiness remains disabled until release acceptance.
Parameters, scopes and examples
Existing POS sale body; original Idempotency-Key header required
POST/api/v1/admin/pos/pass-mobilepay/recoverBearer or API key
Resolve original MobilePay pass sale
Requires original authorized actor/venue and current POS/location access. Reads only the exact original body fingerprint/key and optional bound PI; never reconstructs a sale or allocates a replacement source. Returns original approval or verified original completion, including after a subsequent refund; payment_status is the actual current payment state, never an invented succeeded fallback. Missing/unknown sources stay resolving. Keep approval URLs only in memory; closing the panel never cancels. Poll sequentially every three seconds for at most four minutes, then offer explicit original-status checks. No automatic SMS.
Parameters, scopes and examples
Original identity; operation_key is the original sale key, not a new request key
POST/api/v1/admin/pos/pass-mobilepay/linkBearer or API key
Explicitly send original MobilePay pass link
Requires notifications.send independently of POS transaction and venue-wide location access. Send only after an explicit staff choice. Reads the original bound source/account/PI and obtains its approval URL from the provider; never accepts a caller URL or creates/confirms a payment. Canonical recipient normalization, consent, preferences, suppression, provisioning and source-bound outbox deduplication apply. Accepted means provider acceptance, not delivery. Accepted without notification_id and unknown outcomes must lock repeat send; no automatic replacement or retry.
POST/api/v1/admin/pos/pass-mobilepay/link-statusBearer or API key
Read original pass link delivery evidence
Requires original authorized actor/venue and current POS/location access. Reads a notification scoped to this exact sale, member, organization and template. Pending/unknown is not acceptance; failed is not permission to resend. This endpoint never sends. Poll sequentially with a fixed four-minute deadline.
Process an idempotent, default-silent point-of-sale transaction with pos.access OR pos.sell and venue-wide staff location access. No receipt is queued by this endpoint; an explicit post-sale receipt choice uses the separate receipt endpoint. Supports cash, venue credit, and a server-validated saved card. Pass carts may send split_payments (max 8), Flexible/rich pass fields, and optional additive flash_sale_id (exclusive of promo_code; resolved by the existing sale core). Optional additive promo_window_exception { kind, reason } (pass sales for a client only; reason 5-500 characters): flash_sale_after_end sells an ended flash_sale_id and requires marketing.flash_sales; late_code_redemption uses an expired promo_code and requires marketing.campaigns. Only the time window is waived (published sale that ended by time / code expired in the last 90 days); caps, per-client limits, eligibility, scope and price are unchanged. It is part of the idempotency fingerprint, records an audited 30-minute database exception, and refusals are 422. One sale may carry many lines with quantities (max 50 lines, 100 units per line): products with bundles, or services with products; a pass is always its own sale (422 MIXED_CART_NOT_SUPPORTED) and bundles never combine with services. products.max_quantity_per_order applies to every cart shape, summed across lines, after replay lookup and before collection (422 PRODUCT_QUANTITY_LIMIT with details.product_id/max_quantity/requested_quantity); a retried or 3DS-completed sale keeps its frozen cart. expected_total from POST /admin/pos/catalog-preview guards product, bundle and service carts (400 PRICE_CHANGED before collection). Ordinary product card sales may select product_currency with product_price_book_versions (product UUID to accepted version), the exact signed product_quote, and required expected_total from catalog-preview. New operations verify complete residence, item and included-tax authority before collection; existing operation recovery precedes fresh quote expiry or changed-evidence checks. The member must have current home-country billing evidence; every line needs an enabled catalog-tax book. Commissions, loyalty/course/staff benefits, recurring products, product variants, bundles, stored value and other collection methods are refused with PRODUCT_PRICE_BOOK_UNAVAILABLE (422). The exact selection remains part of the retry/finalize body; replay uses frozen money even after settings change. This additive path requires its selected-product SQL authority before activation. Saved-card SCA returns a 202 challenge response and is completed with a separate idempotent finalize request. Recurring memberships stay on POST /admin/memberships. Fresh cards, MobilePay and Stripe Terminal use their dedicated flows.
Recent POS transactions filtered by location and date. Requires pos.access and venue-wide staff location access; a location filter never grants access to restricted staff. Refund headroom subtracts both succeeded and in-flight operation claims; refunded_amount reports succeeded claims and pending_refund_amount reports the reserved in-flight amount.
Parameters, scopes and examples
Required scopes
pos.access
POST/api/v1/admin/pos/transactions/{id}/receiptBearer or API key
Resend POS receipt
Send a tenant-scoped POS transaction receipt by an explicit email, SMS or push choice. Requires pos.access, notifications.send and venue-wide staff location access before delivery. Uses the client's stored contact unless an explicit recipient is supplied for email/SMS. Idempotency-Key is required and retries must keep its exact body. Atomic caller/venue/payload claims prevent concurrent re-sends during the replay window. RECEIPT_CHANNEL_UNAVAILABLE means no provider send was attempted; RECEIPT_DELIVERY_UNCONFIRMED retains the original operation for reconciliation. A sent result is provider acceptance, not device delivery.
Parameters, scopes and examples
Required scopes
pos.accessnotifications.send
Path parameters
idstring · required
POS transaction ID
Receipt delivery channel and optional recipient override
Request body
{
"method": "email"
}
GET/api/v1/admin/pos/summaryBearer or API key
Daily POS sales summary
Requires pos.access and venue-wide staff location access. Daily sales breakdown for the given date (default today): totals (gross/discounts/VAT/credits/net) plus per-payment-method and per-transaction-type buckets. Completed transactions only; same date-window semantics as /admin/pos/recent.
Parameters, scopes and examples
Required scopes
pos.access
Query parameters
datestring
YYYY-MM-DD (default today)
location_idstring
Filter by location
GET/api/v1/admin/pos/payment-methodsBearer or API key
Read configured POS tenders
Active configured tenders are available with settings.business OR venue-wide POS transaction access (pos.access OR pos.sell). include_archived=true requires settings.business. Creation, edits and archiving remain settings.business-only. Collector availability remains enforced by the canonical POS engine.
Parameters, scopes and examples
Query parameters
include_archivedboolean
Include archived tenders; requires settings.businessDefault: false
Requires booking.checkin; no-show additionally requires bookings.mark_no_show. Only canonical notify audience/client channel choices enable delivery and require notifications.send before mutation. Omitted/empty channels and legacy booleans remain silent. Each recipient is preflighted; a failed check-in never dispatches. Successful explicit check-in delivery uses attendance_corrected with the booking-owning venue. Existing check-in cutoff, history, tenant and idempotency rules remain enforced. Check-in skips streaming (online) bookings, whose attendance is recorded on stream playback, and lists them in an additive skipped array ({ booking_id, code: ONLINE_ATTENDANCE_AUTO, reason }) instead of failing them.
Parameters, scopes and examples
Required scopes
booking.checkin
Path parameters
classInstanceIdstring · required
Class instance UUID
Selected action, booking IDs and explicit client channels
GET/api/v1/admin/pos/transactions/{id}/receiptBearer or API key
Read receipt channel availability
Read canonical email/SMS/push contact, preference and venue capability reasons for a tenant-owned sale. Requires pos.access and venue-wide location access. Opening the sheet never sends a receipt; POST separately requires notifications.send.
Parameters, scopes and examples
Required scopes
pos.access
Path parameters
idstring · required
POS transaction UUID
GET/api/v1/admin/terminal/receiptBearer or API key
Read captured-sale receipt availability
Resolves the stored payment and POS transaction inside the authenticated venue, then returns the same canonical receipt channel availability. Requires pos.access and venue-wide location access. This read does not enable hardware collection or send a receipt.
Parameters, scopes and examples
Required scopes
pos.access
Query parameters
payment_intent_idstring · required
Recorded payment intent ID
GET/api/v1/admin/dashboard/revenue-seriesBearer or API key
Daily revenue series (sparkline)
Zero-filled daily revenue series ending today — succeeded payments bucketed by UTC day, matching the dashboard revenue_today semantics. days clamps to 1–90 (mobile uses 7 and 30).
Parameters, scopes and examples
Required scopes
read:reports
Query parameters
daysinteger
Window length in days (1–90)Default: 7
POST/api/v1/admin/members/{id}/membership/pauseBearer or API key
Pause membership
Pause (freeze) a member’s pass for a date window. Validated against the pass type’s pause policy; recurring memberships receive exact per-cycle billing credits on their own Stripe account; audit_log pass_paused. Client delivery is silent by default and requires notify.audience.clients=true plus explicit Email/SMS/Push channels and notifications.send. Legacy notification booleans remain silent. Selected-channel availability is rechecked before mutation; exact unavailable/preflight-failed responses leave the membership unchanged. Idempotency-Key honored. {id} accepts UUID or display ID (e.g. HYC-0042).
POST/api/v1/admin/members/{id}/membership/resumeBearer or API key
Resume membership
Resume a paused pass (Stripe-first ordering with compensating re-pause). Audit_log pass_resumed. Client delivery is silent by default and uses only an explicit canonical Email/SMS/Push selection. Selected-channel availability is rechecked before mutation; exact unavailable/preflight-failed responses leave the membership unchanged. Idempotency-Key honored.
Parameters, scopes and examples
Required scopes
write:members
Path parameters
idstring · required
Member user ID or display ID
Resume immediately or from a venue-local date. An unproven Stripe pause stays blocked unless acknowledge_unproven_pause types CLEAR PAUSE plus a reason. Optional explicit client delivery.
POST/api/v1/admin/members/{id}/membership/terminateBearer or API key
Cancel / terminate membership
Cancel or terminate a recurring membership with explicit effective dates: mode period_end (cancel at current cycle end), chosen_cycle (kth upcoming cycle, cycle required), or immediate. Runs the kill-switch-gated termination engine (fail-closed Stripe). Response carries the engine-confirmed effective_at. Client delivery is silent by default and requires an explicit canonical channel choice plus notifications.send; legacy booleans remain silent. Selected-channel availability is rechecked before mutation; exact unavailable/preflight-failed responses leave the membership unchanged. Idempotency-Key honored.
GET/api/v1/admin/members/{id}/membership/termination-previewBearer or API key
Termination preview (cycle picker)
Next 6 cycle boundaries (effective_at, venue-local last usable day, precedes-binding flag), venue policy defaults, and billing horizon for the terminate endpoint’s cycle picker.
Parameters, scopes and examples
Required scopes
read:members
Path parameters
idstring · required
Member user ID or display ID
Query parameters
pass_idstring
Pass ID (uuid)
POST/api/v1/admin/terminal/connection-tokenBearer or API key
Stripe Terminal connection token
Mint a Stripe Terminal connection token plus the venue Terminal location id (`{secret, location_id}`) for card-present readers and Tap-to-Pay. Ephemeral-token fetch — no Idempotency-Key (the Terminal SDK always needs a fresh token).
Parameters, scopes and examples
Required scopes
write:pos
POST/api/v1/admin/terminal/payment-intentBearer or API key
Create Terminal payment intent
Create a card-present PaymentIntent (manual capture) on the venue connected account. Returns `{client_secret, payment_intent_id}`. Money mutation — send an Idempotency-Key; replays return the cached response and the key is forwarded to Stripe.
Parameters, scopes and examples
Required scopes
write:pos
Payment intent
Request body
{
"amount": 12000,
"currency": "DKK"
}
POST/api/v1/admin/terminal/captureBearer or API key
Capture Terminal payment
Capture a confirmed card-present PaymentIntent. Returns `{captured: true, payment_intent_id}`. Money mutation — send an Idempotency-Key; an already-captured intent returns success.
Parameters, scopes and examples
Required scopes
write:pos
Capture
Request body
{
"payment_intent_id": "pi_xxx"
}
POST/api/v1/admin/terminal/receiptBearer token
Send Terminal receipt
Email or SMS a receipt for a captured Tap-to-Pay sale, resolved from the Stripe payment intent id. Requires pos.access, notifications.send and venue-wide location access before delivery. TERM-IDEMP-01: requires a caller-scoped Idempotency-Key; a replayed key returns the cached terminal response instead of re-sending. The atomic claim is bound to caller, venue and exact body. RECEIPT_CHANNEL_UNAVAILABLE is a confirmed pre-send refusal; RECEIPT_DELIVERY_UNCONFIRMED must retain the original operation for reconciliation.
POST/api/v1/admin/notifications/broadcastBearer or API key
Send broadcast
Send one or more push, email, and SMS channels to all members, selected member ids, or a server-resolved tag/pass/class audience. Idempotency-Key is required. Each channel derives a stable per-recipient delivery reference; a partial retry skips terminal successes/suppressions and resumes failed legs. Returns sent/skipped/failed counts per channel.
Parameters, scopes and examples
Required scopes
write:notifications
Broadcast
Request body
{
"channels": [
"email",
"sms"
],
"title": "New class added",
"target": {
"type": "tag",
"tag": "vip"
},
"subject": "New class added",
"body": "Check out our new Hot Power class on Saturday!"
}
GET/api/v1/admin/notifications/recentBearer or API key
Recent notifications
Recent email, SMS, and push notifications sent by the venue.
Parameters, scopes and examples
Required scopes
read:notifications
POST/api/v1/admin/staff/inviteBearer token
Invite a staff member
PROMPT_02 (S1-03) — provisions the auth user + profile + membership (status=invited), mints a staff_invitations claim token, and emails the venue-branded /auth/claim-invite link. Permission: staff.manage. Membership role may be admin, manager, reception, instructor, staff, or service_provider; profiles.role stays on the legacy CHECK (staff/provider seats persist as member there). The token is consumed by the WEB claim page (set password → membership flips invited→active); there is no separate accept API endpoint because the claim sets a password. 409 EMAIL_EXISTS when a Booking Bible account already exists for the email (adding an existing user as staff is a role change — use the admin UI). location_ids is stored on the invitation for record-keeping; location assignment remains a post-onboarding admin action. Emits staff.invited. Idempotency-Key supported.
Every staff shift in the venue for a date range. Permission: staff_scheduling.view. Joins staff profile name. Optional ?status= filter.
Parameters, scopes and examples
Query parameters
fromstring
Start ISO datetimeDefault: -7 days
tostring
End ISO datetimeDefault: +14 days
statusstring
Filter by ShiftStatus
POST/api/v1/admin/staff-scheduleBearer token
Create a staff shift
Create a new shift. Permission: staff_scheduling.manage. Note: API path skips the engine compliance pre-checks; for full compliance use the admin panel or the createShift server action.
Current walk-in queue (waiting + notified) for the authenticated org, ordered by position.
Parameters, scopes and examples
Required scopes
read:bookings
POST/api/v1/admin/walk-in-queueBearer or API key
Add walk-in
Add a walk-in to the queue. Allocates an atomic position via allocate_queue_position(). Idempotency-Key required. Add is silent; notification choice belongs to a later explicit Call. Optional service_id, client_id, preferred_provider_id and location_id must belong to the authenticated organization.
POST/api/v1/admin/walk-in-queue/{id}/callBearer or API key
Call queue entry
Atomically claim a waiting queue entry as called. Default-silent; explicit notify_sms=true requires notifications.send and SMS availability before mutation. Saved Add-time preferences never authorize delivery. A concurrent or already-called entry returns 409 INVALID_STATE. Returns sent_sms and optional notification_failure separately from committed call success. Permission: bookings.manage.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Queue entry ID
Explicit notification choice for this call only
Request body
{
"notify_sms": false
}
DELETE/api/v1/admin/walk-in-queue/{id}Bearer or API key
Remove walk-in
Cancel/remove a walk-in queue entry. Permission: bookings.manage.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Queue entry ID
GET/api/v1/admin/checkin/{classInstanceId}/qr-tokenBearer or API key
Get check-in QR token
Returns the current rotating QR token for a class instance. A new token is generated if none exists or the existing one is expired. Force rotation with ?refresh=true. Token TTL: 5 minutes. Permission: booking.checkin.
Read-only venue-local projection, reference validation, conflicts and daylight-saving time choices. No classes are created. The same body is accepted by the create endpoint.
Parameters, scopes and examples
Required scopes
scheduling.manage
Series template with venue-local wall times and recurrence.
Creates the template and projected occurrences atomically. Idempotency-Key is required; the durable operation is scoped to actor, venue and endpoint. Reusing an operation with a different body returns a conflict. Times resolve in the venue timezone; nonexistent wall times are refused and repeated wall times require an explicit choice.
Parameters, scopes and examples
Required scopes
scheduling.manage
Reviewed series template; confirm_conflicts explicitly accepts permitted conflicts.
Returns the authoritative venue-scoped template for editing. Existing occurrence times can differ from the template; clients must display and choose the intended source explicitly.
Read-only preview for this_only, all_future, from_date or all_including_past. Reports conflicts, affected bookings, capacity floors, notification availability and venue-local time ambiguity. A preview never authorizes client delivery.
Parameters, scopes and examples
Required scopes
scheduling.manage
Path parameters
idstring · required
Series UUID in the authenticated venue
Patch and edit scope; from_date is required for that scope.
Idempotency-Key is required. Start and end are independent fields. The canonical transaction validates the resulting series and current physical/online booking counts. Existing booked/past confirmation requirements remain. Staff delivery defaults silent; only explicit participant or instructor channels request delivery, with notifications.send checked before mutation. Cancelled occurrences require a separate reviewed reactivation instead of silently recreating bookings.
Parameters, scopes and examples
Required scopes
scheduling.manage
Path parameters
idstring · required
Series UUID in the authenticated venue
Patch, scope, applicable confirmations, and optional explicit notify channels.
Read-only review of eligible future cancelled occurrences and previous cancelled booking candidates. Returns opaque schedule/occurrence versions and venue-local projected times. Candidate inclusion is not booking eligibility approval; canonical booking rules are checked when each selected client is restored.
Requires Idempotency-Key and the exact reviewed schedule and occurrence versions. Selected client restoration additionally requires booking authority. Previous cancellation history remains immutable; selected clients receive new canonical bookings or individual ineligible/pending outcomes and current waitlist placement. No client notification or payment is requested by this endpoint. Operational projections retain their own recorded effect state. Stale reviews and changed retry intent return 409.
Parameters, scopes and examples
Required scopes
scheduling.manage
Path parameters
idstring · required
Series UUID in the authenticated venue
Explicit occurrence and client selection. Empty occurrences reactivates only the template.
Requires staff.view and an operational or service-delivering membership in the selected venue. Returns contract_version: 1, basic identity, membership role/status/capabilities plus additive show_on_teacher_page and selectable_for_named_booking, and profile_owned_by_venue plus editable.identity/photo. Those two booleans are not writable on this identity PATCH; use GET|PATCH /admin/staff/{staffId}/public-booking-settings. Foreign-home collaborator email, phone, bio and specialties are omitted as null. No compensation, payroll, customer treatment records or new role grants. User JWT only; API keys and mixed credentials are rejected.
Parameters, scopes and examples
Required scopes
staff.view
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
PATCH/api/v1/admin/staff/{staffId}Bearer token
Update a venue staff profile
User JWT only; mixed credentials and API keys are rejected. Idempotency-Key is required and bound to the organization, actor, target and validated body. Concurrent/replayed requests do not repeat the mutation. No client notification is sent. Mutation errors add details.write_state: not_written, partial, saved or unknown. Only proven not_written releases the reservation; partial/saved/unknown retain it. Missing or malformed metadata on older responses is unknown. PROFILE_PARTIAL alone does not prove a save. Keep the same body/key for the same uncertain operation; explicitly reload and review authoritative state before choosing a new intent. A failed acknowledgement never replaces the known HTTP response. Requires staff.edit; protected capability changes additionally enforce settings.permissions, self/hierarchy and last-admin rules. Identity belongs to the home venue; collaborator role changes do not permit editing another venue’s personal profile. Returns the refreshed staff detail DTO. Role/capabilities use membership CAS, not new profiles.role values. Last-admin occupancy plus row CAS is not an organization-wide concurrency lock.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
At least one of first_name, last_name, phone, nickname, bio, specialties and role is required. Empty updates are rejected before claiming the key. No organization, compensation, photo URL, show_on_teacher_page or selectable_for_named_booking fields.
User JWT only; mixed credentials and API keys are rejected. Idempotency-Key is required and bound to the organization, actor, target and validated body. Concurrent/replayed requests do not repeat the mutation. No client notification is sent. Requires staff.edit and home-venue profile ownership. Returns upload_url, immutable path, content_type and max_bytes (5000000) for instructor-photos storage. Upload the original to the returned signed URL, then call finalize with the same path. This step does not change the profile photo.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
Supported content_type: image/jpeg, image/png or image/webp.
User JWT only; mixed credentials and API keys are rejected. Idempotency-Key is required and bound to the organization, actor, target and validated body. Concurrent/replayed requests do not repeat the mutation. No client notification is sent. Mutation errors add details.write_state: not_written, partial, saved or unknown. Only proven not_written releases the reservation; partial/saved/unknown retain it. Missing or malformed metadata on older responses is unknown. PROFILE_PARTIAL alone does not prove a save. Keep the same body/key for the same uncertain operation; explicitly reload and review authoritative state before choosing a new intent. A failed acknowledgement never replaces the known HTTP response. Requires staff.edit and home-venue profile ownership. Accepts the owned immutable upload path or explicit null to remove the photo, never an arbitrary URL. For uploads, the server checks object existence, size and JPEG/PNG/WebP magic bytes before an organization-scoped profile update. Returns photo_url, including null after removal; a missing, oversized or invalid original is rejected without a profile write. Web and JWT share persistence and cache/path invalidation.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
Required path: the exact path returned by upload-url, or null for explicit removal. Omitted, empty and unknown fields are rejected.
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Returns contract_version 1, scope ("own" | "all"), can_manage and records[] with appointment, service name, provider name, note, normalized formula, products_used, photo URLs, privacy/pin flags and timestamps. Optional limit (1–200, default 50).
Parameters, scopes and examples
Required scopes
treatments.view_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Body: optional appointment_id (must belong to this client at this venue; an own-scope caller must have performed it, else 403 NOT_OWN_APPOINTMENT), note, optional formula (color formula schema), products_used[{name, quantity?, unit?, sku?}], is_private, is_pinned. Returns 201 with the staff record.
Parameters, scopes and examples
Required scopes
treatments.record_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Returns the staff record or 404 outside the caller’s scope.
Parameters, scopes and examples
Required scopes
treatments.view_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Own-scope callers may change only records they authored (403 NOT_RECORD_AUTHOR); managers may change any. Same body fields as create, all optional.
Parameters, scopes and examples
Required scopes
treatments.record_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Requires treatments.manage; every other scope receives 403 FORBIDDEN. Audit-logged.
Parameters, scopes and examples
Required scopes
treatments.manage
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Own scope returns tests the caller performed. Returns result, product, performed_at, expires_at and validity_hours.
Parameters, scopes and examples
Required scopes
treatments.view_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
Requires the member JWT and X-Organization-ID. treatments.view_own / treatments.record_own are the entry gates; the server then resolves the caller’s real scope: treatments.view_all or treatments.manage see every record, otherwise only records the caller authored or whose linked appointment the caller performed are returned or writable (others answer 404). Venue-wide reads are audit-logged. Formula data requires active color_formula_v1 consent for the client at this venue (409 CONSENT_REQUIRED / CONSENT_WITHDRAWN / CONSENT_RE_PROMPT_REQUIRED, 503 CONSENT_UNAVAILABLE when the check cannot be read). No payroll, compensation or other clients’ data is ever included. Send a UUID Idempotency-Key; a replay returns the stored response. Body: result (pass|fail|pending), product, notes, validity_hours (1–168, default 48), performed_at. Requires color_formula_v1 consent. Returns 201.
Parameters, scopes and examples
Required scopes
treatments.record_own
Path parameters
idstring · required
Client user UUID (active member of the selected venue)
User JWT and staff.edit required; API keys and mixed credentials are rejected. Returns contract_version:1, organization_id, staff_id, show_on_teacher_page and selectable_for_named_booking for the selected venue membership. The two booleans are independent. Missing/null named-booking coalesces to true; a present non-boolean fails closed. Not appointment-policies and not a raw membership dump. Public booking enforcement remains a required Stage B companion; this read does not change availability or checkout. This dedicated companion is explicitly silent: it never sends Email, SMS or Push and does not accept notification fields. Full Role & access saves keep their explicit channel picker and notifications.send-before-write rule.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
User JWT and staff.edit required; API keys and mixed credentials are rejected. Strict body: at least one of show_on_teacher_page and selectable_for_named_booking as booleans. Idempotency-Key binds selected organization, actor, operation staff.membership_public_booking.patch, staff_id and the validated body. Known refusals add write_state:not_written and release the claim. Unconfirmed writes return HTTP 409 SAVE_OUTCOME_UNKNOWN with write_state:unknown and keep the claim. A known save may include refresh_failed:true. Keep the same key on unconfirmed outcomes and reload before a new intent. Does not write identity, role, compensation or appointment policies. Native Business authoring remains required before full parity. This dedicated companion is explicitly silent: it never sends Email, SMS or Push and does not accept notification fields. Full Role & access saves keep their explicit channel picker and notifications.send-before-write rule.
Parameters, scopes and examples
Required scopes
staff.edit
Path parameters
staffIdstring · required
Staff profile UUID within the selected venue membership
At least one boolean. No organization, actor, notification, identity or appointment-policy fields.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
GET/api/v1/admin/rates/distributionBearer or API key
Get revenue distribution by rate
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
Accepts exactly one credential: Bearer JWT with `rates.view`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication. The client and pass type must both belong to that venue.
Accepts exactly one credential: Bearer JWT with `rates.override`, or an API key bound to the venue with `write:passes`. Dual credentials are rejected before authentication.
DELETE/api/v1/admin/rates/override/{passId}Bearer or API key
Clear a pass rate override
Accepts exactly one credential: Bearer JWT with `rates.override`, or an API key bound to the venue with `write:passes`. Dual credentials are rejected before authentication.
Parameters, scopes and examples
Required scopes
write:passes
Path parameters
passIdstring · required
Pass id
Response example
{
"data": {
"ok": true
},
"error": null
}
GET/api/v1/admin/private-eventsBearer or API key
List private-event bookings
Accepts exactly one credential: Bearer JWT with `private_events.view`, or an API key bound to the venue with `read:private_events`. Targets are non-enumerating and venue-scoped.
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
Parameters, scopes and examples
Required scopes
write:private_events
Private-event booking. Every PS-B2 field below is OPTIONAL and additive: client_id / save_as_client (link or create the client the session is for), partner_id + billing_target/billing_address/billing_vat_number/po_number/department/cost_center (bill a company — the billing block prefills from the partner record), brand_id, location_id, staff_note (a message the client sees), pricing_override ({mode: per_person|total, amount} — total is VAT-inclusive), and start_mode (confirmed | inquiry | confirm_on_payment) with payment_due_at. The legacy `status` field keeps working.
GET/api/v1/admin/private-events/{id}Bearer or API key
Get a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.view`, or an API key bound to the venue with `read:private_events`. Targets are non-enumerating and venue-scoped.
PATCH/api/v1/admin/private-events/{id}Bearer or API key
Update a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
Parameters, scopes and examples
Required scopes
write:private_events
Path parameters
idstring · required
Booking id
Fields to update. PS-B2 adds the same optional fields the create route takes (client_id, partner_id + billing block, brand_id, location_id, staff_note, pricing_override, payment_due_at) plus `reprice` (recompute the frozen subtotal/VAT/total/deposit) and `notify_client` ({enabled, channels}). The response carries the client-visible change summary.
POST/api/v1/admin/private-events/{id}/approveBearer or API key
Approve a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
POST/api/v1/admin/private-events/{id}/cancelBearer or API key
Cancel a private-event booking
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
POST/api/v1/admin/private-events/{id}/quoteBearer or API key
Send a private-event quote
Accepts exactly one credential: Bearer JWT with `private_events.manage`, or an API key bound to the venue with `write:private_events`. The canonical venue-scoped mutation workflow is used.
Accepts exactly one credential: Bearer JWT with `loyalty_price.manage`, or an API key bound to the venue with `read:passes`. Dual credentials are rejected before authentication.
GET/api/v1/admin/loyalty-price/members/{memberId}Bearer or API key
Get a client loyalty-price context for a grant-capable operator
Accepts exactly one credential: Bearer JWT with `loyalty_price.grant`, or an API key bound to the venue with `write:passes`. Returns only the named active member’s programme label and standing; venue-wide configuration and aggregate counts remain manage-only.
POST/api/v1/admin/loyalty-price/grantBearer or API key
Give a client the loyalty price
Accepts exactly one credential: Bearer JWT with `loyalty_price.grant`, or an API key bound to the venue with `write:passes`. Requires a UUID `Idempotency-Key`; a replay is bound to the same venue, caller and grant payload. Writes through the context-free grant core (never a cookie action), so the audit trail and status recompute are identical to the admin web surface.
Parameters, scopes and examples
Required scopes
write:passes
user_id + a short reason. Optional venue-local expiry, and an optional pass_id with an agreed price on the catalog (MAJOR) scale — pass_price_override requires pass_id.
Request body
{
"user_id": "uuid",
"reason": "Agreed with the owner at the desk",
"expires_on": "2027-01-31",
"pass_id": "uuid",
"pass_price_override": 249
}
Staff with members.view_insights (the same read permission as the client's pass history; changing it needs passes.manage). The client's default pass at the caller's venue and every pass that could be chosen (active, past_due, pending_activation, paused) with is_default, usable and unusable_reason (clips_exhausted | expired | paused | past_due | not_eligible | not_found) for the venue-local today; a renewing membership whose current cycle is spent stays usable (it refills). 404 NOT_FOUND when the client is not an active member of the caller's venue.
Staff with passes.manage. Sets or clears (pass_id null) which pass the client's bookings at the caller's venue use first. The client must be an active member of the caller's venue (404 NOT_FOUND) and the pass must be that client's pass at the same venue (404 PASS_NOT_FOUND, including another venue's pass); non-selectable passes are 422 PASS_NOT_SELECTABLE. Audited as member.default_pass_set with the staff actor. The client is not notified.
Server-to-server exchange of a single-use short-lived handoff bound to browser nonce, state, destination, brand, venue and approved origin. Returns a read-only staff report token for httpOnly cookie storage. Existing BookingBible business login authorizes the handoff; no new password or email allowlist.
Parameters, scopes and examples
Exact handoff fields from the authorized business redirect; nonce remains in the site httpOnly cookie.
The caller's referral status for their active org: code, referred-friend count, conversions, rewards earned, and an anonymized (first-name + last-initial) per-referral list. Returns an empty summary when the `referrals` module is disabled.
Everything the caller currently owes in one read: renewing memberships in past_due/suspended with a failed renewal charge, overdue client invoices with a balance at venues that take card payments online (including the remaining balance of a partially paid invoice past its due date), and declined one-off charges. Each item carries its frozen ISO currency, amount (major) and amount_minor, a plain-language failure_message, a state_key for "dismiss until it changes", and the action to take (pay_renewal, retry_invoice or update_payment_method). A membership_renewal item also carries cards_on_file: the member's saved cards on the Stripe account the membership bills through (the only cards pay_renewal can charge), with is_subscription_card marking the card the membership charges today and is_default the customer's default; omitted when the cards could not be read. Totals are per currency. Read-only; never charges. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
The caller’s ten most recent declined charges (failure_code present), newest first, with description for banner copy. Prefer GET /api/v1/me/outstanding-payments, which de-duplicates renewals and names the pay action. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
POST/api/v1/me/invoices/{id}/retryBearer token
Pay my invoice on my saved card
Charges the saved default card for the balance still owed (total − amount_paid) on the caller’s sent, viewed, overdue or partially paid invoice (one charge per invoice at a time); a partially paid invoice is settled by paying the remaining balance and becomes paid. Optional body { expected_amount_minor }: the balance the client showed, in the smallest currency unit (use outstanding_balance_minor from GET /api/v1/me/invoices). The invoice is read again right before charging; if the balance no longer equals that quote the call returns 409 BALANCE_CHANGED and charges nothing — refetch and show the new amount. Returns status succeeded; processing (bank still settling — do not pay again); requires_action with client_secret and stripe_account for Stripe 3-D Secure (then call …/retry/confirm); failed with failure_code/decline_code; or paid with already_settled when an earlier payment already covered it. Settlement records the invoice payment like a Payment Link payment and deactivates the invoice's Payment Link. An open 3-D Secure attempt never outlives a balance change: any payment write on the invoice cancels open retry PaymentIntents (unless still for exactly the balance owed), and a challenge whose balance changed while it was being created is cancelled instead of returned (409 BALANCE_CHANGED). Staff payment writes share the per-invoice lock, so a charge started during one answers 409 PAYMENT_PENDING. A charge received but not yet applied returns status processing with applied: false (never paid; do not pay again), plus review_required: true when the venue must apply or refund it. Every refusal carries error.details.next_step (update_card | refresh | contact_venue | null) and a failed charge carries data.next_step — show only that action: update_card only for a real card decline, null for a processor hiccup (processing_error, try_again_later) or a failure without a reason. 422 ALREADY_PAID, NOT_RETRYABLE or NO_PAYMENT_METHOD (no usable saved card); 422 CARD_PAYMENTS_UNAVAILABLE when the invoice's venue cannot take card payments online (no charge is attempted; can_pay_now is false for such invoices); 409 PAYMENT_PENDING (an earlier payment is processing or another is in progress), BALANCE_CHANGED or RECONCILIATION_REQUIRED (an earlier payment no longer matches the balance; the venue is alerted); 400 VALIDATION_ERROR for a malformed body; 404 for an unknown, foreign or malformed id. Owner scoped, 5/min, audited. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
Optional. expected_amount_minor = the balance the client showed (outstanding_balance_minor); omit the body to charge the balance read when the request starts.
Body { payment_intent_id }. Verifies first that the PaymentIntent belongs to this invoice, venue and member (retrieved on the invoice venue's own accounts), then reports: paid (applied like a Payment Link payment), pending (only while Stripe is processing it; do not pay again) or failed — nothing was charged — with reason not_started (the bank check never ran, e.g. Stripe.js did not load; next_step null, the client may pay again), not_completed (the check ran and failed; failure_code is the processor's reason and next_step is update_card only for a real card decline) or cancelled (the platform stopped it because the amount owed changed; next_step refresh). Call it after the 3-D Secure step whatever the SDK returned. 422 PAYMENT_MISMATCH (not this invoice or not equal to the balance) or NOT_RETRYABLE; 409 RECONCILIATION_REQUIRED; 400 VALIDATION_ERROR; 502 RETRY_FAILED. Owner scoped, 5/min. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
The PaymentIntent id returned by POST /api/v1/me/invoices/{id}/retry.
Request body
{
"payment_intent_id": "pi_123"
}
GET/api/v1/me/venue-affinitiesBearer token
My venue affinities
Active public venues ranked by explicit favourite, then canonical pass, class-booking, and appointment signals. Returns counts and last activity; caller identity is server-bound.
Member-JWT, tenant-scoped same-attempt recovery for one already-created recurring membership. Requires the owned pending pass, its immutable checkout operation and disclosure, an exact matching incomplete Stripe subscription, an open unpaid first invoice, and a still-resumable existing PaymentIntent. The route returns the existing client_secret only; it never accepts price, selection, promotion, quote, or checkout attempt input, and never creates, cancels, reprices, or re-evaluates a sale. Paid, terminal, ambiguous, mismatched, or concurrently changed operations fail closed with 409. Confirm the returned existing PaymentIntent through POST /me/checkout/confirm, which answers a lapsed reservation with 409 OFFER_CONTACT_VENUE at the settle step (never OFFER_HOLD_EXPIRED) whenever fulfilment was refused, including while its refund is still pending or needs a manual touch — a lapsed reservation is terminal there regardless of where the refund itself has gotten to. This endpoint RENEWS the reservation on every call (rate-limited to 15/user) — it is not a passive re-read or a safe poll. Call it only when the buyer explicitly resumes or restarts a checkout, never on a timer to "keep the countdown fresh": each call extends the hold by another 3 minutes, so polling it would hold a place indefinitely. The response carries hold_expires_at (ISO 8601 UTC instant the renewed reservation lapses, or null when no offer hold applies) and server_time (ISO 8601 UTC, the server clock at response time), always together, alongside the still-valid client_secret. Compute the countdown once as hold_expires_at minus server_time and run it locally, never against the device clock. A renewal that fails because the reservation already lapsed and a restart could still succeed fails closed with 409 OFFER_HOLD_EXPIRED; one where restarting cannot work (capacity gone, the per-client limit reached, or a late charge already refunded) fails closed with 409 OFFER_CONTACT_VENUE, which never offers a restart and instead points the buyer to the venue desk. OFFER_SOLD_OUT does not apply here: this endpoint only ever renews a reservation that already existed, never opens a first one.
Idempotently removes only the authenticated member and requested venue pair.
Parameters, scopes and examples
Path parameters
organizationIdstring · required
Venue organization UUID
GET/api/v1/me/credits/balancesBearer token
My venue credit balances
Complete ledger-derived balances grouped by venue and currency. Each row carries `balance` (the venue ledger total), `available` (that total minus credit held by open checkout reservations, which is what checkout will actually spend) and `reserved`. Amounts are in major units. Consumer is account-wide; branded requests are fail-closed to x-organization-slug.
POST/api/v1/me/avatar/upload-urlBearer token
Create avatar upload ticket
Returns a caller-owned, MIME-bound storage path and two-hour signed upload URL for PNG, JPEG, or WebP up to 5 MB.
PATCH/api/v1/me/avatarBearer token
Finalize my avatar
Validates caller path ownership, metadata, size, and image magic bytes before deriving and saving the public URL.
DELETE/api/v1/me/avatarBearer token
Remove my avatar
Idempotently clears the profile reference and removes only the caller-owned canonical avatar object.
GET/api/v1/me/workspace-profileBearer token
My active venue operating profile
Server-authoritative Business-app profile for the active venue selected by X-Organization-ID. Returns booking_mode (classes, appointments, or both), business_type, resolved class/appointment operation gates, venue surface applicability, appointment access/counts, active_modules, and additive operations.appointments.creation_enabled (verified active basic/full staff creation) and historical_creation_enabled (also history permission plus the protected retrocreate executor). Missing creation flags are false; history_enabled preserves existing-row reads after downgrade/archive, separately from management_enabled and new creation. Intersect history with bookings.manage. reschedule_enabled is independently true only for verified active/wind-down fulfilment, including hidden verticals; missing is false. the resolved vertical_modules visibility map. business_type is informational and never used to infer booking_mode. Surface values are venue-level applicability; clients must still intersect them with the caller's effective permissions from GET /api/v1/me.
Current user profile with all active venue memberships and roles. Each membership carries `permissions: string[]` (the caller's OWN effective permission keys for that org — per-user overrides applied over role/capability defaults, resolved identically to requireApiPermissionWithDefaults) and `capabilities: string[]` (the membership capability set, surfaced for every membership). To bound per-request cost in this multi-tenant app, `permissions` is resolved for the ACTIVE org only (top-level `permissions_scope: "active_org"`; non-active memberships carry `[]`) — mobile refetches /me on org switch. Workspace ownership is server-projected as `is_individual`, `is_owned`, `is_workplace`, `is_relationship`, `is_selectable`, and an explicit `workspace_group` (`owned`, `works_at`, `member_venues`, or `relationships`). Accepted role-bearing employer memberships remain selectable in Business under “Works at”; member-only Network relationships do not. Business clients must only put selectable rows in their workspace picker. Gates UI on these instead of discovering denials via 403s. A PATCH /admin/permissions/user/{userId} is reflected within ≤60s (permission-cache TTL). Caller's own permissions only. See docs/api/ME_PERMISSIONS_CONTRACT.md.
POST/api/v1/me/active-organizationBearer token
Switch my active workspace
Authoritatively switches the caller to an active, selectable workspace. When the caller owns an individual professional venue, accepted role-bearing employer memberships remain selectable; only non-operational/member-only relationships return WORKSPACE_NOT_SELECTABLE. The response includes effective permissions for the selected workspace so native role gating is safe immediately.
Resolved feature-module map for the caller's active org (C07): `{ <module_key>: { enabled, source, tier?, settings? } }` — the same four-tier resolution (plan → group → venue → tenant) the admin sees at /admin/features. Also includes `professional_collaborations`, which reflects the platform-wide teacher-settlements rollout independently of the venue-to-venue `network` plan gate. Drives every <FeatureGate> in the branded mobile app. Multi-membership callers must send X-Organization-ID; without it the map resolves empty (all off).
GET/api/v1/me/minimalPublic
Minimal auth check
Cross-origin auth check for venue marketing sites. Returns { logged_in, first_name, venue_id, preferred_brand_id } — or logged_in=false when no session. CORS is gated by the venue/brand embed_allowed_origins allowlist; unknown origins get no CORS headers (treated as "not logged in" by the caller).
PATCH/api/v1/meBearer token
Update profile
Update profile fields. Cannot modify role, balance, or org membership.
Create an account-local Stripe SetupIntent plus matching Customer/ephemeral-key credentials. Requires an Idempotency-Key header. Optional expected_renewals:[{pass_id,payment_id}] freezes the complete original renewal set after member, venue, payment and billing-account validation; omitted retains legacy behavior, [] pins only. The normalized target list is bound to the key and durable SetupIntent metadata, so a later debt cannot be substituted. An unknown outcome requires checking the same operation. The response freezes the server-owned venue country and exact Connect account for native Payment Sheet initialization.
Member self-service cannot detach saved cards. This endpoint returns PAYMENT_METHOD_REMOVAL_NOT_ALLOWED; add a replacement card or contact venue staff instead.
Promote a saved card to the Stripe customer default (invoice_settings.default_payment_method; default_source for a legacy card_ source). Optional expected_renewals:[{pass_id,payment_id}] is the complete frozen debt target set; omitted retains legacy fanout, [] pins without collection. An explicit list requires Idempotency-Key. Targets must belong to the actor, venue, pass and original billing account before a provider update. The key binds the normalized list as well as caller, venue and card; a changed list returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. An explicit unknown/foreign venue slug or stale active-venue pointer is 404 ORG_SCOPE_INVALID; a scope read failure is 503 ORG_SCOPE_UNAVAILABLE. A profile row with an explicit null active-venue pointer permits a platform-wallet default with no automatic membership collection; a missing or malformed profile is unavailable. A provider-ambiguous or post-provider failure is 503 DEFAULT_CARD_OUTCOME_UNKNOWN: read the current default and outstanding payments; the same key stays pending while its outcome is unknown. A proven no-effect failure releases the key for retry. GET /me/payment-methods then returns is_default:true on the matching row (PAY-P3.1). The response lists memberships[] with pin and original renewal outcomes, including payment_id when collected. A requires_action renewal can include client_secret and stripe_account for the existing attempt; complete that challenge then confirm settlement without another collection request. Contract: docs/api/MEMBER_SELF_PAY.md §2d.
Setup-only flow for Stripe Payment Sheet (PAY-P1.1). Requires an Idempotency-Key header. Returns customer_id, ephemeral_key, setup_intent_client_secret, and apple_merchant_id in the exact SetupIntent home account: connected only in direct mode, otherwise platform. Use when collecting a saved card before any purchase.
Register an Expo push notification token for iOS/Android/web. app_variant is required so member, branded-venue, and staff deliveries cannot cross application boundaries. Branded tokens require an explicit venue the member belongs to (X-Organization-Slug, or a member-validated X-Organization-ID; a conflicting pair is refused) and never use the profile's active venue, otherwise 400 ORG_REQUIRED. Business tokens require a validated organization context. A re-registered branded or business token is moved to a different venue only when that venue is named explicitly.
Deactivate the authenticated user's token or device before logout. The token/device selector is sent in the JSON body.
Parameters, scopes and examples
At least one token or device_id is required
Request body
{
"device_id": "installation-uuid"
}
GET/api/v1/me/notificationsBearer token
Notification history
Cursor/page-paginated email, SMS, push, and in-app history. Rows include source-aware `data`, `read_at`, and `app_variant`; X-App-Variant filters app-specific inbox events, while X-Organization-Slug narrows branded clients to their venue.
Self-scoped read marker. Idempotency-Key is required; another user’s row returns 404.
Parameters, scopes and examples
Path parameters
idstring · required
Notification id
POST/api/v1/me/notifications/read-allBearer token
Mark notifications read
Marks all of the caller’s unread rows read. X-Organization-Slug narrows a branded client to its exact venue; otherwise Consumer marks its cross-venue inbox. Idempotency-Key is required.
Returns the canonical ten-category catalog with effective email/SMS/push defaults, frequency caps, and per-member quiet hours for the active/requested organization.
Upserts canonical category toggles/frequency caps and quiet hours. Unknown categories are rejected and every database failure is returned; Idempotency-Key is required.
GET/api/v1/me/paymentsBearer token
List my payments
Cursor-paginated receipt-bearing payment ledger for the caller. Pending and failed attempts are excluded; successful, refunded, partially-refunded and disputed originals remain available with their payment receipt. An App Store purchase is shown at the price the member paid Apple, with `receipt_source: "apple"` (Apple issues that receipt).
Streams the receipt PDF (application/pdf) for one of the caller's payments — branded merchant header, line items, VAT breakdown, totals. Cached in storage after first render. App Store purchases return 409 APP_STORE_RECEIPT: Apple issues their receipt.
Emails the venue-branded receipt PDF for one of the caller's own payments to the address already on file for their account — the same document served by the PDF download. No recipient field exists; any caller-supplied recipient is ignored. Idempotency-Key is required; a retried key replays the cached result instead of re-sending. App Store purchases return 409 APP_STORE_RECEIPT.
Parameters, scopes and examples
Path parameters
paymentIdstring · required
Payment id
Empty body — the request is never read.
Request body
{}
GET/api/v1/me/refundsBearer token
List member refund receipts
Owner-scoped successful refund operations, cursor-paginated and optionally restricted by the branded organization slug. Split-tender operations are returned once with a signed negative amount. Apple’s refunds of App Store purchases show the member’s own price, `receipt_source: "apple"` and no `receiptPath`.
Parameters, scopes and examples
Query parameters
limitnumber
Page size (default 20, max 100)
afterstring
Opaque cursor from a previous page
GET/api/v1/me/refunds/{refundId}/pdfBearer token
Refund receipt PDF
Owner- and venue-scoped canonical refund receipt PDF. Non-final, sibling, cross-member and cross-venue refund ids return a uniform not-found response. Apple’s refunds of App Store purchases return 409 APP_STORE_RECEIPT.
Parameters, scopes and examples
Path parameters
refundIdstring · required
Refund id
GET/api/v1/me/loyaltyBearer token
Loyalty balance + history
The caller's org-scoped loyalty point balance plus a recent per-event history slice. Full paginated history is on /api/v1/me/loyalty/points.
GET/api/v1/me/loyalty/pointsBearer token
Loyalty points history
Cursor-paginated per-event loyalty point ledger for the caller.
GET/api/v1/me/streakBearer token
Attendance streak
Current + longest attendance streak, freezes remaining, and at-risk flag.
GET/api/v1/me/rewardsBearer token
Redeemable rewards catalog
Active loyalty rewards for the caller's org with affordability (is_locked) computed against the caller's balance.
POST/api/v1/me/rewards/redeemBearer token
Redeem a reward
Redeem a loyalty reward. Idempotency-Key supported; audited.
Parameters, scopes and examples
Redemption
Request body
{
"reward_id": "uuid"
}
POST/api/v1/feedbackBearer token
Submit feedback & tip
Rate a class (1-5 stars), leave a comment (optionally `anonymous`), and optionally tip the instructor via Stripe. The tip carries its own `anonymous` flag. The tip block of the response returns `client_secret`, `customer_id`, `ephemeral_key`, and `stripe_account_id` (non-null only in DIRECT charge mode).
Self-scoped class/appointment review and tip eligibility. Organization, target, settings, MobilePay capability, and prompt decision are server-derived from the owned source. Reads are side-effect-free unless `claim_prompt=true` is explicitly supplied by a prompt-mode entry check.
Parameters, scopes and examples
Query parameters
source_typestring · required
class or appointment
source_idstring · required
Owned booking id (class) or appointment id
claim_promptboolean
Reserve an in-app prompt only when true
POST/api/v1/post-attendance/reviewsBearer token
Submit class or appointment review
Creates one source-aware review after server-authoritative attendance/settings checks. Idempotency-Key required. `professional_rating`, tags, recommendation, anonymity, moderation, recipient notification, analytics, and webhooks are venue-controlled.
POST/api/v1/tipsBearer token
Tip a professional (no review)
Create a class or appointment tip in major currency units (`amount: 20` means DKK 20). Organization, professional, currency, Stripe account, and available methods are server-derived. Customer + ephemeral key are optional: customerless PaymentSheet still supports adding a card. MobilePay is returned only for verified Danish/DKK/venue-capable configurations. Idempotency-Key required.
Authenticated tipper-only reconciliation after PaymentSheet/MobilePay/3DS returns. Retrieves the server-owned PaymentIntent in its frozen Stripe account namespace, validates amount/currency/metadata, and emits receipts only after Stripe reports succeeded. Idempotency-Key required.
Parameters, scopes and examples
Path parameters
idstring · required
Tip id
GET/api/v1/tips/{id}Bearer or API key
Tip status
Poll a tip's status after confirming its PaymentIntent (incl. MobilePay / 3DS redirect returns). Access: the tipper (JWT), an org admin/manager (JWT), or an org-scoped API key. Cross-user / cross-tenant reads return 404.
Submit an Art. 15/16/17/20/21/22 request (access, erasure, portability, rectification, objection, art22 review). 30-day SLA. For erasure, account access is disabled immediately and the response reports erasure_status=pending_fulfillment; a super-admin performs the guarded erasure cascade within the SLA, while the SLA cron only alerts. Statutory records may be anonymised and retained for their legal period. Idempotency-Key required.
Parameters, scopes and examples
DSR request
Request body
{
"kind": "access",
"details": "Please send all data you have on me."
}
Re-trigger the guardian verification email for the caller's outstanding parental-consent request (C06). Matched by the authenticated email — no enumeration. Rotates the token and refreshes the 7-day expiry on the existing pending row (never a duplicate request). Empty body; Idempotency-Key supported; throttled 3/min per IP + 5/hr per user.
GET/api/v1/me/consent-statusBearer token
Active consents
Latest consent record per type for the authenticated user, with marketing decisions kept separate by organization_id. An explicit organization_id query or X-Organization-ID header requires active venue membership and must agree with the branded slug; foreign and platform-wide marketing rows are excluded. is_active is fail-closed and true only when the grant is unwithdrawn and policy_version matches the server-canonical current_policy_version; stale grants return requires_reacceptance=true.
Parameters, scopes and examples
Query parameters
organization_idstring
Exact active venue for marketing consent decisions
GET/api/v1/me/consentBearer token
Current native consent state + venue requirement
The venue's photo/video consent requirement (when organization_id is given) plus the caller's version-aware state for legal, marketing, analytics, photo/community and health-questionnaire consent types. Health consent is returned only for the exact membership-verified venue, with the active policy prompt_text for explicit native checkbox capture. A stale policy version is inactive and requires reacceptance.
Parameters, scopes and examples
Query parameters
organization_idstring
Resolve exact active venue marketing/health consent and photo-consent requirement; must match organization headers
POST/api/v1/me/consentBearer token
Capture native consent
Grant or withdraw one supported legal, marketing, analytics, photo/community or health-questionnaire consent for the caller. Health consent requires an explicit membership-verified organization_id, records the current server policy text with checkbox evidence, and a rejection closes prior health grants for that venue. Marketing decisions also require an exact venue. Other venue IDs require active membership. Grant versions are server-canonical; an unavailable policy returns 503 without writing.
The caller's health-questionnaire completion timestamp (completed_at, null when never submitted). Pre-check for the mobile hot-yoga booking gate.
POST/api/v1/me/health-questionnaireBearer token
Submit health questionnaire (Art. 9)
Submit the spa/hot-yoga health questionnaire for the caller's active org. Runs the Art. 9 contraindication consent gate, inserts a health_questionnaires row (plaintext responses; encrypted at rest by cron), stamps profiles.health_questionnaire_completed_at so the booking gate clears, and writes audit_log/user_events. Requires an Idempotency-Key (a double submit replays). Org resolved via X-Organization-ID / active membership.
Read the caller's external calendar-feed state: { token, enabled, generatedAt }. token is the opaque secret embedded in the public .ics feed URL (null when no feed is provisioned).
Enable the caller's external calendar feed and return the token. Idempotent — an existing token is returned unchanged (never rotated); a new one is minted (256-bit, base64url) only when absent. Empty body. Audited (calendar_feed_token_generated).
Revoke the caller's calendar feed: clears the token and disables the feed (the public feed then 404s). Empty body. Audited (calendar_feed_token_revoked).
Parameters, scopes and examples
Response example
{
"data": {
"enabled": false
},
"error": null
}
GET/api/public/calendar-feed/{token}Public
Public calendar feed
UNAUTHENTICATED — the opaque token in the path IS the credential. Returns one user's bookings as JSON for an external calendar subscription: { bookings, cancellations, userId, generatedAt }. bookings are upcoming events for the next 90 days; cancellations are bookings cancelled in the last 7 days (so calendar apps emit STATUS:CANCELLED). 404s on an unknown or disabled token (indistinguishable). Scoped strictly to the token's single user — no other user's data. 60 req/min per token.
Venue-scoped browser checkout adapter over the canonical pass purchase engine. Requires member JWT, x-organization-slug and Idempotency-Key. Accepts pass_type_slug, optional expected_pass_type_id from the original authenticated preview, start_date, selection_kind/quantity/option_id, addons, credit_amount and the complete flexible_quote for Flexible passes. A mismatched expected_pass_type_id returns 409 CHECKOUT_PRODUCT_CHANGED before pricing, promotion or purchase effects; omitted identity keeps legacy behavior. Optional confirm_saved_card:true is reserved for the buyer's explicit Pay action; omitted/false prepares an intent without off-session saved-card confirmation. Prospective checkout_operation_version:1 requires expected_pass_type_id and a fresh checkout:v1:<UUIDv4> key; never upgrade an old attempt. An existing versioned attempt returns CHECKOUT_OPERATION_RESOLVING and must use read-only original-attempt confirmation, never mint replay. The initial bounded free/account-credit fixed-pass cohort retains durable completion; unsupported initial configurations keep existing behavior. Exact retries keep the original body and key. Optional flash_sale_id selects a direct offer and is mutually exclusive with promo_code; fixed direct offers require the complete preview direct_purchase_quote. Sale identity, eligibility, lifecycle, promotion, caps, product and signed reviewed pricing are revalidated server-side before a new payment. Unavailable direct offers return FLASH_SALE_UNAVAILABLE; changed or expired proof returns QUOTE_* without falling back to normal price. Generic requests without flash_sale_id retain existing behavior. Returns the existing browser intent_type, client_secret, payment_intent_id, provider account/customer and canonical pricing envelope. Additive pass_type.id identifies the resolved product. purchase_confirmation is null unless canonical no-PI completion is proven; a proof contains confirmed:true, pass_id, pass_type_id and checkout_attempt. Payment status alone never proves fulfilment. Retain the original preview pass_type_id and attempt before dispatch. A recurring migrated legacy card cannot be prepared without an explicit Pay action: 409 SAVED_CARD_PAY_ACTION_REQUIRED occurs before subscription creation. When the checkout opens or re-opens an offer hold, the response also carries hold_expires_at (ISO 8601 UTC instant the reservation lapses, or null when no offer hold applies) and server_time (ISO 8601 UTC, the server clock at response time), always together, next to client_secret. Compute the countdown once as hold_expires_at minus server_time and run it locally, never against the device clock; at zero, disable the pay step and stop any confirm from starting. A lapsed hold that can still be retried returns 409 OFFER_HOLD_EXPIRED; a lapsed hold with no way to restart (capacity gone, the per-client limit reached, or a late charge already refunded) returns 409 OFFER_CONTACT_VENUE, which never offers a restart and instead points the buyer to the venue desk. A first attempt with no reservation ever held, against an offer that is already fully booked, returns 409 OFFER_SOLD_OUT with plain copy and no charge attempted. The response also carries an additive subscription_id (nullable), so a client replaying this same request body later can re-read the countdown on a recurring reservation without opening a second checkout. A recurring membership whose first period is free today (a 0 kr offer or partner code, a free trial, a first cycle fully discounted) and that has no card on file returns intent_type setup with a SetupIntent client_secret (or a payment client_secret when a registration fee is due): nothing is activated and no offer place or code is consumed until that card step succeeds. Confirm it, then call POST /me/checkout/confirm. Abandoning it leaves the code unused; the same member can start a new checkout with the same code. A declined saved-card first invoice returns 402 INSUFFICIENT_FUNDS when the original Stripe charge proves that issuer reason; otherwise it returns 402 PAYMENT_FAILED.
Parameters, scopes and examples
Optional flash_sale_id and the exact flexible_quote or fixed direct_purchase_quote are forwarded unchanged from preview; client amounts never authorize a charge. Exact retained HTTP receipts replay the original payment status after offer expiry, without new provider work. A missing or pruned receipt still requires current offer validation.
Buyer-facing canonical PricingBreakdown with additive pass_type_id to retain before purchase — net/VAT split, registration fee, total today + recurring, localized policy terms, the start-date window, the required legal artifacts (with already_signed), and the buyer's saved signatures. NO charge. Member-JWT + stable x-organization-id (preferred) or legacy x-organization-slug, both membership-scoped. Query: pass_type_slug (required), start_date, binding_months, locale (en|da). A selected binding tier is validated and priced server-side; unavailable tiers return 422. Optional flash_sale_id (UUID) selects a direct venue offer and is mutually exclusive with promo_code. The server resolves the linked promotion and checks venue, lifecycle, eligibility, caps and product compatibility; unavailable offers return 422 FLASH_SALE_UNAVAILABLE, never a normal-price fallback. Flexible direct-offer quotes add flash_sale_id, flash_sale_rule_fingerprint and purchase_obligation_fingerprint; pass the complete flexible_quote unchanged to purchase. One-time direct offers are fixed-price products only: the existing compatibility policy continues to reject Flexible class-pack and time-pass flash sales. Direct offers add purchase_obligation {status:available} for canonical one-time purchases and supported non-deferred introductory memberships. minimum_total_payable is the all-tender contractual minimum in minor units. Recurring disclosures include intro_through_date inside purchase_obligation. Immediately reachable self-service notice retains earliest_cancellation_effective_on. Staff-managed cancellation instead includes purchase_obligation.cancellation {kind:conditional_contractual_minimum, request_method:contact_studio, condition:timely_valid_notice, earliest_possible_end_on} and omits earliest_cancellation_effective_on. Clearly display that both the minimum and earliest possible end depend on timely valid notice; neither is confirmation of an actual cancellation, receipt time or staff response SLA. Aligned trial schedules and exact first/whole-cycle coupon schedules use canonical renewal and commitment rules. Deferred, inexact coupon cadence, immediate-cancellation or unreadable policy contexts return {status:unavailable, reason_code, message} without purported exact facts. One-time purchases omit cancellation facts. Both signed quote authorities bind all facts and policy/anchor inputs; changed authority is refused before new payment side effects. Existing accepted purchase recovery keeps its original snapshot. Fixed direct offers instead return direct_purchase_quote {version:1, organization_id, user_id, pass_type_id, flash_sale_id, authority_fingerprint, credit_applied_minor, payable_minor, issued_at, expires_at, fingerprint}. This five-minute signed proof binds the reviewed canonical price, commercial terms and exact account-credit/cash split. Both tender fields are nonnegative integer minor units; payable_minor equals charged_today. A changed credit balance or tender requires a new review (QUOTE_STALE), never a larger cash charge. Direct gift cards are not supported unless quoted; generic gift-card checkout is unchanged. Send the proof unchanged to purchase. Optional direct_offer {flash_sale_id, name, normal_amount, offer_amount} is display metadata for fixed one-time offers (amounts in minor units); charged_today is the payable authority. Deprecated for promo codes: a promo_code in the query string lands in logs and referrers; send it with POST /api/v1/me/checkout/preview instead.
Parameters, scopes and examples
Query parameters
pass_type_slugstring · required
Venue pass slug
flash_sale_idstring
Optional direct-offer UUID; mutually exclusive with promo_code
promo_codestring
Deprecated here (URLs land in logs): send it with POST instead
POST/api/v1/me/checkout/previewBearer token
Checkout pricing preview (parameters in the body)
Identical to GET /api/v1/me/checkout/preview — same auth, rate limit, validation and response — with the parameters as a flat JSON object instead of the query string, so a promo code never travels in a URL. Use this whenever promo_code is sent.
Alternatively provide checkout_attempt and the original preview pass_type_id for DB-read-only completion reconciliation: confirmed:true includes purchase_confirmation {confirmed:true,pass_id,pass_type_id,checkout_attempt}; missing or expired proof returns confirmed:false,purchase_confirmation:null,payment_state:resolving. This branch performs no purchase replay or provider work and does not authorize a retry. Free-pass recovery currently requires a retained original response proof; durable raw-attempt mapping remains incomplete. Confirm the exact existing PaymentIntent or deferred SetupIntent for an authenticated venue member. A successful client-side payment is not proof of pass fulfilment. The canonical finalizer can return 409 CUSTOMER_ELIGIBILITY if admission is refused or unresolved after settlement. Preserve the original purchase operation and show a neutral resolving outcome; do not start a replacement payment. Other existing 409 outcomes remain distinct. For a free-start membership (setup_intent_id), this call activates the membership and redeems its offer, exactly once together with the setup_intent.succeeded webhook; a SetupIntent that has not succeeded yet (for example still in 3-D Secure) returns 409 PAYMENT_NOT_COMPLETED and activates nothing. If the offer place can no longer be granted it returns 409 OFFER_HOLD_EXPIRED (start again; nothing was charged and the code was not used) or 409 OFFER_CONTACT_VENUE, and an attempt that was already closed returns 409 MEMBERSHIP_SETUP_EXPIRED.
Parameters, scopes and examples
Exactly one payment_intent_id, setup_intent_id, or checkout_attempt paired with original pass_type_id
Records a waiver / ToS / privacy / contract acceptance with IP + user-agent + version + signature. Idempotent on (user, document, version); a stale version → 409 force-refetch; a minor (DOB < 18) → 409 + parental consent. Supports saved-signature reuse (saved_signature_id) honouring signature_kind. Linked contracts require pass_type_slug and may include start_date; the endpoint idempotently creates/adopts the exact current-version pre-purchase contract before signing.
Read-only payment snapshots retained during a brand split or transfer. These records are separate from the live payment ledger and do not support refunds, receipt resends, invoice actions or revenue totals. Each row retains its original amount in major currency units, ISO currency, source status, occurrence time and snapshot time. Archive IDs are not payment IDs. Pagination sorts by original occurred_at and archive ID; reuse the exact opaque cursor with the same venue and member. Invalid pagination returns 400; unavailable or unverified history returns 503 rather than an empty history. Always scoped to the authenticated member. Optional X-Organization-Slug restricts the collection to one venue; an unknown slug returns no rows, never a global fallback. Without that header, the collection includes this member’s archived history across venues.
Parameters, scopes and examples
Query parameters
limitnumber
Page size: 1–100, default 25.
afterstring
Opaque next_cursor from the same archive collection and scope.
POST/api/v1/me/checkout/recoveryBearer token
Inspect an original product-package or paid service purchase
Read-only, non-minting recovery for an active authenticated venue member. Requires x-organization-slug and the exact original purchase key. kind product_package reads the canonical product-package operation; kind service reads the paid appointment operation. Returns frozen amount_minor/currency and durable/provider status, never client secrets. unknown/read errors are distinct from an authoritative not_found; unbound creation stays pending and provider success stays pending until durable fulfillment. Every outcome retains the original key; not_found is a point-in-time read and never authorizes a replacement financial operation. Passes, recurring products, bundles and staff POS are unsupported.
Returns the authenticated Namasté Online member’s Apple-billed pass status and exact expiration. A null result means this account has no linked Apple purchase.
POST/api/v1/me/apple-subscriptionBearer token
Link a verified Apple subscription
Verifies a StoreKit signed transaction against Apple’s certificate chain and binds its appAccountToken to the authenticated Namasté Online member. Replays update the same pass. A Production purchase is also recorded once as an App Store sale at estimated net proceeds; Sandbox purchases never are.
Parameters, scopes and examples
StoreKit 2 signed transaction JWS
Request body
{
"signed_transaction": "<Apple signed transaction JWS>"
}
GET/api/v1/me/entitlementsBearer token
My entitlements
The caller's entitlement matrix for one venue: can_book_physical (any active pass with grants_in_person), can_watch_online (grants_online_class_access), online_only, bookable_class_type_ids ("all" when any usable pass is unrestricted), and an active_passes[] summary (slug, category, grants, validity, clips, allowed_brand_ids). Optional brand_id OR class_instance_id scopes the capability flags to passes valid for that brand / occurrence (effective brand ∪ class-type brand ∪ "Also show under…" brands) and is echoed as brand_scope. Errors: 400 INVALID_BRAND_SCOPE (both, or a non-UUID), 404 BRAND_NOT_FOUND / CLASS_NOT_FOUND, 500 BRAND_LOOKUP_FAILED (brand read failed), 503 PASS_RESTRICTION_UNVERIFIED (pass restrictions unreadable; retry). The booking engine enforces the same matrix; booking refusals include PASS_BRAND_RESTRICTED and CLASS_TYPE_RESTRICTED (422).
Parameters, scopes and examples
Query parameters
organization_idstring · required
Venue to resolve entitlements for
brand_idstring
Optional brand (UUID, this venue): flags for classes owned by that brand
class_instance_idstring
Optional class occurrence (UUID, this venue): flags under the booking engine brand scope
Cursor-paginated list of the caller's member-visible client invoices. Drafts are excluded and every row includes an authenticated document_path for the print-ready HTML invoice. Every row also carries outstanding_balance (the balance still owed on an open invoice, 0 once paid or closed, major units of the invoice currency), outstanding_balance_minor (the same in the smallest unit) and can_pay_now (true when POST /api/v1/me/invoices/{id}/retry accepts it: sent, viewed, overdue or partially paid with a balance, at a venue that can take card payments online) and pay_now_unavailable_reason (card_payments_unavailable when an owed balance cannot be paid online — tell the client to contact the venue — else null). Show "Pay remaining balance" when amount_paid > 0 and pass outstanding_balance_minor as expected_amount_minor.
Owner-scoped detail for one issued client invoice, including line items and the totals breakdown (subtotal, discount, VAT, total, amount_paid), plus outstanding_balance, outstanding_balance_minor, can_pay_now and pay_now_unavailable_reason (same meaning as on GET /api/v1/me/invoices).
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
GET/api/v1/me/invoices/{id}/documentBearer token
Invoice print document
Authenticated owner- and venue-scoped print-ready HTML for one member-visible invoice.
Parameters, scopes and examples
Path parameters
idstring · required
Invoice id
GET/api/v1/me/guest-invitesBearer token
Can I bring a guest to this class?
GUEST-INVITE-01 — whether the caller's passes qualify them to host a guest at this class, the venue guest price, the standard single-class price to strike through (compare_at_price, display only), and any invitations they already have open for it. `reason` is plain-language copy safe to render verbatim when `eligible` is false. For the two Vibro brands, a booked host also receives `age_band_prices` for a separate one-class guest sale.
Creates the invitation plus its pending guest seat (a GUEST-PAY-01 `pending_payment` booking that holds NO capacity until paid). `payer:'guest'` returns the link to share; `payer:'host'` additionally returns a Stripe Checkout URL (saved card, new card, or MobilePay). `return_base_url` must be an allowlisted host or it is ignored. Vibro Yoga and Vibration Shower require a host-owned class booking, `payer:host`, and an `age_band` of `u30` or `o30`; U30 also requires `guest_date_of_birth`, and Vibro Yoga requires `guest_address`.
For a pending host-paid guest invitation, returns the existing or idempotently prepared PaymentIntent so a brand site can reconcile an uncertain MobilePay or card return. A paid invite returns status `paid`. The caller must be the original host in the same venue.
DELETE/api/v1/me/guest-invites/{id}Bearer token
Withdraw a guest invitation
Withdraws an UNPAID invitation and releases its pending seat. A paid guest spot is a real booking — cancel it through the normal booking cancellation path so the venue's refund and fee rules apply (409 `ALREADY_PAID`).
GET/api/v1/guest-invites/{token}Public
Resolve a guest invitation (public)
GUEST-INVITE-01 — the invitation landing page a friend opens. Anonymous-allowed by design (the token is the capability); returns who invited them, the class, the price and the struck-through standard price, and nothing else about the host's account. `state` is `needs_account` for a signed-out visitor, `payable` once signed in, plus `already_paid` / `cancelled` / `expired` / `class_started` / `class_full`.
POST/api/v1/guest-invites/{token}/checkoutPublic
Pay a guest invitation without an account
GUEST-INVITE-01 — the Guest Visitor branch. ANONYMOUS-ALLOWED (the token is the capability): the invited friend pays without creating an account and receives a Stripe Checkout URL. Deliberately does NOT claim the seat, so no profile is created and `bookings.user_id` stays the host. Confirmation is still the verified-payment webhook. Trade-off the calling site MUST surface: with no login, only the host or the venue can cancel it afterwards. Refuses with 409 `ALREADY_CLAIMED` once someone has linked the invitation to an account.
GUEST-INVITE-01 — the class filled up before the invited friend accepted. ANONYMOUS-ALLOWED. An unpaid invitation never held a seat, so this is a normal outcome, not an error: the friend joins the waiting list and is NOT charged. If a spot opens, `reinviteWaitlistedGuests` sends a fresh payment link. Returns `{ position, already_on_waitlist }`.
The invited friend, now signed in, takes ownership of the guest seat and gets a Stripe Checkout URL. Claiming rebinds `bookings.user_id` to their profile (the host stays on `host_user_id`), which is what makes the spot appear in their own bookings and cancellable by them under the venue's ordinary cancellation rules. Capacity is still only taken by the verified-payment confirm RPC.
Confirmed pilot member and active scoped viewer session required. Stable event IDs dedupe selection, attempt, player progress, stop and error observations. Actual output is not inferred.
List authenticated user's bookings across all venues, or only the venue named by X-Organization-Slug when that header is sent (an unknown slug, or an organization_id naming another venue, returns an empty list). Supports cursor and offset pagination. Active upcoming bookings carry `cancellation_terms` (CANCEL-PREVIEW-01 v1.1): that booking’s effective window (`source` course | location | brand | venue), its late and no-show fee and `pass_effect_if_late` for its pass, by the same rule as GET /api/v1/me/bookings/{id}/cancellation-preview; null for cancelled, past or imported rows and when it cannot be read.
Venue-local YYYY-MM-DD start of a pass usage cycle (inclusive). Requires organization_id.
cycle_endstring
Venue-local YYYY-MM-DD end of a pass usage cycle (exclusive). Requires organization_id.
afterstring
Cursor for pagination
limitinteger
Items per pageDefault: 20
POST/api/v1/bookingsBearer or API key
Book a class
Create a booking. Validates pass eligibility, capacity, booking window, daily limits, and class restrictions. A venue daily-limit refusal is DAILY_LIMIT_REACHED with details { daily_limit, daily_used, daily_limit_scope, workshops_count, course_sessions_count } and a message naming what does not count; a pass per-day cap refusal is CLASS_LIMIT_EXCEEDED with details { pass_daily_limit, pass_daily_used, workshops_count, course_sessions_count }; if a pass per-day cap cannot be checked the booking is refused with 503 DAILY_LIMIT_UNVERIFIED (nothing booked; retry). Supports idempotency via Idempotency-Key header. Default pass: when the member (JWT) has chosen a default pass (PUT /api/v1/me/default-pass) that is used up or cannot be used for this class and another pass can, the response is 409 DEFAULT_PASS_UNUSABLE with details { default_pass: { id, name, reason: clips_exhausted|expired|paused|past_due|not_eligible|not_found }, suggested_pass: { id, name, remaining, end_date }, choices }. Retry with pass_id = suggested_pass.id plus switch_default: true (book and make it the default) or use_once: true (book, keep the default). switch_default and use_once are mutually exclusive and require pass_id; API-key bookings never receive this prompt. With no default the existing order applies silently; the booking response names the pass used (passes.pass_types.name). Waiver: when the class type requires one, an unsigned member gets 400 WAIVER_REQUIRED; if the signature check cannot run the booking is refused with 503 WAIVER_CHECK_UNAVAILABLE (nothing booked; retry). The response carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.2): the new booking’s effective cancellation terms at booking time, from the same rule as GET /api/v1/bookings/{id}; null when none apply.
Retrieve a single booking with class details, pass info, and check-in status. Optional X-Organization-Slug restricts access to that venue; unknown or mismatched venue and another member return 404 NOT_FOUND before effects. No header preserves account-wide member ownership. An active upcoming booking carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.1) — its effective window, fees and `pass_effect_if_late`, by the preview’s rule; null otherwise.
Parameters, scopes and examples
Required scopes
read:bookings
Path parameters
idstring · required
Booking ID
POST/api/v1/bookings/buddyBearer token
Invite a client to the same class
Creates and emails a venue-branded invitation to an existing active client at the same venue. The inviter must already have a confirmed booking. The recipient books with their own pass or payment; no guest funding or inviter entitlement is used. Idempotency-Key supported.
Accepts a buddy invitation for the authenticated recipient and books the same class with that recipient’s own eligible pass or normal venue booking rules. The invited email/account and active venue membership must match. Idempotency-Key supported.
Cancel a booking with the same rule as GET /api/v1/me/bookings/{id}/cancellation-preview. data.applied is that CancellationDecision for what the cancel actually did (fee, clip effect, copy); cancellation_fee (major units) is unchanged. Send accepted_outcome and accepted_fee_minor from the preview the member confirmed: when the fresh terms differ (the window closed meanwhile, the fee changed, or online cancellation is no longer allowed) nothing is cancelled and the response is 409 CANCELLATION_TERMS_CHANGED with error.details.decision holding the fresh terms. 503 CANCELLATION_TERMS_UNAVAILABLE means the venue rules could not be read and nothing was cancelled. Send accepted_outcome and accepted_fee_minor together or neither. A body that is not JSON, only one of the two, or a malformed reason / accepted_*, is 400 VALIDATION_ERROR and nothing is cancelled. Optional X-Organization-Slug restricts access to that venue; unknown or mismatched venue and another member return 404 NOT_FOUND before effects. No header preserves account-wide member ownership.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Booking ID
Optional reason plus the terms the member confirmed
Cookie-authenticated alternate cancellation endpoint with its legacy raw response envelope (the RPC result plus `applied`, the CancellationDecision for what the cancel did). Accepts the same accepted_outcome / accepted_fee_minor as DELETE /api/v1/bookings/{id} (409 cancellation_terms_changed with details.decision when they no longer hold; 400 validation_error for a body that is not JSON, only one of accepted_outcome / accepted_fee_minor, or a malformed reason / accepted_*; nothing cancelled). Honors X-Organization-Slug and member ownership before mutation; unknown or conflicting venue returns 404.
Parameters, scopes and examples
Required scopes
write:bookings
Path parameters
idstring · required
Booking ID
POST/api/v1/bookings/{id}/checkinBearer token
Self check-in
Member self check-in. Available within configured time window before class start. Optional X-Organization-Slug restricts access to that venue; unknown or mismatched venue and another member return 404 NOT_FOUND before effects. No header preserves account-wide member ownership.
NAMASTE-GATES-01 / LIVE-PARTICIPATION-01 — entitlement-based live-watch path. An existing physical booking returns 409 PHYSICAL_BOOKING_SWITCH_REQUIRED with error.details {booking_id, class_instance_id} before reservation, signing or admission; a Join tap never converts attendance or assesses a fee. The explicit switch endpoints are not yet released. Requires an active pass whose type grants online class access and covers the class type; finds or creates the caller’s attendance_type=online booking (idempotent, respects online_capacity, consumes a clip only for clip-based passes) and returns a provider-signed playback URL plus viewer-session telemetry token. The additive playback_transport is hls or whep. A WHEP input requires X-Playback-Transports containing whep; legacy clients receive 426 PLAYBACK_TRANSPORT_UNSUPPORTED before booking or clip consumption. WHEP never falls back to HLS. First entry opens 10 minutes before start and closes exactly at start; admitted viewers and an entitled viewer with a server-confirmed pre-start waiting reservation may recover through end + 5 minutes unless they explicitly leave after start. A not-ready/paused response includes additive error.details {viewer_session_id, class: {name, instructor, start_time, end_time}} only when the reservation RPC supports durable waiting; legacy RPC deployments omit that promise. Waiting creates no booking or clip consumption and every retry rechecks current entitlement. Stream readiness is checked BEFORE any booking side effect: the class instance must be status=live AND carry a playback id, otherwise 409 STREAM_NOT_READY (retryable) — a prepared playback id alone is never readiness, because the phone publisher stamps one while the instance is still scheduled. The additive stream_paused flag is true while the operator has paused the stream; the URL stays valid, so render a paused notice instead of the player. The additive music_pilot_enabled field controls music UI; signed private music_url, stable mix IDs and brand choices are issued only to the confirmed pilot account. GET /api/v1/bookings/{id}/online-join (the booking-scoped twin for members who already hold an online booking) returns the same STREAM_NOT_READY code and the same stream_paused flag. A request with `X-App-Digital-Content: none` (store build without in-app digital content) is refused by both routes with 403 IN_APP_DIGITAL_CONTENT_DISABLED before any booking or signed playback URL.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
Query parameters
music_brand_idstring
Optional brand UUID for the private music pilot; only the confirmed pilot account receives signed mix choices.
max_resolutionstring
Optional Mux playback cap: 720p or 1080p, embedded in the signed token. Omit for automatic quality. Invalid values return 400 INVALID_QUALITY; Cloudflare delivery ignores this cap.
What cancelling one owned class booking NOW means, from the venue’s current rules and in the venue’s timezone. `data.decision` is the CancellationDecision every client renders: outcome free | late | not_cancellable, reason_code, the moment the free window closes (ISO with the venue offset), the exact fee (minor units, currency, VAT breakdown, formatted), the pass effect (clip_returned | clip_deducted | unlimited | course_booking_returned | course_booking_used | none), the policy line, the consequence message and the confirm-button label, each in English and Danish. The window resolves course-access snapshot → class location → venue → 3 h; the fee resolves from the venue’s cancellation matrix (pass type → category → default) and the location/venue late_cancel_fee_amount. The window is closed AT its closing instant. The member cancel endpoints decide with the same rule, so the preview is what the cancel does; send accepted_outcome / accepted_fee_minor on the cancel to have it refused (409 CANCELLATION_TERMS_CHANGED) when the terms changed meanwhile. Computed per request and served with Cache-Control: no-store — never cache it across the window boundary. The pre-v1 flat fields (can_cancel, blocked_reason, cancellation_window_hours, is_late, will_charge, fee_amount in major units, currency, will_forfeit_clip, course_access, will_forfeit_course_booking, message) remain, derived from the same decision; locale=da selects the language of the flat message. Honours the optional x-organization-slug tenant scope; another member’s or venue’s booking returns 404. A failed rules read returns 500 CANCELLATION_PREVIEW_QUERY_FAILED — clients must then refuse to cancel blindly and offer a retry.
Parameters, scopes and examples
Path parameters
idstring · required
Booking UUID
Query parameters
localestring
Language of the legacy flat `message` fieldDefault: en
Response example
{
"data": {
"booking_id": "uuid",
"can_cancel": true,
"blocked_reason": null,
"cancellation_window_hours": 3,
"is_late": true,
"will_charge": true,
"fee_amount": 50,
"currency": "DKK",
"will_forfeit_clip": false,
"course_access": false,
"will_forfeit_course_booking": false,
"message": "Cancelling now counts as a late cancellation: the cancellation window closed at 06:30 today (venue time). You will be charged 50 kr.",
"decision": {
"version": 1,
"booking_id": "uuid",
"outcome": "late",
"reason_code": "inside_window",
"computed_at": "2026-09-24T05:05:00.000Z",
"timezone": "Europe/Copenhagen",
"class": {
"name": "Hot Yoga 60",
"starts_at": "2026-09-24T09:30:00+02:00"
},
"window_hours": 3,
"window_closes_at": "2026-09-24T06:30:00+02:00",
"hours_before_start": 2.42,
"fee": {
"amount_minor": 5000,
"currency": "DKK",
"vat_included": true,
"vat_rate_percent": 0,
"vat_amount_minor": 0,
"formatted": "50 kr"
},
"pass_effect": "unlimited",
"pass": {
"id": "uuid",
"name": "Unlimited Monthly"
},
"refund": null,
"policy_summary": {
"en": "Free cancellation until 3 hours before class. After that, a 50 kr late-cancellation fee applies.",
"da": "Gratis afmelding indtil 3 timer før holdet. Derefter koster en sen afmelding 50 kr."
},
"message": {
"en": "Cancelling now counts as a late cancellation: the cancellation window closed at 06:30 today (venue time). You will be charged 50 kr.",
"da": "Afmelder du nu, er det en sen afmelding: afmeldingsfristen udløb kl. 06:30 i dag (lokal tid). Du bliver opkrævet 50 kr."
},
"confirm_label": {
"en": "Cancel and pay 50 kr",
"da": "Afmeld og betal 50 kr"
}
}
}
}
POST/api/v1/qr/checkinBearer token
QR self-check-in
Client-facing: exchange a valid QR token for a check-in on the caller's booking. Token must be active and not expired. Anti-replay: a booking can only transition to checked_in once. Every scan writes an audit_log entry regardless of outcome.
Returns offers and current server eligibility for the authenticated member. Optional venue filter narrows active memberships; branded organization slug scope is enforced. Paused, ended and unavailable offers may remain visible without valid proof. No cached result is proof.
Parameters, scopes and examples
Query parameters
organization_idstring
Venue UUID filter
POST/api/v1/benefits/proofBearer token
Create a live benefit card
Rechecks the vendor agreement, venue settings and exact qualifying pass or service settlement under the agreed trigger. Proof rotates every 20 seconds and expires after 60 seconds. verification_url contains an opaque fragment token; never log or persist this response.
Parameters, scopes and examples
Selected offer and owning venue
Request body
{
"deal_id": "uuid",
"organization_id": "uuid"
}
GET/api/v1/admin/benefitsBearer token
Read venue benefits configuration
Requires passes.manage. Returns approved deals, pass/service choices, saved assignments, rollout enabled state and optimistic revision for the authorized venue.
PUT/api/v1/admin/benefitsBearer token
Configure venue benefits
Requires passes.manage. Atomically replaces pass and service assignments within current vendor constraints. accepted_version must match each enabled agreement; expected_revision prevents lost edits. Does not notify clients.
Parameters, scopes and examples
Complete configuration; disabled assignments retain history of accepted terms
Requires passes.manage and resolves the target member only within the authenticated venue. Evaluates saved configuration with the canonical eligibility service; never issues a proof for staff.
Parameters, scopes and examples
Query parameters
user_idstring · required
Member UUID
Discovery22 documented operations
GET/api/v1/discoveryPublic
Discover live venue inventory
Bounded, paginated Universe discovery for one canonical world and venue-local date. Returns capped live class or service/appointment summaries with explicit partial failures; member coordinates are not accepted.
Parameters, scopes and examples
Query parameters
worldstring · required
Required: classes, treatments, or salon
datestring · required
Required venue-local date (YYYY-MM-DD)
time_windowstring
any, morning, afternoon, or eveningDefault: any
searchstring
Venue, location, class, or service search
pageinteger
Page numberDefault: 1
limitinteger
Venue items per page (max 4)Default: 4
GET/api/v1/discovery/countsPublic
Legacy activity and published service venue counts
Lightweight Explore landing counts. Legacy classes, treatments and salon fields retain their same-day activity signals for older clients. Additive offering_treatments and offering_salon fields count eligible venues with at least one active published service. Offering counts do not promise a free slot today; use /discovery for actual availability.
Full venue profile including brands, locations with rooms, opening hours, amenities, photos, booking mode and branded app configuration. settings.booking.max_bookings_per_day is the daily booking limit (0 = no limit); settings.booking.daily_limit_counts_workshops and daily_limit_counts_course_sessions (default true) say whether workshops and course sessions count toward it and toward pass per-day caps. fresh=1 requests an uncached configuration read for active mobile venue refreshes. Failed configuration reads return 503 rather than a successful empty configuration.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
Query parameters
freshstring
Set to 1 for a private no-store configuration read
GET/api/v1/venues/{slug}/joinBearer token
Check venue join eligibility
Bearer-authenticated, read-only membership check for Consumer apps. Resolves the target from its slug or organization UUID and reports member, can_join, or an unavailable reason without changing account state.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug or organization UUID
POST/api/v1/venues/{slug}/joinBearer token
Join a venue with explicit consent
Bearer-authenticated and Idempotency-Key protected. Requires {consent:true}; creates one active member relationship without changing an existing role, assigns the venue client ID, and emits the canonical audit, analytics, and member.created integration events.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug or organization UUID
Explicit user consent to add this venue to their account.
Request body
{
"consent": true
}
GET/api/v1/venues/{slug}/schedulePublic
Get class schedule
Live class schedule with real-time availability. Filter by date range, location, brand, class type, instructor, or online-only. `class_type.image_url` is the nullable class hero image for native discovery and booking. Each row includes the additive general-policy `requires_workshop_entry` flag and a nullable public `workshop_entry_target`; `is_bookable` retains its capacity/status/time meaning. Each row carries `course_identifiers` (additive; `[]` when none): `{course_id, label, tone, course_name}` pills the venue set on the public course(s) a workshop date is linked to, `tone` one of default|success|warning|danger|info|purple|pink. Each row also carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.1): the class’s effective cancellation window (`window_hours`, `window_closes_at` in the venue timezone, `source` location | brand | venue), the venue-default `late_fee` / `no_show_fee` (`varies_by_pass` when some passes have their own rule), `online_cancellation` and a `policy_summary`, from the same rule as the cancellation preview; null when it cannot be read. Static per class, so safe to cache; the preview stays authoritative at cancel time.
Bearer-authenticated exact class detail for Consumer push deep links. Requires X-Organization-ID for an active member relationship, retains public/member-entitled completed or cancelled classes, and never exposes an unlisted class. `class_type.image_url` is the nullable class hero image. Includes the same additive general-policy `requires_workshop_entry` and nullable public `workshop_entry_target` fields as the venue schedule, plus the additive `course_identifiers` pills; `is_bookable` remains capacity/status/time-only. Also carries `cancellation_terms` (CANCEL-PREVIEW-01 v1.1), the class’s effective cancellation window and venue-default fees by the preview’s rule; null when unavailable.
Active residence price books add residence_commerce with price_book_version, exact prices_minor and enabled countries mapping to currencies. No legal research or provider/reviewer identifiers are public. Invalid configured policy returns an empty unavailable book; an unactivated draft retains ordinary catalog behavior. Display discovery is not a signed purchase quote. All active pass types with pricing tiers, binding commitments, class restrictions, and location availability. Returns an `{ org, pass_types }` envelope (BB-R4): `org` carries `slug`, `name`, `currency`, `timezone`, and `vat_exempt_age_threshold` (for an under-/over-threshold pricing toggle); each `pass_types` entry includes its `slug` for `/buy/{slug}` deep-links. Configurable recurring entries also include `pricing_mode`, billing cadence, the immutable active pricing version, quantity range/step, volume tiers, unlimited option, and change-cycle policy. Every entry carries `registration_fee_u30_amount` / `registration_fee_o30_amount`: the pass's own pre-offer registration fee per age band, priced exactly as the public offers feed prices it (identical when no age split applies). Optional `category`, `location_id`, `brand_id` filters apply to `pass_types`. Each entry's `purchase_channel` (native in-app payment vs web) counts a `pass_type_video_access` live/recording level as a digital grant. Send `X-App-Digital-Content: none` from a store build without in-app digital content: an entry that includes any in-person grant then reads `native`, and a digital-only entry stays `web`. Absent or `full` keeps today's rule (any digital grant means `web`). The shared cache varies on that header; a `none` response is private.
The venue's live and scheduled flash-sale offers for one placement. `surface` is required and is one of `pricing_page` (the venue's own website), `global_app` (the Booking Bible consumer app) or `branded_app` (the venue’s own app, plan-gated). An absent or unknown `surface` returns an empty list rather than an error. Optional `brand_id` keeps only products whose pass type is venue-wide or linked to that brand, and drops a sale left with no eligible product. Each product carries the offer and normal prices for both VAT age bands, plus its canonical pre-waiver under-30 / 30+ registration-fee totals and the effective decimal VAT rate when Danish age pricing applies. A configurable (`pricing_mode: "flexible_quantity"`) product additionally carries a `flexible` block with its immutable published price book, the quantity vocabulary and the normal-price range: its scalar `price_amount` / `normal_price_*_amount` describe the CHEAPEST selection, and a product whose active published pricing version is missing or mismatched is omitted rather than quoted at a fallback price. Anonymous; a Bearer JWT is used only to apply the sale’s `exclude_active_pass_clients` rule to that client. Each offer includes `presentation` with `style` (clean, venue, or magic), `showRemainingCapacity`, and `showTimeToEnd`; absent legacy presentation defaults to clean with both visibility flags false. `capacity` contains `maximum`, `claimed`, and `remaining`; maximum and remaining are null for an unbounded offer. Render capacity and deadline only when the corresponding presentation flag is true. These are display values, not reserved inventory or checkout authority. Pass the offer UUID as `flash_sale_id` when requesting the canonical checkout quote. An optional eligibility decision is a provisional buyer-specific preview, or unresolved for an anonymous viewer; it is never admission or a payment guarantee. Canonical checkout preview, intent creation, and settlement revalidate it. Each product carries `purchase_channel` (native | web) from the same caller-aware rule as venue pricing: a `pass_type_video_access` level counts as digital, and with `X-App-Digital-Content: none` a product with any in-person grant is native while a digital-only product stays web. The response varies on that header.
Returns one usable published offer with the same product, published Flexible pricing, presentation and capacity fields as the offer list. Invalid IDs and unavailable offers return 404. `surface` defaults to public_link, the direct-link placement; pricing_page, global_app and plan-gated branded_app are also accepted. The route does not accept brand_id. A visible offer or remaining-capacity value does not grant purchase eligibility: the authenticated canonical checkout still validates the buyer, selected product, price version and current offer availability. An optional public eligibility decision is provisional, not final admission. Products carry the same caller-aware `purchase_channel` as the list (`X-App-Digital-Content`); the response varies on that header.
Class catalog with descriptions, difficulty levels, durations, and included services.
Parameters, scopes and examples
Required scopes
read:classes
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/instructorsPublic
Get instructors
Instructor profiles with bios, photos, and specialties.
Parameters, scopes and examples
Required scopes
read:instructors
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/locationsPublic
Get locations
Physical locations with rooms, capacity, opening hours, amenities, and Google Maps integration.
Parameters, scopes and examples
Required scopes
read:locations
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/brandsPublic
List brands
Active brands at this venue. Each entry includes identity (name, slug, description), theming (colors, logo, hero), social links, and a class_types_count for quick summary rendering.
Parameters, scopes and examples
Required scopes
read:brands
Path parameters
slugstring · required
Venue URL slug
GET/api/v1/venues/{slug}/brands/{brandSlug}Public
Get brand detail
Full brand record plus class_types tagged to this brand and the pass_types available for it (respecting pass_type_brands restrictions — passes with no brand-junction rows are venue-wide and are included).
Parameters, scopes and examples
Required scopes
read:brands
Path parameters
slugstring · required
Venue URL slug
brandSlugstring · required
Brand slug within the venue
GET/api/v1/geoPublic
Geo prefill for the signup form
Anon utility that reads Vercel's request-geo headers (`x-vercel-ip-country`/`x-vercel-ip-city`) so a client can prefill signup's optional `country`/`city` fields from the caller's own IP before submitting POST /api/v1/auth/signup. `country` is an ISO 3166-1 alpha-2 code; `city` is URI-decoded free text. Either is `null` when the header is absent (e.g. local dev). No DB touch; never cached (per-caller response).
PUBLIC (no auth) balance lookup for a venue gift card, for a storefront "check your balance" widget. Org resolved from {slug}; lookup scoped to that org's cards by the FULL generated code OR the printed physical barcode (same fallback `redeemGiftCard` uses). Returns the minimal `{ code, remaining_amount, currency, status, expires_at }` — never purchaser/recipient PII. Enumeration-hardened: an unknown code, a cross-org code under the wrong slug, and a cancelled card all return the SAME generic 404. IP rate-limited (20/min).
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
codestring · required
Full gift-card code or the printed physical barcode
PUBLIC (no auth) gift-card preview so a BRANDED storefront can show a recipient what they were gifted ("Alex sent you a 3-month membership") before prompting signup/redeem — instead of bouncing them to BB's /gift/redeem/[code] venue portal. Accepts the generated code OR the printed physical barcode. Returns `{ code, gift_type, sender_name, gift_description, pass_name, amount, currency, status, expires_at }` — the sender's display name + a human gift description only, NEVER recipient/purchaser contact info, the personal message, or the redeemer. Enumeration-hardened: unknown code, wrong slug, and a cancelled card all return the SAME generic 404. IP rate-limited (20/min). Redeem itself is member-authenticated (POST /api/v1/gift-cards/redeem).
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
codestring · required
Full gift-card code or the printed physical barcode
Member-authenticated request-a-booking for a service that accepts inquiries (`accepts_inquiries: true` on the public services list). Free-text preferred time, not a real slot — the venue converts it to a real appointment once a time is agreed. `Idempotency-Key` is optional but MUST be a UUID when sent (400 `INVALID_IDEMPOTENCY_KEY` otherwise); it is the database ingest request id. Every 201 and every 503 `INQUIRY_RECONCILE_FAILED` returns an `Idempotency-Key` RESPONSE header (mirrored as `error.details.request_id` on the 503) carrying the identity the inquiry was accepted under — your key when you sent one, the server-generated UUID when you did not. Retry a 503 with that exact value as `Idempotency-Key`: it replays the accepted inquiry (no second row) and re-drives only what is still missing. Retrying without it mints a new identity and files a duplicate inquiry. Rate-limited 10/min. Errors: 503 INQUIRIES_DISABLED (kill switch off), 404 NOT_FOUND (venue), 422 SERVICE_NOT_ACCEPTING_INQUIRIES, 422 VALIDATION_FAILED, 409 IDEMPOTENCY_KEY_REUSE_MISMATCH (same key, different answers — nothing written), 500 SUBMIT_FAILED (`details.reason` passthrough).
Parameters, scopes and examples
Path parameters
slugstring · required
Venue URL slug
Inquiry details
Request body
{
"service_id": "uuid",
"preferred_time": "Tuesdays or Thursdays after 17:00",
"message": "Looking for a 90-minute deep tissue session.",
"contact_name": "Jane Doe",
"contact_email": "jane@example.com",
"contact_phone": "+4520123456"
}
Response example
{
"data": {
"id": "uuid"
},
"error": null
}
GET/api/v1/me/inquiriesBearer token
My booking inquiries
The caller's own booking inquiries across every venue, newest first. Status is mapped to plain language (never the raw form_submissions enum): 'Sent — waiting for the venue', 'The venue replied', or 'Closed'; archived/spam/deleted rows are never returned.
Exchange a Google or Apple identity token for a Booking Bible session. Existing email/password accounts are unchanged. New accounts require a venue (organization_id or organization_slug). Unclaimed imported emails are not auto-linked.
Exchange a signup confirmation token for a session on an explicitly owner-configured partner receiver for a validated organization and brand. Requires signed signup provenance, active tenant membership, password and MFA checks. Never accepts recovery or mobile magic-link credentials. Rate-limited 5/10 min per IP.
Exchange a device-bound mobile token_hash or a 6-digit email OTP for a session. Mobile token hashes require the callback request_id and device-held code_verifier. Rate-limited 5/10 min per IP.
Check whether an email already has an account before signup. Returns a state hint (absent | active | password_never_used | imported_unclaimed | unknown). Rate-limited 20/min per IP. Constant-time floor of 250ms to prevent enumeration.
Create a TOTP challenge for one of the caller's factors. Returns {id, expires_at}; pass the id as challenge_id to /auth/mfa/verify. Repeating mints a fresh challenge (intended resend). Rate-limited 5/min per user.
Verify a 6-digit TOTP code (enrollment confirmation or login challenge). Accepts {type: totp, factor_id, code}, {type: backup_code, code}, or the challenge-bound {challenge_id, code} (factor resolved from the preceding /mfa/challenge). Rate-limited 5/5 min per user.
Generate 10 single-use backup codes for MFA recovery. Previous unused codes are invalidated. Codes are shown once in plaintext — only hashes are stored.
Parameters, scopes and examples
Response example
{
"data": {
"backup_codes": [
"ABCD1234EF",
"..."
],
"warning": "Save these codes securely. They will not be shown again.",
"count": 10
}
}
POST/api/v1/auth/mfa/resetBearer or API key
Admin MFA reset
Admin-initiated MFA reset for a user. Unenrolls all factors and invalidates backup codes. Permission: admin.users.manage.
Exchange refresh token for new access and refresh tokens.
Parameters, scopes and examples
Refresh token
Request body
{
"refresh_token": "xxx"
}
POST/api/v1/auth/logoutBearer token
Log out current session
Revoke the refreshable Supabase session represented by the caller JWT. The access JWT remains valid until its encoded expiry.
Parameters, scopes and examples
Response example
{
"data": {
"ok": true
}
}
POST/api/v1/auth/password/forgotPublic
Forgot password
Send a single-use 6-digit password verification code. Honours venue branding when an org is identified and never reveals whether the email exists. No reset link is generated.
Verify the single-use numeric code and permanently save a new password in one operation. Recovery tokens and reset links are not accepted. When MFA is required or assurance cannot be checked, success returns session:null and sign_in_required:true; mfa_required:true identifies a verified MFA requirement. Sign in normally with the saved password to complete verification.
Authenticated password change that still requires a fresh single-use numeric code. Submit the code and new password together; current-password-only and session-only changes are rejected. A successful save can return session:null with sign_in_required:true (and mfa_required:true when verified); normal sign-in must satisfy configured MFA before a recovery session is released.
NAMASTE-GATES-01 — mint a one-time SSO handoff code (60s TTL, single-use, SHA-256 hashed at rest) bound to an allowlisted destination domain. The destination site exchanges it at /auth/handoff-exchange for a fresh session.
NAMASTE-GATES-01 — consume a one-time handoff code (atomic single-use) and receive a fresh Supabase session for the bound user. Same session shape as /auth/login.
TV-DEVICE-AUTH-01 — RFC 8628-style device authorization (mint side). An input-constrained device (TV) receives a 256-bit device_code (its poll credential) plus a short user_code (shown as XXXX-XXXX + QR). Both are SHA-256 hashed at rest, bound to one 10-minute expiry, single-use.
TV-DEVICE-AUTH-01 — a SIGNED-IN member submits the user_code shown on the TV (normalized: uppercase, dashes/spaces stripped). Binds the pending device code to the caller so the TV poll returns a session. Every failure (unknown / expired / attempts-capped) is the same generic 400 INVALID_CODE; per-code attempts<5 cap.
Parameters, scopes and examples
The short code shown on the TV
Request body
{
"user_code": "ABCD-EFGH"
}
Response example
{
"data": {
"approved": true
}
}
POST/api/v1/auth/device/tokenPublic
Poll TV device code for session
TV-DEVICE-AUTH-01 — the TV polls with its device_code (every `interval` seconds). 400 AUTHORIZATION_PENDING until approved; 400 EXPIRED_TOKEN / 403 ACCESS_DENIED / 400 INVALID_CODE are terminal. On approval the code is consumed atomically (single-use) and a fresh Supabase session is returned — same shape as /auth/login.
List active passes across all venues with usage stats. Each pass carries is_default (the member's chosen default at that venue), usable and unusable_reason (clips_exhausted | expired | paused | past_due | not_eligible | not_found, evaluated on the venue-local today; a renewing membership whose current cycle is spent stays usable; class-specific restrictions are decided at booking time).
Parameters, scopes and examples
Required scopes
read:bookings
GET/api/v1/passes/{id}Bearer token
Pass detail
Single pass detail with usage stats, freeze/binding state, configurable recurring selection/allowance, and the pass-type + venue. Owner-scoped: cross-user reads return 404. FLEX-PARITY-01 — a top-level `allowance` block ({kind, quantity, class_allowance, interval, interval_count, label}) names the purchased tier of a Flexible (configurable-quantity) membership, e.g. "1 class / month"; null for a fixed pass. `pass_type.slug`/`pricing_mode` are additive.
Returns the authenticated member’s frozen selection, current published recurring choices, pricing version, and any pending renewal change. Owner-scoped and available only when member changes are enabled by the venue.
Preview a venue-approved quantity/unlimited change for renewal 1–24, or schedule it with the exact unexpired quote fingerprint. The change takes effect only after the target renewal invoice is paid.
Cancels the authenticated member’s pending future allowance change without altering the current frozen entitlement.
Parameters, scopes and examples
Path parameters
idstring · required
Issued pass UUID
GET/api/v1/me/passes/{id}/addonsBearer token
Get add-ons for my Flexible membership
Returns the active, pending and currently eligible recurring extras for an authenticated member’s active monthly Flexible pass, with server-priced monthly amounts.
Parameters, scopes and examples
Path parameters
idstring · required
Issued pass UUID
POST/api/v1/me/passes/{id}/addonsBearer token
Preview or buy a recurring Flexible add-on
Preview the Stripe-calculated charge through the next membership renewal, then buy with that quote and an Idempotency-Key. The extra starts after a confirmed paid invoice and renews with the membership. Pending or scheduled subscription changes are refused.
Pass-type catalog detail used by checkout — price, duration, benefits, binding tiers, eligible class types, configurable kind, and the immutable published Flexible pass configuration when enabled. Active public items need no authentication; a hidden exhausted-credit target requires member JWT authentication plus its clips_empty_offer_id capability. `purchase_channel` follows the same caller-aware rule as venue pricing: a video-access level counts as a digital grant, and `X-App-Digital-Content: none` makes an entry with any in-person grant `native` while a digital-only entry stays `web`.
Parameters, scopes and examples
Path parameters
idstring · required
Pass-type id
Query parameters
clips_empty_offer_idstring
Opaque clips-empty path UUID. Revalidated against the authenticated member’s exhausted, still-valid source pass before a hidden target is returned.
The existing compatibility policy still rejects direct flash sales for Flexible class packs and time passes; one-time direct disclosure applies to supported fixed products only. Direct Flexible proofs also carry purchase_obligation_fingerprint: preserve it unchanged. The canonical preview reports purchase_obligation availability and all-tender minimum_total_payable. Supported recurring schedules expose intro_through_date and either reachable self-service earliest_cancellation_effective_on or a distinct cancellation {kind:conditional_contractual_minimum, request_method:contact_studio, condition:timely_valid_notice, earliest_possible_end_on}. Staff-managed facts are conditional on timely valid notice, not cancellation confirmation. Render this distinction before acknowledgement. One-time totals come from final canonical pricing and omit cancellation dates. Unsupported shapes report unavailable without guessed facts. These are purchase-time disclosures, not a replacement for existing live cancellation policy. Initiate pass purchase. Returns a PaymentIntent or SetupIntent client_secret (`client_secret_type` identifies which) with the provider-frozen customer, ephemeral key, Connect account, merchant country, and regional revision. Customer credentials are paired and may both be null only for a supported generic-sheet/no-customer result. Optional binding_months must identify a current server-side tier and is priced by the same canonical resolver as checkout preview; unavailable tiers return 422 rather than falling back. Flexible recurring passes require selection_kind=quantity with quantity, or selection_kind=unlimited; Flexible class/time passes require selection_kind=option with option_id. A recurring Flexible pass accepts only a Flash Sale backed by one shared introductory amount, which applies regardless of the chosen allowance. Fixed passes retain promo codes, gift cards, and account credits. Optional flash_sale_id (UUID) selects a direct venue offer and cannot be combined with promo_code. Offer price, linked promotion, dates, buyer eligibility, caps and compatible passes are server-authoritative; unavailable offers return 422 FLASH_SALE_UNAVAILABLE without charging normal price. Flexible purchases must send the complete preview flexible_quote, including its optional flash_sale_id and flash_sale_rule_fingerprint; stale or changed quotes return a typed QUOTE_* error. Reuse the same Idempotency-Key only for the same purchase choices. The pricing response remains canonical: charged_today is payable; optional direct_offer {flash_sale_id, name, normal_amount, offer_amount} is display-only fixed one-time metadata in minor units. Existing requests without flash_sale_id retain generic checkout behavior. When an offer applies, the response also carries hold_expires_at (ISO 8601 UTC instant the reservation lapses, or null when no offer hold applies) and server_time (ISO 8601 UTC, the server clock at response time), always together. Compute the countdown once as hold_expires_at minus server_time and run it locally; never compare hold_expires_at against the device clock. A lapsed hold that can still be retried returns 409 OFFER_HOLD_EXPIRED; a lapsed hold with no way to restart (capacity gone, the per-client limit reached, or a late charge already refunded) returns 409 OFFER_CONTACT_VENUE, which never offers a restart and instead points the buyer to the venue desk. A first attempt with no reservation ever held, against an offer that is already fully booked, returns 409 OFFER_SOLD_OUT with plain copy and no charge attempted — never the raw promo-code refusal reason. A canonical customer eligibility refusal or unresolved decision returns 409 CUSTOMER_ELIGIBILITY with a safe decision in error.details.eligibility; this is final admission for this purchase attempt, unlike a provisional public preview. Show the reason in EN/DA and current options at the trusted venue. This route mints a native in-app Stripe payment, so a pass type whose catalog purchase_channel is web (any digital entitlement: online classes, course, video library, digital materials) is refused before any charge work with 409 PURCHASE_CHANNEL_WEB_ONLY and error.details {purchase_channel: 'web'}; send the buyer to the venue website instead (App Store 3.1.1 / Google Play payments). The channel is the one the catalog serves to the same caller: a `pass_type_video_access` level counts as digital, and a request with `X-App-Digital-Content: none` (store build without in-app digital content) may buy a pass with any in-person grant; digital-only passes stay refused.
Parameters, scopes and examples
Purchase. Optional flash_sale_id: UUID (mutually exclusive with promo_code). Optional flexible_quote: unchanged preview quote; required for Flexible purchases. Direct-offer quote authority includes flash_sale_id and flash_sale_rule_fingerprint. Fixed direct-offer purchases require direct_purchase_quote: the unchanged five-minute signed preview proof {version:1, organization_id, user_id, pass_type_id, flash_sale_id, authority_fingerprint, credit_applied_minor, payable_minor, issued_at, expires_at, fingerprint}. Both tender fields are nonnegative integer minor units; payable_minor equals preview charged_today. Before new payment work, the canonical engine verifies the reviewed price, terms and exact account-credit/cash split. Changed balances cannot silently increase the payment. Direct gift_card_code is rejected unless equivalently quoted; generic gift cards are unchanged. Missing, invalid, expired or changed proof returns QUOTE_REQUIRED, QUOTE_INVALID, QUOTE_EXPIRED or QUOTE_STALE (409 at adapter preflight, 422 at native core); an already committed matching operation retains its frozen result.
MEMBER (JWT) redeem endpoint so a branded storefront can host the whole redeem flow on its own domain. Applies the gift `{ code }` — generated code OR printed physical barcode — to the caller's account: a custom-amount gift credits the balance (`{ type:"credit", amount, newBalance }`); a pass gift creates + activates a pass (`{ type:"pass", passId }`). Atomic SELECT FOR UPDATE claim — two concurrent calls can never both redeem. A logged-out recipient must sign up / log in first (that creates/links the BB member); this endpoint is member-only by design. Org from the caller's active membership (X-Organization-ID header or single membership). Errors: 401 UNAUTHORIZED, 403 NO_ORG / MODULE_DISABLED, 400 VALIDATION_ERROR, 404 INVALID_CODE, 409 ALREADY_REDEEMED / EXPIRED / NOT_AVAILABLE, 500 REDEEM_FAILED.
Parameters, scopes and examples
Gift card code to redeem onto the caller's account
Authenticated buyer-only gift purchases, scoped to the active organization. Historical access remains when new gift sales are disabled. Optional page (positive integer, default 1) returns up to 50 records ordered newest first: {id, amount, currency, status, paid, code, purchased_at, recipient_name, message, expires_at, delivery_method, scheduled_send_at, delivery}. Delivery is a channel-to-status map from durable provider evidence: accepted is not delivered, failed includes a verified bounce. Code is null until webhook-confirmed payment. Recipient email, buyer identity and public bearer tokens are never returned. Private no-store response.
Parameters, scopes and examples
Query parameters
pageinteger
Positive page number, default 1. Each page contains up to 50 purchases.
GET/api/v1/gift-cardsBearer token
My gift cards
Gift cards the caller purchased or received (buyer, redeemer, or addressed recipient email). Without scope=account, scoped to the active org and returning an array. With scope=account, returns `{cards, failed_organization_ids}` across current and former venues; each card includes organization_id and organization_name. A branded x-organization-slug narrows the account read to that venue. Cards include `{id, code, initial_amount, balance, currency, status, recipient_email, recipient_name, message, expires_at, created_at}` with status `active|redeemed|expired|void`. Owned history stays readable when new gift-card sales are disabled.
Parameters, scopes and examples
Query parameters
scopestring
Use account for an owner-scoped cross-venue wallet; omit for the legacy active-venue array.
POST/api/v1/gift-cards/purchaseBearer token
Purchase gift card
Buy a gift card (custom amount or a gifted pass) for a recipient. Creates a Stripe one-time PaymentIntent and returns client_secret plus customer_id + ephemeral_key for the Stripe Payment Sheet. Fixed gifts retain their existing contract. A published Flexible pass is available only when is_giftable is not false and flexible_gift_supported is true; flexible_gift_funding_modes currently supports prepaid only. Send preview:true with the published flexible_selection, optional flexible_addons and prepaid duration to receive the canonical unknown-recipient gross amount (this endpoint reports amount in major units), flexible_gift disclosure and an opaque flexible_quote without creating a provider intent or gift card. The explicit purchase must echo that flexible_quote unchanged. The recipient authenticates when redeeming the gift, and the prepaid pass starts then; it does not auto-renew or charge the recipient for the funded period. Gated on the gift_cards module. Idempotency-Key supported.
Parameters, scopes and examples
Gift card purchase. Additive Flexible fields: preview?: boolean; flexible_selection?: {kind:"quantity",quantity:number}|{kind:"unlimited"}|{kind:"option",optionId:string}; flexible_addons?: Array<{key:string,quantity:number}>; flexible_quote?: opaque server-signed preview object echoed exactly. Existing fixed-gift inputs and behavior are unchanged.
Anonymous (or logged-in) native gift-card checkout — phase 1. Mints a Stripe PaymentIntent for a gift card and returns client_secret so the buyer can mount Stripe Elements in a modal. Two gift kinds (exactly one of the two fields): a CUSTOM-AMOUNT gift via `amount` (smallest currency unit, min 5000, max 5000000), or a PASS-BASED gift via `pass_type_id` (GIFT-PASS-API-01 — must be an active, giftable, non-intro pass type of this org; price is server-resolved via calculateGiftPrice, optional `duration_months` 1–120 prepays a recurring membership). Fixed gifts retain their existing contract. A published Flexible pass is available only when is_giftable is not false and flexible_gift_supported is true; flexible_gift_funding_modes currently supports prepaid only. Send preview:true with the published flexible_selection, optional flexible_addons and prepaid duration to receive the canonical unknown-recipient gross amount in minor units, flexible_gift disclosure and an opaque flexible_quote without creating a provider intent or gift card. The explicit purchase must echo that flexible_quote unchanged. The recipient authenticates when redeeming the gift, and the prepaid pass starts then; it does not auto-renew or charge the recipient for the funded period. The gift_cards row is created only on confirm, so an abandoned payment leaves no orphan. Anonymous callers must pass a Turnstile token. VAT is accounted at redemption (multi-purpose voucher) so vat_amount is 0. Gated on the gift_cards module. Rate-limited 10/min.
Parameters, scopes and examples
Gift card checkout. Additive Flexible fields: preview?: boolean; flexible_selection?: {kind:"quantity",quantity:number}|{kind:"unlimited"}|{kind:"option",optionId:string}; flexible_addons?: Array<{key:string,quantity:number}>; flexible_quote?: opaque server-signed preview object echoed exactly. Existing fixed-gift inputs and behavior are unchanged.
The sender-enabled source uses SQL93 durable recipient authority; source readiness is not a deployment or provider-delivery claim. Recipient email/SMS requires positively identified Vercel production runtime; preview, development, missing or mismatched identity is held. The 423 maintenance contract below also applies if the same-schema recipient pause is restored. Native gift-card checkout — phase 2. Finalizes the original PaymentIntent gift (custom-amount or pass-based), canonical accounting and buyer receipt. Temporary recipient maintenance returns 423 GIFT_RECIPIENT_DELIVERY_HELD with data:null,error:{code,message} when immediate delivery is held. Payment/gift may already be durably confirmed: preserve the original intent, show held, never pay again and never claim sent or activated. A scheduled gift may return200 before its scheduled delivery is held;200 is not proof of recipient delivery. Idempotent on the original intent; ordinary success returns the last4 code, masked recipient email, gift_type and separate durable delivery channel statuses when available. Provider accepted is not delivered; a missing status stays unknown. organization_slug is recommended for direct-charge venues. The authenticated recurring SetupIntent branch preserves the original giver/account/card and uses the same held contract without claiming a payment was taken. Its held error.details adds giver_receipt_state (succeeded, busy or manual_reconciliation) and payment_collected:false. 409 GIFT_CONFIRMATION_PROCESSING or GIFT_GIVER_RECONCILIATION_REQUIRED also retain the original SetupIntent; they never authorize a new checkout or ambiguous historical resend. Durable delivery incomplete returns409 GIFT_RECIPIENT_DELIVERY_PENDING or GIFT_RECIPIENT_DELIVERY_REVIEW_REQUIRED, with purchase_preserved:true and retry_same_confirmation in error.details. Only pending permits rechecking the same confirmation; review never authorizes another purchase or automatic resend. Recurring responses additionally preserve giver_receipt_state and payment_collected:false. Accepted channels are not proof of delivered messages, and all original activation requirements must complete before recipient-dependent confirmation succeeds.
{
"data": null,
"error": {
"code": "GIFT_RECIPIENT_DELIVERY_PENDING",
"message": "Your gift is preserved, but its confirmation is not complete. Keep this checkout and check its confirmation again. Do not purchase again.",
"details": {
"purchase_preserved": true,
"retry_same_confirmation": true
}
}
}
GET/api/v1/me/passes/{id}/pauseBearer token
Get pass pause capability and policy
Owner-scoped, read-only pause capability for an issued pass. Resolves the current venue-local date and timezone, earliest allowed start, next billing date and notice deadline, currency, pause policy and termination boundary. can_pause plus reason/reason_code is authoritative for availability. preview_required identifies a subscription whose configured notice or calendar-month policy requires a reviewed token; clients must not calculate policy, billing amounts or date bounds independently.
Preview or commit an owner-scoped pass pause for an inclusive venue-local date range. preview:true performs no mutation and returns the canonical resolved policy, dates and financial cycle schedule. A normal commit retains the existing pause response. An opted-in subscription with preview_required:true requires the exact preview_token from the reviewed preview; a missing token returns 409 with error.details.code PAUSE_PREVIEW_REQUIRED, while changed or expired review authority returns 409 PREVIEW_STALE. Other fixed and legacy pause behavior remains unchanged. The server enforces notice, calendar-duration, annual allowance, binding and billing rules; clients must not calculate credit or charge amounts. Idempotency-Key supported for commit.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Inclusive pause window. Use preview:true without preview_token to review; for a commit with preview_required:true, resend the same dates and exact preview_token.
Confirmed owner-scoped cancellation alias for /terminate. Requires acknowledged=true, accepts reason, preview_token and optional cycle from the termination preview. Enforces current venue, brand, product and purchased rules; provider synchronization fails closed. Idempotency-Key supported.
Move a deferred (pending_activation) membership start to today or an earlier future date: re-anchors Stripe billing, charges the first membership payment, and activates the pass. Owner-scoped. Idempotency-Key supported; rate-limited 5/min. Returns payment_status succeeded | requires_action (confirm with client_secret; the invoice.paid path then activates) | pending. Errors: PASS_NOT_FOUND, FORBIDDEN, ALREADY_STARTED, IN_PROGRESS, INVALID_START_DATE, PAYMENT_FAILED, STRIPE_UNAVAILABLE.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
New start date (must be earlier than the current start)
Owner-scoped read-only cancellation summary from stored pass brand, product, purchased terms and venue rules. Optional cycle selects a server-offered later end date. summary.cycleChoices contains {cycles,effectiveAtIso,effectiveDateVenueLocal}; empty when unavailable. Includes final scheduled payment, commitment refusal and preview_token; confirm with the same cycle and token. Omitted cycle preserves default notice.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Query parameters
cycleinteger
Optional offered billing cycle, 0–12; 0 only for an immediate default
POST/api/v1/me/passes/{id}/terminateBearer token
Terminate a recurring membership
Confirmed owner-scoped membership termination. Enforces venue allow_member_cancel, minimum membership age, binding period, required reason and the configured termination boundary. Stripe synchronization is fail-closed and Idempotency-Key is supported.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Explicit acknowledgement, optional/venue-required reason, and selected preview token/cycle. Omitted cycle retains default notice. A revoked later choice returns CANCELLATION_CHOICE_UNAVAILABLE; changed dates or terms return PREVIEW_STALE.
Return the authenticated member’s venue-scoped self-extension policy and live quote: proposed expiry, price/currency, configured duration, remaining extension allowance, clips, a machine-readable unavailable_reason, and the pass type’s purchase_channel (native | web, the same value the catalog serves to this caller, including the `X-App-Digital-Content` rule). A paid extension with purchase_channel web must be sold on the venue website, not in the app. Requires X-Organization-ID and fails closed on invalid venue configuration.
Revalidates the venue’s live self-extension policy and creates a durable operation before any processor call. Paid responses include PaymentSheet customer/ephemeral-key credentials in the exact frozen Stripe namespace; direct mode returns stripe_account_id. Free responses still return operation_id but do not mutate the pass. A paid extension of a pass type whose purchase_channel is web is refused before any idempotency replay, operation claim or processor call with 409 PURCHASE_CHANNEL_WEB_ONLY and error.details {purchase_channel: 'web'}; the channel follows the catalog's caller-aware `X-App-Digital-Content` rule. Requires X-Organization-ID and Idempotency-Key.
Authoritatively rechecks owner, tenant, venue policy, maximum count, hard end, frozen Stripe provenance and payment status under database locks. Paid success atomically records payment, fee, audit, pass, and operation; explicit post-charge conflicts are idempotently refunded. Nonterminal 202 statuses are finalizing or refund_pending and are safe to retry. The Stripe webhook shares this reconciler. Idempotency-Key is required.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Durable operation reference plus PI reference for paid extensions
Request body
{
"operation_id": "uuid",
"payment_intent_id": "pi_xxx (omit when free)"
}
Settles the outstanding renewal of the caller’s past_due or suspended membership. Optional JSON body { payment_method_id, payment_id }: payment_id is the original local renewal UUID from the outstanding item and prevents collecting a later debt. Legacy bodies without it remain accepted. payment_method_id (v1.2) is a saved card (pm_… or a legacy card_… source) that becomes the subscription’s default and the customer’s default before the open renewal invoice is paid with it, so this and every later renewal charge it. Without a body the card the member set as default is used when it differs from the subscription’s card, else the subscription’s card. Returns payment_id and an additive receipt with operation_id, payment_id, status (settled, failed, pending) and settled. Keep both IDs through all retries and bank outcomes. Paid requires a scoped durable receipt; requires_action with client_secret and stripe_account for 3-D Secure (then call …/pay-renewal/confirm); processing while the processor still works on it (do not pay again). Every refusal carries a machine code and error.details.next_step: 422 PAYMENT_FAILED for a real card decline (next_step update_card, details.decline_code); 422 NO_PAYMENT_METHOD when there is no usable saved card; 422 CARD_NOT_AVAILABLE when the sent card is not saved on this membership’s customer; An unproven closed attempt stays pending; 400 VALIDATION_ERROR for a malformed id; unclassified processor failures return processing and require confirmation of the original payment; legacy 502 RETRY_FAILED is also an uncertain result (next_step null); 422 PAYMENT_NOT_COLLECTABLE or CARD_PAYMENTS_UNAVAILABLE when only the venue can take it (next_step contact_venue); 422 NOTHING_OUTSTANDING (next_step null). Owner scoped, 5/min. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Optional payment_id binds the original local renewal UUID before collection. Optional Idempotency-Key atomically reserves the authenticated actor, owning venue and exact body; mismatches refuse. The durable operation retains its exact original provider body and key; bounded retries may replay only that command. Unknown outcomes remain processing and require original-operation/payment confirmation. Optional (v1.2) payment_method_id: a saved card from GET /api/v1/me/payment-methods (pm_… or a legacy card_… source). Pinned on the subscription and the customer, then charged for this renewal.
Verifies the exact original renewal payment with the processor and its scoped durable receipt. Retain payment_id and the additive receipt.operation_id from the outstanding item/pay response before setup/default/challenge, and send it on every confirmation including SDK cancel/error. An active membership is not payment proof. Without payment_id, legacy confirmation examines the latest scoped payment and accepts only a verified subscription_cycle invoice; purchase invoices and native PI-only rows cannot prove a renewal. Returns settled true only after the exact receipt, otherwise false, plus receipt.status when an operation exists. Only provider-proven terminal decline permits selecting a new card; pending never permits a replacement operation. Owner scoped, 5/min. Optional X-Organization-Slug scopes the call to one venue (branded apps, brand sites); an unknown slug returns nothing and never another venue’s rows.
Parameters, scopes and examples
Path parameters
idstring · required
Pass id
Optional payment_id and operation_id bind the original renewal and durable recovery operation. Return receipt.status settled, failed or pending; unknown outcomes require the same operation. Empty legacy bodies remain accepted conservatively.
Accept a pending pass-share invitation by token. Verifies the caller’s email matches the invite recipient, then grants booking access by appending the caller to `passes.shared_with` (respecting `pass_types.max_sharers`) and converges the share into the `pass_shares` table. Emits `pass.share_accepted`. Member-JWT. Original Idempotency-Key is required. Exact token/body/key replays the immutable accepted or closed receipt; unknown outcomes retain the original request.
Authenticated recipient only. Use the exact original {token} body and Idempotency-Key. Existing acceptance wins closure. No inline notification. Errors never authorize replacement.
Authenticated recipient only. Use the exact original {token} body and Idempotency-Key. Existing acceptance wins closure. No inline notification. Errors never authorize replacement.
Member-JWT. Sets which of my passes a venue uses first for bookings, or clears it with pass_id null ("let the system choose"). The venue is the pass's own venue; organization_id (required when clearing) and the optional X-Organization-Slug scope must agree with it (400 ORGANIZATION_MISMATCH / ORGANIZATION_REQUIRED). Another member's or another venue's pass is 404 PASS_NOT_FOUND; a pass that is not active, past_due, pending_activation or paused is 422 PASS_NOT_SELECTABLE; no active membership is 404 MEMBERSHIP_NOT_FOUND. Audited as member.default_pass_set. Without a default, bookings use passes in good standing first, then clip cards before unlimited passes, then the soonest-expiring, then the fewest clips left. When the default is used up or cannot be used for a class, POST /api/v1/bookings answers 409 DEFAULT_PASS_UNUSABLE.
Published VODs and class replays for the caller's venue. Visibility public + members only; pass_restricted items are accessible via /video-catalog/:id once the pass check passes. Signed Mux playback URLs valid for 2 hours. A request with `X-App-Digital-Content: none` is refused with 403 IN_APP_DIGITAL_CONTENT_DISABLED before any lookup or signed URL.
Parameters, scopes and examples
Query parameters
pageinteger
Page numberDefault: 1
limitinteger
Items per page (max 50)Default: 20
categorystring
Filter by category (class_recording | tutorial | workshop)
Single video detail with signed playback URL. Pass_restricted videos require a qualifying active pass (returns 403 PASS_REQUIRED otherwise). A request with `X-App-Digital-Content: none` is refused with 403 IN_APP_DIGITAL_CONTENT_DISABLED before any lookup or signed URL.
The caller's required/pending intake forms for their active org. Each entry is annotated with whether the member already submitted (the pre-booking form gate's source of truth). Returns [] when the `forms` module is disabled.
Parameters, scopes and examples
Response example
{
"data": [
{
"id": "uuid",
"slug": "new-client-intake",
"name": "New Client Intake",
"description": "Tell us about your practice and any injuries.",
"required": true,
"submitted": false,
"submission_id": null,
"submitted_at": null
}
]
}
GET/api/v1/forms/{id}Bearer token
Get form schema
Render schema (fields, steps, submit label) plus the venue's configured `legal_basis` (`consent` | `contract` | `legitimate_interest` | `legal_obligation`) for a single published form. Use `legal_basis` to render the matching privacy notice and, for a `consent` form, to present its required consent checkbox as the gate it is — a consent-basis submission is refused unless that box was ticked. Scoped to the active org — forms in other orgs return 404.
Parameters, scopes and examples
Path parameters
idstring · required
Form UUID
Response example
{
"data": {
"id": "uuid",
"slug": "new-client-intake",
"name": "New Client Intake",
"description": "Tell us about your practice and any injuries.",
"required": true,
"legal_basis": "consent",
"schema": {
"version": 1,
"fields": [],
"steps": null,
"submit_label": "Submit"
},
"thank_you": {}
}
}
POST/api/v1/forms/{id}/submitBearer token
Submit a form
Submit `{ answers }` for a published form. Validates required fields + types, persists a submission stamped with the caller, and routes it into the unified inbox. `Idempotency-Key` is optional but MUST be a UUID when sent (400 `INVALID_IDEMPOTENCY_KEY` otherwise) — it is both the HTTP replay token and the database ingest request id. The same key with the same answers replays the original response; the same key with different answers writes nothing and returns 409 `IDEMPOTENCY_KEY_REUSE_MISMATCH`, so mint a new key whenever the answers change. Every 201 and every 503 `SUBMIT_RECONCILE_FAILED` returns an `Idempotency-Key` RESPONSE header (mirrored as `error.details.request_id` on the 503) carrying the identity the submission was accepted under — your key when you sent one, the server-generated UUID when you did not. Retry a 503 with that exact value as `Idempotency-Key`: it replays the accepted submission and re-drives only the missing delivery. Retrying without it mints a new identity and files a duplicate. 422 with `details.missing[]` on required-field failures; 422 `CONSENT_REQUIRED` when a consent-basis form was sent without its consent box ticked; 409 `FORM_CONSENT_MISCONFIGURED` when the form itself cannot lawfully collect.
Returns the authenticated member’s appointments with the exact updated_at concurrency token required for cancellation and the owning venue’s id, name, slug and timezone for local date/time presentation, including retained history after membership ends. Supports upcoming/past direction, status, venue narrowing and cursor pagination.
Parameters, scopes and examples
Query parameters
directionstring
upcoming | pastDefault: upcoming
statusstring
Appointment status
organization_idstring
Optional venue UUID narrowing
POST/api/v1/appointmentsBearer token
Book my appointment
Creates a free, pass-covered, or pay-at-venue member appointment. Retries recover the matching durable operation before current availability, named selection or payment policy; keep the same Idempotency-Key and booking details. Recovery is scoped to the authenticated actor and resolved organization, never a user-only HTTP cache. New Any requests retain unnamed intent in their operation hash while storing the concrete assignment. A legacy concrete-only hash cannot establish earlier Any intent and returns a conflict rather than guessing. Fresh named choices require active assignment/membership, selectable_for_named_booking and client_picks policy; disabled names return 409 NAMED_PROVIDER_NOT_SELECTABLE. Send provider_id=any for automatic assignment without removing hidden staff capacity. Explicit room_id is separate from provider_id. Paid-at-booking appointments use the checkout endpoints below. When the venue mode is client_choice, omitted payment_choice defaults to online; venue is allowed only when the canonical quote permits it. X-Organization-ID and a stable Idempotency-Key are required; client communication follows the locked member-transactional policy rather than staff-selectable channels.
GET/api/v1/appointments/{id}Bearer token
Get my appointment
Returns one appointment owned by the authenticated member, including its updated_at concurrency token, rescheduled_to_id replacement pointer, visit_id for a composed visit, and the owning venue’s id, name, slug and timezone for local date/time presentation, including retained history after membership ends. Pending or discarded visit legs are hidden. Open the whole-visit endpoint when visit_id is present. The optional organization_id query narrows the owned result to one venue, including retained history after an offering is removed. Follow replacement pointers using the same venue scope; each target is independently authorized.
Parameters, scopes and examples
Path parameters
idstring · required
Appointment UUID
Query parameters
organization_idstring
Optional venue UUID narrowing; never grants access to another member’s appointment
DELETE/api/v1/appointments/{id}Bearer token
Cancel my appointment
Atomically cancels one owned current appointment. Clients must send the exact rendered updated_at token; a missing token returns 400 VALIDATION_ERROR and a stale token returns 409 STALE_TARGET. Refresh the displayed appointment before retrying. Configured venue self-service cutoffs return SELF_SERVICE_CUTOFF when the remaining time is strictly less than the cutoff; exactly the cutoff remains eligible. Paid/deposit appointments return REFUND_REQUIRED and package legs return PACKAGE_REQUIRES_STAFF until the venue handles the whole-visit/refund workflow. Composed-visit legs return GROUPED_VISIT_REQUIRES_VISIT_ACTION; use the whole-visit routes. X-Organization-ID and a stable Idempotency-Key are required.
Read-only preview of the consequence of cancelling one owned appointment right now. The window and fee come from the service row (services.cancellation_window_hours / cancellation_fee_amount, defaults 24 / 0) — the exact pair the cancel RPC enforces — so the number shown matches the number charged. An appointment with a paid deposit or a linked payment is blocked with blocked_reason "refund_required" rather than previewing a self-service refund; a terminal appointment is blocked "not_cancellable". A Combo Package leg is blocked "package_requires_staff", with package_linked=true, because its payment and change workflow belong to the whole package. The separate optional venue self_service_cutoff_hours restricts when clients may act, independently of late fees: blocked_reason "self_service_cutoff" means staff must help. Exactly the configured number of hours remains eligible. A null cutoff preserves ordinary behavior unless malformed configuration produces a blocked decision; clients must use can_cancel/blocked_reason as authority. Composed-visit legs return can_cancel=false, blocked_reason "grouped_visit_requires_visit_action", visit_linked=true and visit_id; preview and manage the whole visit instead. Pending and discarded legs return 404. Honours the optional x-organization-slug tenant scope; an appointment outside the resolved scope, or belonging to another client, returns 404.
Returns the authenticated member’s server-authoritative effective service price, deposit, amount due at booking, remaining venue balance, payment timing, and payment_at_booking_mode (venue | online | client_choice). Named choices require active service assignment and venue-membership permission. Any or omitted requests return provider_id=null without changing the concrete internal quote. Keep Any intent in subsequent requests. An any provider request resolves through public availability, including active assignments and membership, venue-local hours, service options, location and occupancy, before pricing. Optional payment_choice=online|venue is honoured only when the venue mode is client_choice; omitted choice defaults to online so older clients keep paying at booking. A configured deposit still requires the deposit online. Requires X-Organization-ID.
Parameters, scopes and examples
Query parameters
service_idstring · required
Service id
provider_idstring
Selectable provider UUID or any; omitted is Any. Unnamed responses return provider_id=null
start_timestring · required
ISO appointment start
location_idstring
Optional location UUID; required for a location-restricted service. Must match availability and checkout.
variant_idstring
Optional service option UUID; the same option must be used for availability and checkout.
pass_idstring
Optional owned pass UUID; only validated service coverage affects the quote.
payment_choicestring
Optional online | venue. Omitted = pay now when the venue lets the client decide.
requested_currencystring
Optional uppercase ISO currency for an enabled home-country service/option book. Requires an uncovered service and named selectable professional (facilities use Any). Returns selected_currency_quote for exact acceptance. Service country enablement remains closed pending cross-surface qualification.
Claims a durable, tenant-bound operation before creating an account-pinned Stripe PaymentIntent. Accepts a selectable provider UUID or any. Fresh operations check public selection before claim/payment; existing operations keep the frozen provider after admin settings change. Returns PaymentSheet credentials and the frozen operation quote, not current venue policy. New unnamed operations return quote.provider_id=null while retaining the actual provider internally. Retry the same intent/key; changing named/any intent or an explicitly supplied payment choice conflicts. Older operations without snapshot metadata remain resumable. X-Organization-ID and a stable Idempotency-Key are required.
Parameters, scopes and examples
Exact live slot and provider selection. provider_id may be a concrete UUID or "any"; the server freezes one provider and its effective price before payment. Optional payment_choice online|venue follows shared quote policy. A choice requiring no online payment returns APPOINTMENT_PAYMENT_NOT_REQUIRED; use unpaid create after review. Optional selected_currency is {currency, acceptedQuote}, where acceptedQuote is the exact selected_currency_quote returned by review. Retain the complete original body and key on every retry. Home-country only; enabled owned service/option books and migration-backed claim required. Finer-than-two-decimal currencies, professional custom prices, offers, passes, bundles and composed visits refuse. Activation remains closed pending all consumer qualification.
Retrieves the exact account-scoped PaymentIntent, requires processor status succeeded, creates the appointment idempotently, and atomically links payment/accounting. Legacy operations recheck live policy; accepted selected-currency operations use their frozen original price/tax while canonical scheduling and eligibility remain authoritative. Slot conflicts are compensated with the original idempotent refund; 202 finalizing states retain the original key. The Stripe webhook uses the same reconciler.
Read public appointment times with named or Any intent
The slug resolves the authoritative venue. A named provider must be an active assigned venue member allowed by the service policy and named-booking flag. Any or omitted provider keeps all operational capacity and returns one deterministically priced slot per instant with provider_id=any, provider_name empty and provider_selection_intent=any. Named slots retain their permitted UUID/name and provider_selection_intent=named. Room-only slots remain opt-in and use provider_id=null, resource_kind=room and a real room_id, never an aliased provider. Failed reads return 503, denied names 409; responses are private/no-store. Staff fulfillment and existing booking history use separate authorized operational reads.
Parameters, scopes and examples
Path parameters
slugstring · required
Public venue slug
Query parameters
service_idstring · required
Active service UUID in this venue
datestring · required
Venue-local calendar date YYYY-MM-DD
provider_idstring
Selectable professional UUID or any; omitted means Any
Read a venue service and its named booking choices
Public, rate-limited service detail. The venue slug resolves the owning organization; active service and provider assignments are scoped to that venue. providers contains only active members selectable_for_named_booking when the service policy is client_picks (null policy retains that default). any_available and admin_assigns return no named choices. Team-page listing is independent. Provider-read failure returns 503 PROVIDERS_UNAVAILABLE, not an unfiltered list. This endpoint does not determine Any capacity, quote pricing, booking authorization or historical provider identity.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Available only when appointment detail returns paid_self_service_available. Atomically attaches the existing appointment and verified payment to a complete-visit management record; returns data {visit_id}. It does not book or charge again. The original checkout must have frozen explicit owner-configured self-service refund terms. Legacy payments, prepaid pass/series/bundle bookings and unsupported payment evidence retain staff management. The venue cutoff still applies to subsequent cancellation or movement. Checkout quote refund_terms describes the accepted single-appointment terms before payment.
Parameters, scopes and examples
Path parameters
idstring · required
Owned appointment UUID
POST/api/v1/appointments/visits/planBearer token
Plan my complete appointment visit
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send date (YYYY-MM-DD), ordered services, optional provider_id/location_id and, for a new booking, optional provider_selection_intent. With intent "any" (or provider_id "any") candidates return provider_id "any", an empty provider_name and provider_selection_intent "any": times and prices stay real, the professional is assigned by the venue and can include people not offered for named choice. With intent "named" and a provider_id, every selected service must allow that professional by name (409 NAMED_PROVIDER_NOT_SELECTABLE, 503 PROVIDER_SELECTION_UNAVAILABLE); candidates carry that UUID and provider_selection_intent "named". Without intent the earlier concrete candidate contract is unchanged. An owned from_visit_id move never takes intent. One service may probe capability; new bookable candidates require at least two. An owned confirmed visit adopted from an eligible paid single appointment may retain its one service when moving. An owned from_visit_id may seed the original services with services: [] for a move or rebooking. Returns capability, max_services/max_services_per_visit, refund_terms and server-calculated candidates with provider_id, location_id/location_name, start_time/end_time, total_price_amount, amount_due_at_booking, currency and each leg. An existing confirmed visit can be fulfilled while new sales are closed.
POST/api/v1/appointments/visitsBearer token
Book my complete unpaid appointment visit
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Services are an ordered array of 2–8 {service_id, variant_id?}; send provider_id, concrete location_id, start_time and the planner’s expected_total_price_amount, expected_amount_due_at_booking and expected_currency. The server calculates prices, buffers and availability again. New bookings may add provider_selection_intent: "any" (provider_id "any"; the venue assigns the professional the plan priced) or "named" (a professional the venue allows clients to pick by name on every selected service; otherwise 409 NAMED_PROVIDER_NOT_SELECTABLE, 503 PROVIDER_SELECTION_UNAVAILABLE when the check cannot be read). A concrete provider_id without intent keeps the earlier contract and is not treated as a named pick. Intent, when sent, is part of the idempotent request and cannot change on retry. Returns data as the complete visit detail. All legs become visible together. A required deposit/payment returns 402 PAYMENT_REQUIRED; use checkout.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Services are an ordered array of 2–8 {service_id, variant_id?}; send provider_id, concrete location_id, start_time and the planner’s expected_total_price_amount, expected_amount_due_at_booking and expected_currency. The server calculates prices, buffers and availability again. New bookings may add provider_selection_intent: "any" (provider_id "any"; the venue assigns the professional the plan priced) or "named" (a professional the venue allows clients to pick by name on every selected service; otherwise 409 NAMED_PROVIDER_NOT_SELECTABLE, 503 PROVIDER_SELECTION_UNAVAILABLE when the check cannot be read). A concrete provider_id without intent keeps the earlier contract and is not treated as a named pick. Intent, when sent, is part of the idempotent request and cannot change on retry. Freezes the accepted composition, currency, deposit amount, refund terms and payment execution. Returns operation_id, client_secret, customer_id, ephemeral_key, stripe_account_id and quote for PaymentSheet/web confirmation. Credentials are transient. already_booked=true is a successful replay. Payment covers the whole visit; unavailable fulfillment is durably refunded.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send operation_id from checkout. Returns data {status:"booked",visit}. 202 VISIT_FINALIZE_RETRY or VISIT_REFUND_PENDING remains pending; 409 PAYMENT_REQUIRES_ACTION requires returning to payment with the same checkout key. PAYMENT_CANCELLED and VISIT_PAYMENT_REFUNDED are terminal. A webhook and periodic worker reconcile payment independently of the client.
GET/api/v1/appointments/visits/{id}Bearer token
Read my complete appointment visit
Requires the member JWT and verifies client ownership independently of active membership. Legacy requests retain X-Organization-ID narrowing. Optional scope=owned ignores the active header and accepts an explicit organization_id UUID filter; merged identities remain confined to their authorized venue. Returns organization_id and an owning organization {id, slug, timezone} relation, plus the full itinerary, exact updated_at, payment_status, amount_paid_minor, frozen refund_terms, cancellation preview and rescheduled_from_visit_id/rescheduled_to_visit_id. Historical visits remain accessible when a venue removes an offering. Provider credentials and internal payment keys are never returned.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Optional reason. Cancels every leg in one transaction and returns visit fields plus cancellation_result under data. An optional venue self-service cutoff is checked against the first service after row locks; exactly the cutoff remains eligible. Paid/deposit cancellation requires the accepted venue terms to permit self-service. Refund failure remains VISIT_REFUND_PENDING and the worker retries the same refund.
Requires the member JWT and X-Organization-ID. Every visit is scoped to the organization and its owning client. Send a stable UUID Idempotency-Key. Retry the identical request with that key after transport failure or a 202 pending response; never create a second payment attempt to poll the first. Send expected_updated_at exactly as returned by GET; stale snapshots return 409 STALE_TARGET. Send provider_id, location_id, start_time and the planner’s expected_total_price_amount, expected_amount_due_at_booking, expected_currency. The original service/variant order is retained. An overlapping move releases old slots and confirms every new leg in the same transaction; any conflict preserves the complete original visit. A price/deposit change requires a new quote. Returns the replacement visit detail with reciprocal history pointers.
Parameters, scopes and examples
Path parameters
idstring · required
Whole appointment visit UUID
GET/api/v1/me/treatmentsBearer token
Read my treatment history
Requires the member JWT and X-Organization-ID. Returns contract_version 1, records[] (appointment, service name, provider display name, products used with quantity/unit, dates) and patch_tests[] (date, expiry, result, product). Formulas, staff notes, photos, internal ids, costs and compensation are never returned; the platform DSR export remains the portability path.
Atomically resolves all selected resources within the API-key venue, updates independent class/workshop allowance buckets, and reconciles active participants.
Applies or resets class/workshop limits and validity windows for one or many participants in the same tenant-scoped course cohort.
Parameters, scopes and examples
Required scopes
write:courses
Path parameters
courseIdstring · required
Course cohort UUID
POST/api/v1/courses/enrollment-statusAPI key
Course enrollment status for a venue site
COURSE-ONLINE-01 — read-only roster status for one course of the API-key venue: participant contact, enrollment/payment status and attendance mode (`in_person` | `online`). Optional `X-Organization-ID` must equal the key venue (403 otherwise). Filters: `attendance_mode`, `enrollment_ids` (1-100). Keyset pagination by enrollment id, 100 per page; pass `meta.next_cursor` back as `cursor` while `meta.has_more` is true. `updated_at` is the last change time (rows untouched since migration 20261020000264 report their latest lifecycle timestamp). Another venue's course is 404.
Member JWT and venue membership required. Retrieves the caller-owned linked PaymentIntent using its frozen account, customer, amount, currency, provider and regional context. No financial write, claim reset, PaymentIntent creation, or idempotency-key change. Optional plan, purchaser_type and attendance_mode must match the frozen enrollment. A successful null response means no checkout exists; read failures never authorize a new purchase.
Add a paid-claim website applicant to a managed course roster
Trusted server-to-server bridge for venue application forms. Resolves the API-key tenant, the pass type's managed course, the applicant client/membership, and an optional localized track name; then creates or annotates an active roster enrollment and books its upcoming course sessions. Self-reported paid_deposit/paid_full values are retained as claims requiring reconciliation and never fabricate or overwrite BookingBible payment ledger state. API-key only (write:members), rate-limited, Idempotency-Key required.
Pay for a course enrollment (early-bird-aware price, or the deposit when required). Requires an Idempotency-Key header and returns a Stripe PaymentIntent client_secret + customer_id + ephemeral_key + stripe_account_id for the Payment Sheet. The enrollment is created `unpaid`; on `payment_intent.succeeded` it flips to paid/deposit_paid and its sessions are booked (deduped on the payment-intent id). Gated on membership + venue legal docs. Member-JWT. COURSE-SUITE — the body additionally accepts optional `plan` (payment-plan id), `purchaser_type` (`individual`|`company`), and `company` details (name/VAT/address) for VAT-by-purchaser + debtor invoicing. A supplied plan must exactly match a currently offered server-side plan; only an omitted property uses legacy/default behavior. The GET `/api/v1/courses/{id}` course detail additionally returns a `staff` array — `[{ role, name, title_label, photo_url, show_on_landing_page }]` — for the landing-page teaching team (COURSE-SUITE-02 multi-trainer). CV3-03 — the GET detail also returns `payment_plans` (`{ plans: [{ id, kind, installment_count? }], collection_method }`, the normalized plan OPTIONS this purchase route accepts as `plan`) and, for an authenticated Bearer caller with an enrollment, `viewer_enrollment` (`{ id, enrollment_status, payment_status, payment_plan, amount_paid, total_amount, balance, installments: [{ installment_number, amount, due_date, status }] }`; the response is always `Cache-Control: private, no-store`). COURSE-O30-01 — in a Danish age-split venue a course with a 30+ price is charged by the buyer age band: under 30 (date of birth + complete VAT evidence, 0 % VAT) pays `price`, everyone else (including a missing DOB/evidence or a company) pays the 30+ `price_o30_override` with standard VAT inside it; the band is chosen before early bird, proration, offers and the deposit/instalment split. The GET detail adds `age_band_prices` (`{ under30, over30, earlyBirdApplied }` or null) and, for a signed-in caller, `viewer_age_band_status` (`under_30`|`over_30`|`missing_dob`|`evidence_required`). New error: 503 `AGE_PRICING_UNAVAILABLE` when the band cannot be verified. COURSE-ONLINE-01 — optional body `attendance_mode` (`in_person` default | `online` on a hybrid course with an online price; own price book and online VAT category; an online seat never grants a studio place); 422 `ATTENDANCE_MODE_UNAVAILABLE`, 409 `ATTENDANCE_MODE_CONFLICT`, online seats full → 409 `COURSE_FULL`; the response echoes `attendance_mode`. The GET detail adds `attendance_options: [{ mode, available, price, early_bird_price, currency, age_band_prices, viewer_price?, spots_left, vat_rate }]` and `viewer_enrollment.attendance_mode`. `/courses/{id}/sessions` also carries additive per-session `course_identifiers` (`{course_id, label, tone, course_name}`, public courses only; `[]` when none), so a workshop page can tell which programme (for example 8W or 18W) each interleaved date belongs to; the venue course list adds `kind` (`course`|`workshop`) to each item. GET detail and `/courses/{id}/sessions` also add `online_replay: { enabled, until, open }` (replays for online participants, enforced by BB). COURSE-ONLINE-O30-01 — online group instruction follows the same Danish under-30 / 30+ rule: a hybrid course or workshop with an online 30+ price (`online_price_o30_override`, optional `online_early_bird_price_o30_override`) charges an online seat / online drop-in (`POST /api/v1/workshops/{id}/occurrences/{instanceId}/purchase` with `attendance_type: online`) by the buyer band — under 30 (DOB + VAT evidence, 0 % VAT) pays `online_price`, everyone else pays the 30+ online price with the online VAT category rate inside it; without an online 30+ price the online seat keeps its single online price for every age. Additive DTO fields: `attendance_options[mode=online].age_band_prices` (`{ under30, over30, earlyBirdApplied }` for the online book, else null) and a band-aware `viewer_price`; `viewer_age_band_status` is also returned when only the online book is split; `/courses/{id}/sessions` `workshop.prices` adds `physical_age_band_prices` and `online_age_band_prices` (`{ under30, over30 }` or null). Top-level `age_band_prices` stays the in-studio book. See docs/api/NAMASTE_SITES_API.md section 17.
Receives Apple-signed App Store Server Notifications V2 (Production and Sandbox URL). Both the outer notification and nested transaction signatures are verified (x5c chain to Apple’s roots, bundle id, app id, environment). Each notificationUUID is stored and applied once: renewals extend the pass and record an App Store sale, expiry, refund and revoke end it, a reversed refund restores it. Refunds become venue refunds. Sandbox never records revenue. Returns 500 on a transient failure so Apple retries.
Parameters, scopes and examples
App Store Server Notifications V2 signed payload
Request body
{
"signedPayload": "<Apple signed notification JWS>"
}
Calendar1 documented operation
GET/api/v1/calendarBearer or API key
Unified calendar feed
List every event on the unified calendar (classes, appointments, private events, streams, blocked time, instructor unavailability, blackouts, room rentals, maintenance, staff shifts, open gym) in a date range. JWT (any staff role) returns the org feed; API key with read:calendar returns the same. Filters: room_id, staff_id, location_id, brand_id, sources (comma-separated), only_blocking.
List maintenance slots in a date range. API key with read:maintenance scope. Filters: start, end (ISO datetime), room_id, status, limit (1-200, default 50).
Create a maintenance slot. API key with write:maintenance. Body: maintenance_type (preventive | corrective | inspection | deep_clean | equipment | renovation), title, start_time, end_time, plus optional priority, room_id, equipment_id, blocks_room (default true), assigned_staff_id, vendor_name, vendor_contact, estimated_cost, notes. Idempotency-Key header honored. When blocks_room is true and a room is set, conflicts against classes / appointments / private events / streams / room rentals / other maintenance return 409 with the conflict list. Emits maintenance.scheduled.
Parameters, scopes and examples
Required scopes
write:maintenance
Maintenance creation payload
Request body
{
"maintenance_type": "deep_clean",
"title": "Quarterly studio deep clean",
"start_time": "2026-05-01T20:00:00Z",
"end_time": "2026-05-01T22:00:00Z",
"room_id": "uuid",
"priority": "normal",
"blocks_room": true
}
Staff40 documented operations
GET/api/v1/staff/scheduleBearer token
My teaching schedule
Instructor's classes. Optional scope=own|partner|all; every row includes origin venue metadata and origin.timezone so apps bucket collaboration classes in the owning venue's local day.
GET/api/v1/staff/earningsBearer token
My earnings
Compensation, tips, and commissions broken down by period and class. tips_settled_via_collaboration is additive visibility for gratuities paid on a practitioner statement and is deliberately excluded from tips_received and total.
GET/api/v1/staff/classes/{id}/rosterBearer token
Class roster
View attendee list for a class the instructor is assigned to. Returns class and booking updated_at CAS tokens, venue-local day_state, and historical_capabilities. include_historical_records=true additionally exposes terminal roster rows and requires scheduling.manage_history. Guest rows include guest_host_name when a host is linked. Each attendee also carries the additive notification_availability block ({email|sms|push: {available, reason_code, reason}}) so a staff notify picker can enable a channel and state the precise reason an unusable channel is disabled; it is null for a guest booking or a degraded read, and a null block leaves every channel disabled (fail-closed, never an unintended send). The same block additively carries booking_removal and waitlist_promote ({clients: {email|sms|push: {available, reason_code, reason, recipient_id, reachable, total, blocked, reach_label}}}). Each attendee carries online_attendance: null for in-studio bookings; for streaming bookings { state: watching | attended | not_yet | null, playback_state, first_played_at, last_heartbeat_at }, the same server-computed block as the admin check-in roster.
Check one attendee into a class the caller is assigned to. Delegates to the same check-in core as the admin check-in route. Requires staff_portal.roster.view plus booking.checkin (any class in the venue) or staff_portal.check_in.own_classes (assigned or substitute instructor only). Client notification is default-silent: only an explicit non-empty notify.channels selection delivers, and it requires notifications.send plus a per-channel availability preflight before the mutation. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED — use the historical-corrections route. A streaming (online) booking is attended automatically when its stream plays: it is refused with 409 ONLINE_ATTENDANCE_AUTO unless the body sets mark_attended_override: true (the audited "Mark attended" override). Idempotency-Key is honored.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
bookingIdstring · required
Class booking ID
Optional canonical client-notify selection (omit to stay silent); mark_attended_override for a streaming booker
Mark one attendee of an assigned class a no-show, applying the venue no-show consequence. Delegates to the same no-show core as the admin route. Requires staff_portal.roster.view plus bookings.mark_no_show or staff_portal.check_in.own_classes; an explicit user denial of bookings.mark_no_show vetoes the own-class alternative. Default-silent client notification with notifications.send authorization and a per-channel preflight before the fee-producing write. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED. Idempotency-Key is honored so a retry replays instead of charging twice.
Current status, outstanding no-show fee, venue fee amount, clip consumption, currency and per-channel notification availability for one attendee of an assigned class. Also returns booking_updated_at, can_check_in_after_cutoff, ended_class_confirmation_required, credit_applicable and no_show_forfeits_clip. Requires staff_portal.roster.view and class assignment. Read-only; a degraded availability read returns notification_availability: null rather than failing the correction sheet.
Move one attendee of an assigned class to checked_in, no_show, confirmed (undo) or removed. Delegates to the same attendance-override core as the admin route and parses the same field set. Requires staff_portal.roster.view plus the target-specific grant: booking.checkin or staff_portal.check_in.own_classes for checked_in/confirmed, bookings.mark_no_show or staff_portal.check_in.own_classes for no_show, booking.cancel_member or staff_portal.cancel.own_classes for removed. Explicit user denials of the primary action veto own-class alternatives; org-wide check-in actors must hold the specific no-show/removal grant. refund_fee is refused with REFUND_REVIEW_REQUIRED (fee refunds go through the reviewed refund path). Default-silent client notification with notifications.send authorization and a per-channel preflight before the correction. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED. UUID Idempotency-Key REQUIRED, expected_updated_at and ended_class_confirmed are part of the reviewed body; changed retry intent returns 409 IDEMPOTENCY_KEY_REUSE_MISMATCH. Success preserves operationId, bookingUpdatedAt, creditDelta, seatDelta and effectsStatus; pending/held effects do not undo the correction.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
bookingIdstring · required
Class booking ID
Target status plus the canonical client-notify selection
Apply check_in, no_show or undo to up to 200 selected attendees of an assigned class, one per-booking core call each. Requires staff_portal.roster.view plus the action grant (booking.checkin or staff_portal.check_in.own_classes; bookings.mark_no_show or staff_portal.check_in.own_classes for no_show). Explicit user denials of the primary action veto own-class alternatives. Results are truthful per booking: succeeded lists only bookings whose write returned success and failed[] carries each id with its own reason, including a booking whose selected notification channel is unavailable (that booking is not mutated). A selection containing a booking outside this class rejects the whole batch with BOOKING_SCOPE_MISMATCH before anything is attempted. Default-silent client notification with notifications.send authorization. Explicit successful check_in choices dispatch attendance_corrected from the booking-owning venue; failed, empty or legacy-only requests never dispatch. A past venue day is refused with HISTORICAL_CORRECTION_REQUIRED. UUID Idempotency-Key REQUIRED. Check-in skips streaming (online) bookings, whose attendance is recorded on stream playback, and lists them in an additive skipped array ({ booking_id, code: ONLINE_ATTENDANCE_AUTO, reason }) instead of failing them.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
Action, roster booking ids, and the canonical client-notify selection
Send one email or SMS to an explicit subset of an assigned class's roster through the canonical consent/suppression-aware bulk senders. Requires staff_portal.roster.view plus members.contact or staff_portal.contact.own_classes, and the can_view_client_contact_info membership toggle. Submitted booking_ids are intersected server-side with this venue's contactable roster for this class; stale, cancelled and foreign ids are dropped and counted in skipped. Caller-supplied contact data is never accepted. Subject is required for email. Empty intersection returns 422 NO_RECIPIENTS. Idempotency-Key is honored so a retry cannot fan out twice.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
Channel, roster booking ids, and message
Request body
{
"channel": "email",
"booking_ids": [
"00000000-0000-4000-8000-0000000000b1"
],
"subject": "Class update",
"message": "Hi {{first_name}} — here is an update about your class."
}
Staff JWT, staff_portal.roster.view, scheduling.manage_history and ordinary correction permissions. Enforces assigned-instructor and tenant/class/booking binding. Query operation plus status (omit for invalidate). Returns the same signed data.preview contract as the admin booking review; no attendance or financial mutation.
Assigned staff historical correction through the canonical admin class-booking engine, with additive roster/history/ordinary permissions. Idempotency-Key must be a UUID. expected_class_updated_at and expected_updated_at are compare-and-set tokens. Existing-booking reasons and legacy REWRITE are optional. GET review_token is required for explicit fee refund/waive, consumed clip return or client/instructor Email/SMS/Push selections; finances and notifications are default-silent. Extra billing/pass/send permissions are checked before mutation. Shared notification_batch_id defers instructor delivery to the canonical admin batch-finalization endpoint and produces one summary per instructor/channel. Returns exact CAS and separate financial/delivery outcomes; failed or ambiguous sends remain held.
Parameters, scopes and examples
Path parameters
idstring · required
Class instance ID
bookingIdstring · required
Class booking ID
Bounded historical roster correction with UUID Idempotency-Key
Provider-scoped atomic correction for a past class. The active provider must be assigned to the existing class; retrocreate must assign that provider directly, and assignment corrections must retain them. Assignment-only corrections preserve linked operational and financial records; other linked-class corrections require specialist review. Requires schedule.view_own, scheduling.manage_history, scheduling.manage, UUID Idempotency-Key, CAS evidence for existing rows, past effective_at, and typed REWRITE. Existing tenant, location and instructor erasure guards remain enforced. Notifications are always silent.
Mark or unmark a participant present for a course session ({user_id, present}). Idempotent; writes the same attendance store the web roster uses. Supports Idempotency-Key.
Send an email or SMS to course participants (audiences: enrolled, waitlisted, all, by track, by payment status, hand-picked). Requires course-manage scope; rate-limited; supports Idempotency-Key (retries never double-send).
Parameters, scopes and examples
Path parameters
idstring · required
Course ID
Message
Request body
{
"channel": "email",
"subject": "Bring a mat tomorrow",
"message": "Hi everyone — please bring your own mat to tomorrow’s session.",
"audience": {
"kind": "enrolled"
}
}
GET/api/v1/staff/availabilityBearer token
List my unavailable dates
Calling staff member's current and future unavailable dates for the selected venue. Permission: staff_portal.availability.
POST/api/v1/staff/availabilityBearer token
Set availability
Add or update unavailable dates for the calling staff member. Permission: staff_portal.availability.
Strict partial update of a current/future window using the exact updated_at token returned by GET. Caller must own the window; another instructor requires staff.edit. A stale token returns 409 STALE_TARGET. Existing or target ranges touching venue-local history fail closed until the dedicated executor is installed.
Parameters, scopes and examples
Path parameters
idstring · required
Window ID
Concurrency token plus one or more changed window fields
Sets is_active=false on a current/future window using the exact updated_at token returned by GET, after tenant and owner-or-staff.edit authorization. A stale token returns 409 STALE_TARGET. Historical ranges fail closed until the dedicated executor is installed.
Atomic, immutable-ledger correction for a past recurring availability window owned by the active staff member. Requires availability.manage_history, staff_portal.availability, UUID Idempotency-Key, expected_updated_at plus expected_is_active for existing rows, a past effective_at, and typed REWRITE. Notifications are always silent.
Parameters, scopes and examples
Path parameters
idstring · required
Availability window ID
Self-owned historical availability correction
Request body
{
"operation": "availability_window.invalidate",
"expected_updated_at": "2026-08-20T09:00:00.000Z",
"expected_is_active": true,
"history_reason": "Approved rota confirms that this window did not apply",
"history_confirmation_token": "REWRITE",
"effective_at": "2026-08-10T10:00:00.000Z",
"intent": {}
}
GET/api/v1/staff/substitute-poolBearer token
Read substitute-pool opt-in
Returns { enabled, updated_at } for the calling user's active org.
PUT/api/v1/staff/substitute-poolBearer token
Toggle substitute-pool opt-in
Set whether the calling user is available to be auto-suggested as a substitute. Body: { enabled: boolean }. Emits substitute_pool.opt_in_changed.
Parameters, scopes and examples
Opt-in state
Request body
{
"enabled": true
}
GET/api/v1/staff/appointmentsBearer token
My appointments
Cursor-paginated list of the calling provider's appointments, including venue currency and the same client name and 80-character provider-note preview shown in the web staff list. Contact details are not exposed. Query params: cursor (opaque next_cursor; legacy ISO timestamps are temporarily accepted), limit (1..100, default 25), status (one of the appointment status strings), direction (upcoming|past, default upcoming).
Parameters, scopes and examples
Query parameters
cursorstring
Opaque next_cursor returned by the previous page
limitnumber
Page size (1..100)Default: 25
statusstring
Optional status filter
directionstring
upcoming | pastDefault: upcoming
GET/api/v1/staff/appointments/{id}Bearer token
My assigned appointment detail
Provider-scoped detail with updated_at CAS evidence, server-authoritative historical review flags, privacy-gated client contact fields, and exact per-channel notification availability. The route always binds provider_id to the caller.
PATCH/api/v1/staff/appointments/{id}Bearer token
Act on my assigned appointment
Provider-scoped check_in, start, complete, no_show, cancel, or reschedule. Requires the action-specific appointments.*_own permission, exact expected_updated_at, and Idempotency-Key. Client notifications are default-silent and require explicit notification_channels plus notifications.send and server preflight. Past/terminal mutations fail closed until the appointment historical executor is installed.
Parameters, scopes and examples
Exact provider lifecycle intent and caller-rendered concurrency snapshot
Provider-scoped form of the dedicated atomic appointment-history command. Requires staff_portal.appointments, appointments.manage_history, the ordinary operation permission, a UUID Idempotency-Key, REWRITE attestation, and exact expected_updated_at plus expected_status CAS for existing rows. The existing appointment and any retrocreate or assignment target must remain assigned to the active provider. Corrections are always silent. Returns 503 HISTORICAL_EXECUTOR_UNAVAILABLE without table-call fallback until correct_appointment_historical is installed.
Parameters, scopes and examples
Required scopes
appointments.manage_history
Path parameters
idstring · required
Appointment UUID
The same closed appointment correction body as the admin route
Calling staff member's own non-instructor shifts (reception, cleaning, manager, front desk) in a date range. Use ?from=&to= ISO datetimes; defaults to next 14 days.
Parameters, scopes and examples
Query parameters
fromstring
Start ISO datetimeDefault: now
tostring
End ISO datetimeDefault: +14 days
POST/api/v1/staff/shifts/clock-inBearer token
Clock in
Clock in to an own staff shift. Allowed from 15 min before scheduled start through 30 min after. Sets status to in_progress and stamps clock_in_at. Emits shift.clock_in.
Parameters, scopes and examples
Shift to clock in to
Request body
{
"shift_id": "uuid"
}
POST/api/v1/staff/shiftsAPI key
Create a staff shift
Create a non-instructor staff shift. API-key only (write:staff). Body: start_time, end_time, optional staff_id, shift_type (regular | overtime | on_call | training | meeting), break_minutes, role_required, location_id, hourly_rate, notes. Idempotency-Key header honored. Emits shift.created (and shift.assigned if a staff_id is set).
Soft-cancel a shift (sets status=cancelled, preserves audit/payroll references). JWT (admin/manager) or API key with write:staff. Use ?reason= to attach a cancellation reason to the audit row.
Parameters, scopes and examples
Required scopes
write:staff
Path parameters
idstring · required
Shift UUID
Query parameters
reasonstring
Cancellation reason (free text)
POST/api/v1/staff/clockBearer token
Clock in or out (unified)
Unified clock-in/out endpoint. JWT only — resolves the staff member from the session token. Body: { action: "in" | "out", shift_id }. On clock-out the response includes actual_hours and total_pay. Emits shift.clock_in or shift.clock_out.
Parameters, scopes and examples
Clock action
Request body
{
"action": "in",
"shift_id": "uuid"
}
POST/api/v1/staff/shifts/clock-outBearer token
Clock out
Clock out of an in-progress staff shift. Computes actual_hours, actual_break_minutes, and total_pay (when hourly_rate is set). Returns warnings for break/EU compliance issues. Emits shift.clock_out.
Parameters, scopes and examples
Shift to clock out of
Request body
{
"shift_id": "uuid"
}
GET/api/v1/staff/time-offBearer token
My time-off requests
Latest 100 own time-off requests across all statuses.
POST/api/v1/staff/time-offBearer token
Request time off
Submit a new time-off request. Always created with status=pending. Manager approval/decline happens via the admin panel. Emits time_off.requested.
Create or update a lead for the venue. Public rate-limited (10 req/min/IP) or API-key authenticated (write:leads). One lead per (organization_id, lower(email)), race-proof: concurrent posts converge on one row, provided fields populate blanks and existing non-null values are preserved (first touch wins, including brand_id). Optional brand_id must be an active brand of the resolved organization (422 BRAND_NOT_FOUND). source is one of exit_intent, landing_page, referral, manual, import, api, website_form, newsletter, embed_form, offer_popup. An optional consent block (marketing_email / marketing_offer_email / marketing_sms / marketing_push, granted: true only, policy_version, mechanism, the exact prompt_text, locale; source_ip / user_agent honoured only from an API-key caller; without an API key only checkbox or button, else 400 CONSENT_MECHANISM_NOT_ALLOWED) is recorded through the canonical consent engine against the lead, with the prompt text kept verbatim, before lead.created fires, and echoed as consent_id; if it cannot be recorded the call answers 500 CONSENT_RECORD_FAILED, the lead is kept and lead.created is not sent. An unauthenticated caller always receives the same shape ({ email, source, accepted, consent_id }) whether or not the email was already a lead; an API-key caller receives the stored row with brand_id and created (true only when this call created the row). A 500 never carries database detail. Fires the lead_captured analytics event and emits a lead.created webhook with the full record (including brand_id) plus an attribution object (utm_*, fbclid, gclid, landing_page, referrer).
Parameters, scopes and examples
Required scopes
write:leads
Lead payload. Org resolves from API key > X-Organization-ID header > subdomain > organization_id.
List leads for the API key's organization. API key only (JWT not permitted). Requires the read:leads scope.
Parameters, scopes and examples
Required scopes
read:leads
Query parameters
searchstring
Search by email, first_name, or last_name
sourcestring
Filter by source (website_form, exit_intent, referral, etc.)
statusstring
Filter by status (new, contacted, converted, unsubscribed)
pageinteger
Page numberDefault: 1
limitinteger
Items per page (max 100)Default: 20
Events5 documented operations
POST/api/v1/eventsPublic
Track an analytics event
Record a server-side analytics event into user_events. Public rate-limited (60 req/min/IP) or API-key authenticated (write:events). For conversion event names (purchase, subscribe, refund, lead_captured) we additionally fire Meta CAPI + GA4 MP when the venue has pixel credentials configured.
Parameters, scopes and examples
Required scopes
write:events
Event payload. UTM + click-id + page URL get merged into event_properties.
Creates or updates the verified member’s RSVP for a scheduled community-event UUID through the shared web/member core. Requires active owning-venue membership, enabled community_events and an upcoming RSVP-only event. Send X-Organization-ID for the event venue and a stable Idempotency-Key; identical retries preserve the request and response. Guest limits and capacity may return waitlist. Returns data.rsvp with the effective status. The canonical SQL operation serializes capacity and commits the RSVP and replay receipt together. REQUEST_MISMATCH is 409; REQUEST_UNCONFIRMED is 503 and requires the original key/body.
Parameters, scopes and examples
Path parameters
idstring · required
Event booking id
Status + optional guest count + notes.
Request body
{
"status": "going",
"guest_count": 1,
"notes": "Bringing my partner"
}
Venue event catalog or scheduled community sessions
Without view, returns the public event-type catalog: active, website-visible types with id, name, slug, description, short_description, category, pricing_model, base_price, per_person_price, min/max_participants, default_duration_minutes, image_url, location_type, featured, is_active and the purchase_channel routing fields. view=community_sessions returns upcoming scheduled occurrences of active, website-visible community types when the venue module is enabled. Session id and slug are the booking UUID; organization_id, organization_slug and timezone identify the owning venue. Attendance includes guests. No booking contact/payment/admin fields are returned. In both modes purchase_channel follows the caller-aware `X-App-Digital-Content` rule (a `both` location is native only for `none`); the catalog cache varies on that header.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue slug
Query parameters
viewstring
community_sessions for the native scheduled-event DTO
With view=community_sessions, eventSlug must be a scheduled-session UUID owned by this venue, with the same public visibility and module gates as the session list. Returns 404 for unavailable sessions; read failure is an error, not an empty event. Without view, retains the legacy event-type slug detail and pricing tiers.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue slug
eventSlugstring · required
Event-type slug or, in community_sessions mode, scheduled-session UUID
Query parameters
viewstring
community_sessions for the native scheduled-event DTO
Create an open-gym session for a client. Validates an active pass with allow_open_gym=true and the access schedule. JWT users self-check in; API keys must include user_id.
Parameters, scopes and examples
Required scopes
write:bookings
Optional location, source, pass override, and notes.
Returns the latest version of each active required waiver for the authenticated client in the validated X-Organization-ID venue, including the exact markdown body to display and the latest version the client signed. `body_md` is canonical; `body` is the native-app compatibility alias with the same value.
Returns active public/member clubs in the selected venue plus active invite-only memberships. is_member is true only for active membership; membership_status preserves pending/invited/left/removed.
Removes the caller's own club membership. Idempotency-Key supported; leaving a club you are not in is a no-op success. Emits club.member_left (audit + webhook).
Parameters, scopes and examples
Path parameters
idstring · required
Club id
Response example
{
"data": {
"ok": true
},
"error": null
}
DELETE/api/v1/clubs/{id}/membershipBearer token
Leave a club (membership alias)
REST-shaped alias for POST /clubs/{id}/leave used by the mobile clubs contract — identical behavior (Idempotency-Key, no-op success when not a member, club.member_left emit).
Parameters, scopes and examples
Path parameters
idstring · required
Club id
Response example
{
"data": {
"ok": true
},
"error": null
}
GET/api/v1/clubs/{id}/membersBearer token
List active club members
Returns active members for a club in the selected venue. Invite-only rosters require the caller to be an active club member. Display names and avatars respect each member’s public-profile and privacy settings.
Members can suggest leading a new club when the venue has opted into suggestions. The suggestion appears in the admin review queue; this endpoint does not send email.
Parameters, scopes and examples
Club details + why the suggester wants to lead.
Request body
{
"name": "Early Birds",
"category": "running",
"description": "Morning runners group",
"why_lead": "I run every morning and want company"
}
POST/api/v1/clubs/suggestions/{id}/approveBearer or API key
Approve a club suggestion (admin)
Approves a suggestion, creates the club, auto-assigns the suggester as leader, and posts to the feed. The member sees the status in My suggestions; this endpoint does not send email.
Parameters, scopes and examples
Path parameters
idstring · required
Suggestion id
Optional reviewer notes.
Request body
{
"notes": "Looks great — approved."
}
Response example
{
"data": {
"club_id": "uuid"
},
"error": null
}
POST/api/v1/clubs/suggestions/{id}/rejectBearer or API key
Reject a club suggestion (admin)
Rejects the suggestion and saves the reason for the member to read in My suggestions; this endpoint does not send email.
Parameters, scopes and examples
Path parameters
idstring · required
Suggestion id
Rejection reason.
Request body
{
"reason": "Too niche for our community right now."
}
Returns a single chat channel summary (channel row + unread_count + last_message_at) for the authenticated org. Staff role (or chat.read scope) required.
A read with the code in the request body (codes never travel in URLs). The state of a code issued by the issue endpoint: its status, deadline (valid_until / deadline_set_at), redemption time, contact and the caller's metadata, plus in_checkout (a checkout holds it), usable and unavailable_reason (redeemed, voided, expired, cancelled, promotion_inactive or promotion_ended) and the server time. Other codes of the venue are never visible (404 CODE_NOT_FOUND). API key only with read:promo_codes or write:promo_codes, 120 requests/min per key.
Issue the next free code of a batch to one contact
Atomically hands the next unissued code of a ready, non-partner, unexported batch that the venue flagged for personal issuance (batch metadata personal_issuance = true) to one contact and activates it. Optional initial_valid_minutes (1–43200) lets an unopened code expire by itself; lead_id must belong to the same email. The same contact (case-insensitive email) always receives the same code (200, reused: true); the batch size is the budget (409 BATCH_EXHAUSTED). Redemption requires the buyer to use the same email. Caller metadata is stored verbatim and echoed in responses and promo_code.* webhooks. Emits promo_code.issued for a new code. API key only (write:promo_codes), 60 requests/min per key, Idempotency-Key required (409 IDEMPOTENCY_CONFLICT when reused for another contact or batch).
The code travels in the request body. The first call sets valid_until = now + minutes (1–1440, chosen by the caller) unless an earlier deadline already exists, and stamps deadline_set_at; every later call changes nothing and returns the stored values (applied: false), so a page refresh cannot restart the clock. Only codes issued by the issue endpoint are visible (404 CODE_NOT_FOUND otherwise); a used, voided or expired code answers 422 CODE_UNAVAILABLE. Every redemption path enforces valid_until. Emits promo_code.deadline_set when applied. API key only (write:promo_codes), 120 requests/min per key.
Parameters, scopes and examples
Required scopes
write:promo_codes
The issued code (body only) and the deadline length in minutes
Sends the brand-styled promo_code_personal email to the code's own contact (the recipient is never caller-supplied) through the canonical templated pipeline: suppression and marketing unsubscribes honoured, RFC 8058 footer, notifications_log row, sender resolved brand → venue → platform. The caller supplies the https landing URL and may supply its own subject, plain-text message and button label (rendered verbatim, escaped); Booking Bible-authored words are English. With include_code false the email carries the call to action only. When the code belongs to a lead, that lead needs an active marketing_offer_email or marketing_email consent (else 422 CONSENT_REQUIRED); at most 3 emails per code per 24 hours (429 SEND_LIMIT_REACHED); an archived stored brand falls back to the venue sender. Answers 200 with status sent or suppressed, 502 EMAIL_SEND_FAILED when the pipeline failed (retry with the same Idempotency-Key), 422 CODE_UNAVAILABLE for a dead code. Emits promo_code.sent. API key only (write:promo_codes), 30 requests/min per key, Idempotency-Key required.
Parameters, scopes and examples
Required scopes
write:promo_codes
The issued code (body only), the landing URL and optional tenant copy
Request body
{
"code": "ACME-7KQ2M9XW",
"include_code": false,
"cta_url": "https://www.acme-yoga.example/offer/t1",
"locale": "en",
"subject": "Your personal offer",
"message": "Thanks for your interest — here is your code.",
"cta_label": "See your offer"
}
Bulk Scan Session — map a single scanned barcode to a preset target
Hot-path endpoint for the rapid-mapping Bulk Scan Session. Each call maps one scanned barcode. Returns status=created (new mapping), duplicate_in_session (same barcode+target already exists), requires_confirmation (different target, resend with allow_overwrite=true to proceed), or overwritten.
Model Context Protocol server (HTTP transport, JSON-RPC 2.0, protocol 2025-03-26). Authenticate with `X-API-Key`. Methods: `initialize`, `ping`, `resources/list`, `resources/read`, `tools/list`, `tools/call`. Read-only in v1. See `/developers/mcp` for the full guide.
Returns every registered feature module with its resolved enabled/settings/source for the calling venue. Resolution honors the four-tier precedence (tenant override → group lock → venue → group default → plan → default). Mobile Business app uses this for parity with /admin/settings/features.
Parameters, scopes and examples
Response example
{
"data": [
{
"key": "leaderboards",
"label": "Leaderboards",
"description": "Member-facing leaderboards by class type, period, and metric.",
"category": "Community",
"enabled": true,
"source": "plan",
"locked_by_group": false,
"settings": {}
}
]
}
PATCH/api/v1/admin/features/[moduleKey]Bearer or API key
Update a venue-level feature toggle
Flip enabled/settings for a feature module at the venue tier. Idempotency-Key supported. Returns 400 with `Locked by group: <paths>` when the venue tries to flip a group-locked toggle or write to a group-locked dot-path in `settings`. Audit-logged + emits `feature_toggle.changed` webhook.
Parameters, scopes and examples
Required scopes
write:settings
Path parameters
moduleKeystring · required
Module key from feature_modules.key
Partial update — only the fields you want to change.
Browse partner venues available to the member across the BOOKING BIBLE network. Each entry exposes a public summary plus the exact relationship status, active partnership id, and venue-level bookable flag for the organization selected by X-Organization-ID. Member-JWT.
Book a class at a partner venue using a network-eligible pass. Resolves the legal gate against the HOST venue’s documents before booking. Requires a caller-stable `Idempotency-Key` header; exact retries return the original booking and visit. Member-JWT.
Resolve an interrupted network booking before choosing again
Uses the original three-ID booking body and original Idempotency-Key. Atomically closes an uncommitted attempt so a delayed request cannot book it, or recovers the original completed booking. Never cancels an existing booking. Member-JWT only; no mutable catalog or legal preflight can replace the database proof.
Creates a venue-to-venue Network partnership request through the context-free Network mutation core. Bearer JWT requires network.manage; API keys require write:network.
GET/api/v1/network/partnerships/{id}Bearer or API key
Read a venue Network partnership
Reads one venue-to-venue Network partnership after proving the caller belongs to either party. Bearer JWT requires network.view; API keys require read:network.
PATCH/api/v1/network/partnerships/{id}Bearer or API key
Update a venue Network partnership
Updates a venue-to-venue Network partnership through the commercial lifecycle. Bearer JWT requires network.manage. API keys with write:network may change lifecycle status, but agreement negotiation and terms revisions require a verified human JWT administrator and return 403 for API-key callers.
DELETE/api/v1/network/partnerships/{id}Bearer or API key
Terminate a venue Network partnership
Terminates immediately only when binding and notice have both elapsed; otherwise schedules termination through the locked service RPC. Bearer JWT requires network.manage; API keys require write:network.
GET/api/v1/network/partnerships/{id}/visitsBearer or API key
List visits for a venue Network partnership
Lists visit ledger rows for one venue-to-venue Network partnership after proving the caller belongs to either party. Bearer JWT requires network.view; API keys require read:network.
GET/api/v1/network/partnerships/{id}/settlementsBearer or API key
List settlements for a venue Network partnership
Lists network-only settlements for one venue-to-venue Network partnership after proving the caller belongs to either party. Bearer JWT requires network.view; API keys require read:network.
For a venue workspace, lists its professional collaborations with network.view. For a selected individual workspace, an active member may read only rows where the Bearer user is practitioner_user_id and that workspace is the receiving org; person history remains readable when teacher_settlements is off and does not require network.view. Person writes separately require active admin membership. Exact-email invitation lookup is venue-only and requires network.manage. Unknown workspace or database authority fails closed. Each row also carries `status_changed_at`, and venue-direction rows carry `reinvite_option` (`reopen` = ended or declined and Re-invite is possible, `cooldown` = declined under 30 days ago with `reinvite_available_at`, `resend` = pending, Resend invitation; null otherwise). The email lookup returns `existing_collaboration_id` + `existing_collaboration_status` for an open or active collaboration and `ended_collaboration` ({id, status, reopenable, retry_at}) for the latest ended or declined one, so clients offer Re-invite instead of a new invitation.
Creates a pending venue-to-professional collaboration with an explicit venue role and compensation model. Requires network.manage and a non-individual venue workspace. When the venue already has a collaboration with this professional (the venue-professional pair is unique regardless of status) it returns 422 `COLLABORATION_EXISTS` with details {partnership_id, status, reopenable, retry_at}: link an open collaboration, or use POST /reopen for an ended or declined one.
The signed-in practitioner may accept, decline, or terminate their own collaboration when they are an active admin of its receiving individual workspace. These person actions use the canonical lifecycle without the venue network.manage or teacher_settlements gate. A venue with network.manage may set role, pause, resume, or terminate its relationship. A person cannot keep their former venue staff membership on termination.
Ends the relationship through the canonical termination core. The signed-in practitioner may end only their own row while an active admin of its receiving individual workspace; their venue membership is suspended. A venue with network.manage may retain former staff only by explicitly setting keep_membership true.
Parameters, scopes and examples
Optional termination reason and membership handling
Re-invite a professional (reopen an ended collaboration)
COLLAB-REINVITE-01. The selected venue workspace (network.manage, enforced before any read or write) reopens its own ended (terminated) or declined professional collaboration as pending, keeping the existing role and terms, and the professional receives a new request email. A professional workspace gets 403. The body must be empty: terms are edited afterwards on the pending collaboration. Declined collaborations can be reopened 30 days after the decision; pending or active ones refuse. Requires the platform `teacher_settlements` rollout (not the directory `collaboration_requests` switch) and the professional must still own their professional workspace. Consumes one unit of the venue daily collaboration-request budget (10 per rolling day, shared with directory requests). Audits `network.collaboration_reopened`; the email outcome is reported separately as `delivery`.
COLLAB-REINVITE-01. The selected venue workspace (network.manage, enforced first) re-sends the request email for its own still-pending professional collaboration, for example when the first email never arrived. No lifecycle change. One resend per collaboration per 10 minutes; each resend consumes one unit of the venue daily collaboration-request budget. The body must be empty. Requires the `teacher_settlements` rollout. Audits `network.collaboration_invitation_resent` with the delivery outcome.
List the caller’s relationships (bidirectional — both relationships the member created and ones pointing back at them), hydrated with the linked member’s profile. Unlocks family pricing, shared booking, and pass sharing. Member-JWT, org-scoped.
Add a relationship. When `related_email` matches a member in the same venue, the relationship links to their profile and a mirror row is written so both members see it. Member-JWT. Original Idempotency-Key is required. Exact token/body/key replays the immutable accepted or closed receipt; unknown outcomes retain the original request.
Public catalog of bookable private-event types for a venue (active + shown on website). Used by the app inquiry screen. Cached, IP-throttled. `purchase_channel` derives from `location_type` (online/both web, in_studio/offsite native); with `X-App-Digital-Content: none` a `both` type reads `native`, and the cache varies on that header.
Parameters, scopes and examples
Path parameters
slugstring · required
Venue slug
Response example
{
"data": [
{
"id": "uuid",
"name": "Private Group Yoga",
"slug": "private-group-yoga",
"tagline": "Book the studio for your team",
"category": "corporate",
"min_participants": 5,
"max_participants": 30,
"default_duration_minutes": 90,
"pricing_model": "per_person",
"base_price": 0,
"per_person_price": 250,
"currency": "DKK",
"deposit_required": true,
"deposit_amount": 1000
}
],
"error": null
}
Public detail for a single private-event type (active + shown on website). 404 for hidden/draft types. Cached, IP-throttled. `purchase_channel` follows the same caller-aware `X-App-Digital-Content` rule as the list.
Submit a private-event inquiry as the authenticated member. Validates participant count against the event type’s min/max, computes pricing, and inserts a `private_event_bookings` row with `booked_by` set; emits `private_event.inquiry_created`. Member-JWT, org from X-Organization-ID. Idempotency-Key supported.
PROMPT_11 — returns the Stripe `client_secret`, frozen `merchant_country_code`, and `stripe_account_id` (`acct_*` for a direct Connect PI, otherwise null) for the booking’s deposit/full charge so the member can initialize Stripe Elements in the exact payment context. Customer/ephemeral-key credentials are returned only when this member owns the customer frozen by the first payment operation; admin-created or another accepted booker identity receives a safe generic sheet with null customer credentials. Idempotent (reuses the frozen PaymentIntent execution created at confirmation). `{ skipped: true }` when the event type’s payment_mode is `none`. Member-JWT; ownership by contact_email.
Member approves a quoted booking and gets Payment Sheet credentials
The member’s “Approve & pay” CTA: the booker confirms a quote the venue sent (status `quoted`) and receives the same frozen PaymentIntent, `merchant_country_code`, and `stripe_account_id` (`acct_*` only for direct Connect; otherwise null). Customer/ephemeral-key credentials are returned only to the user id that owns the frozen Stripe customer; another accepted booking identity receives a generic sheet with null customer credentials. Member-JWT; ownership by `booked_by` or `contact_email`. Idempotency-Key header REQUIRED — a retry re-enters the repairable confirmation pipeline and returns the same frozen execution (no duplicate PI, account drift, or regional drift). Honors the event type’s payment_mode via the shared helper; `{ skipped: true }` when payment_mode is `none` (invoice path).
PROMPT_11 — public catalog of a venue’s active, publicly-listed private-session types for the embeddable widget (/embed/private-sessions). Cross-origin access is governed by the platform dynamic CORS allowlist (venue custom domains). Cached, IP-throttled.
List products for the authenticated venue. Business-app JWTs require pos.access; API keys require read:products. Archived products are hidden unless include_archived=true.
Parameters, scopes and examples
Required scopes
read:products
Query parameters
include_archivedboolean
Include archived products. Defaults to false.Default: false
POST/api/v1/admin/productsAPI key
Create a venue product
Create through the canonical product mutation contract. Unknown/protected fields are rejected; initial stock creates one movement.
Parameters, scopes and examples
Required scopes
write:products
GET/api/v1/admin/products/{id}Bearer or API key
Get a venue product
Get one product only when it belongs to the authenticated venue. Business-app JWTs require pos.access; API keys require read:products.
Parameters, scopes and examples
Required scopes
read:products
Path parameters
idstring · required
Product UUID
PATCH/api/v1/admin/products/{id}Bearer or API key
Update a venue product
Update mutable catalog fields through the canonical product core. Business-app JWTs require products.manage; API keys require write:products. Products and its tier-gated Point of Sale dependency must be active. stock_quantity and protected fields are rejected.
Parameters, scopes and examples
Required scopes
write:products
Path parameters
idstring · required
Product UUID
DELETE/api/v1/admin/products/{id}API key
Archive a venue product
Archive through canonical catalog semantics (is_active=false plus archived_at). Permanent deletion is separate and guarded.
Parameters, scopes and examples
Required scopes
write:products
Path parameters
idstring · required
Product UUID
GET/api/v1/venues/{slug}/product-packagesPublic
List buyable clip cards
Public catalog of active product passes (clip cards) for a venue. Cached, IP-throttled.
Authenticated read-only exact package review. Body {currency}; returns accepted_quote with tenant/member/item, entitlement, price-book version, currency, gross minor units and included VAT. Only enabled home-country catalog-tax books are supported. Draft books refuse; no payment or provider mutation occurs.
Initiate a product-pass (clip card) purchase. A nonempty Idempotency-Key header is required and defines the durable operation. Returns Customer/ephemeral-key credentials in the same frozen Stripe account as the PaymentIntent, plus `merchant_country_code` and `stripe_account_id` (`acct_*` only for direct Connect; otherwise null). A service-only pre-provider claim binds tenant, catalog, customer, regional, routing, fee, amount, and currency; `product_passes` is granted atomically on `payment_intent.succeeded`. Gated on the `products` module + venue legal docs. Member-JWT. Optional selected body {currency, acceptedQuote} must contain the exact /quote review and is frozen into the original operation; stale book/gross/tax/entitlement or unsupported fields refuse before payment. Empty legacy bodies retain catalog checkout. Selected claims require released SQL; settings activation stays closed.
Buy N paid tickets for a community event (`{id}` is the event booking id). Price = `member_price` + `guest_price` × (ticket_count − 1). Requires an Idempotency-Key header. Returns `operation_state` (payment_required, processing, completed or canceled), durable `operation_status`, and immutable ticket_count/amount_minor/minor_unit_multiplier/member_price_minor/guest_price_minor/amount/currency. Same-key replay checks the durable owned order before cached responses; succeeded payments reconcile through the existing finalizer. Completed/processing/canceled responses have no client_secret and must not open PaymentSheet. Only payment_required returns Customer/ephemeral-key credentials in the same frozen Stripe account as the PaymentIntent, plus `merchant_country_code` and `stripe_account_id` (`acct_*` only for direct Connect; otherwise null). On `payment_intent.succeeded` the ticket order flips to paid and a `going` RSVP is upserted with `guest_count = ticket_count − 1`. Gated on the `community_events` module. Member-JWT.
Strictly necessary cookies keep the site working. With your consent we use analytics and marketing cookies to improve the experience and measure ad performance. You can change this any time from your account’s privacy settings.