{
  "openapi": "3.1.0",
  "info": {
    "title": "Slotstack Widget API",
    "version": "1.0.0",
    "description": "Public API consumed by the Slotstack embeddable widget. All routes are scoped to a tenant via the `?tenant=slug` query parameter. Currency amounts are in the smallest unit (pence/cents). Dates use YYYY-MM-DD, times use HH:MM."
  },
  "servers": [
    {
      "url": "/api/v1",
      "description": "Widget API v1"
    }
  ],
  "tags": [
    { "name": "Auth", "description": "Passwordless email-code authentication" },
    { "name": "Schedule", "description": "Public schedule, classes, spaces, instructors, and tenant info" },
    { "name": "Booking", "description": "Group class checkout and booking management" },
    { "name": "Private Lessons", "description": "Private lesson availability, checkout, and series booking" },
    { "name": "Passes", "description": "Class pass templates, purchase, and redemption" },
    { "name": "Gift Cards", "description": "Gift card purchase and balance checking" },
    { "name": "Subscriptions", "description": "Subscription plans, sign-up, and management" }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "JWT access token returned from /auth/verify-code"
      }
    },
    "parameters": {
      "tenant": {
        "name": "tenant",
        "in": "query",
        "required": true,
        "description": "Tenant slug identifying which business to query",
        "schema": { "type": "string" }
      },
      "page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "Page number for pagination (1-based)",
        "schema": { "type": "integer", "minimum": 1, "default": 1 }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Number of items per page",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" },
          "details": { "type": "object", "description": "Field-level validation errors, if applicable", "additionalProperties": true }
        }
      },
      "Customer": {
        "type": "object",
        "required": ["email", "name"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "name": { "type": "string" },
          "phone": { "type": "string" }
        }
      },
      "Slot": {
        "type": "object",
        "properties": {
          "classId": { "type": "string", "format": "uuid" },
          "title": { "type": "string" },
          "category": { "type": "string" },
          "sessionDate": { "type": "string", "format": "date", "description": "YYYY-MM-DD" },
          "startTime": { "type": "string", "description": "HH:MM" },
          "endTime": { "type": "string", "description": "HH:MM" },
          "durationMinutes": { "type": "integer" },
          "pricing": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PricingVariant" }
          },
          "spaceName": { "type": "string" },
          "instructors": {
            "type": "array",
            "items": { "type": "object", "properties": { "name": { "type": "string" } } }
          },
          "capacity": { "type": "integer", "nullable": true },
          "spotsLeft": { "type": "integer", "nullable": true }
        }
      },
      "PricingVariant": {
        "type": "object",
        "properties": {
          "variant": { "type": "string", "description": "Variant key, e.g. 'standard', 'trial'" },
          "label": { "type": "string", "description": "Display label" },
          "priceInSmallestUnit": { "type": "integer", "description": "Price in pence/cents" }
        }
      },
      "Class": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "title": { "type": "string" },
          "description": { "type": "string" },
          "category": { "type": "string" },
          "durationMinutes": { "type": "integer" },
          "capacity": { "type": "integer", "nullable": true },
          "pricing": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/PricingVariant" }
          },
          "schedule": { "type": "array", "items": { "type": "object" } }
        }
      },
      "Space": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "description": { "type": "string" }
        }
      },
      "Tenant": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "slug": { "type": "string" },
          "settings": {
            "type": "object",
            "description": "Tenant configuration including timezone, currency, booking terms, etc.",
            "additionalProperties": true
          }
        }
      },
      "Instructor": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "bio": { "type": "string" },
          "photoUrl": { "type": "string", "format": "uri" }
        }
      },
      "Booking": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "classId": { "type": "string", "format": "uuid" },
          "className": { "type": "string" },
          "sessionDate": { "type": "string", "format": "date" },
          "startTime": { "type": "string" },
          "status": { "type": "string", "enum": ["confirmed", "pending_payment", "cancelled"] },
          "amountPaid": { "type": "integer", "description": "Amount in smallest currency unit" },
          "bookingType": { "type": "string", "enum": ["group", "private"] },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "PrivateSlot": {
        "type": "object",
        "properties": {
          "startTime": { "type": "string", "description": "HH:MM" },
          "endTime": { "type": "string", "description": "HH:MM" }
        }
      },
      "PassTemplate": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "credits": { "type": "integer", "description": "Number of classes the pass covers" },
          "priceInSmallestUnit": { "type": "integer" },
          "validDays": { "type": "integer", "description": "Number of days the pass is valid after purchase" },
          "applicableCategories": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Class categories this pass can be used for"
          }
        }
      },
      "Pass": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "templateId": { "type": "string", "format": "uuid" },
          "templateName": { "type": "string" },
          "remainingCredits": { "type": "integer" },
          "totalCredits": { "type": "integer" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "status": { "type": "string", "enum": ["active", "expired", "exhausted"] }
        }
      },
      "GiftCardCheck": {
        "type": "object",
        "properties": {
          "valid": { "type": "boolean" },
          "status": { "type": "string" },
          "remainingAmount": { "type": "integer", "description": "Remaining balance in smallest currency unit" },
          "initialAmount": { "type": "integer", "description": "Original amount in smallest currency unit" },
          "expiresAt": { "type": "string", "format": "date-time" }
        }
      },
      "SubscriptionPlan": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "priceInSmallestUnit": { "type": "integer" },
          "interval": { "type": "string", "enum": ["month", "year"] },
          "applicableCategories": {
            "type": "array",
            "items": { "type": "string" }
          },
          "bookingsPerInterval": { "type": "integer", "nullable": true, "description": "Max bookings per billing period, null for unlimited" }
        }
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "planId": { "type": "string", "format": "uuid" },
          "planName": { "type": "string" },
          "status": { "type": "string", "enum": ["active", "cancelled", "past_due"] },
          "currentPeriodStart": { "type": "string", "format": "date-time" },
          "currentPeriodEnd": { "type": "string", "format": "date-time" },
          "bookingsUsed": { "type": "integer" },
          "bookingsPerInterval": { "type": "integer", "nullable": true }
        }
      },
      "CheckoutResponse": {
        "type": "object",
        "description": "Returned from checkout endpoints. If the booking is free (pass, gift card, subscription, or zero-price), `free` is true and `bookingId` is set. Otherwise, `clientSecret` and `stripeAccount` are provided for Stripe payment confirmation.",
        "properties": {
          "free": { "type": "boolean", "description": "True if no payment is needed" },
          "bookingId": { "type": "string", "format": "uuid" },
          "clientSecret": { "type": "string", "description": "Stripe PaymentIntent client secret for frontend confirmation" },
          "stripeAccount": { "type": "string", "description": "Stripe Connect account ID for the tenant" }
        }
      },
      "BookingSeries": {
        "type": "object",
        "properties": {
          "seriesId": { "type": "string", "format": "uuid" },
          "bookingId": { "type": "string", "format": "uuid" },
          "totalSessions": { "type": "integer" },
          "startsFrom": { "type": "string", "format": "date" },
          "dayOfWeek": { "type": "integer", "description": "0 (Sunday) to 6 (Saturday)" },
          "startTime": { "type": "string", "description": "HH:MM" }
        }
      },
      "Session": {
        "type": "object",
        "properties": {
          "access_token": { "type": "string" },
          "refresh_token": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/auth/send-code": {
      "post": {
        "tags": ["Auth"],
        "summary": "Send a login code via email",
        "description": "Sends a one-time verification code to the provided email address. The code is valid for a short period.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": { "type": "string", "format": "email" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid email or missing tenant",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/auth/verify-code": {
      "post": {
        "tags": ["Auth"],
        "summary": "Verify a login code and receive session tokens",
        "description": "Validates the one-time code sent via /auth/send-code. Returns a session with access and refresh tokens for authenticated endpoints.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email", "code"],
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "code": { "type": "string", "description": "Six-digit verification code" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification successful",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "session": { "$ref": "#/components/schemas/Session" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid or expired code",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/schedule": {
      "get": {
        "tags": ["Schedule"],
        "summary": "Get schedule slots for a date range",
        "description": "Returns all available class slots between the start and end dates, including pricing, capacity, and instructor details. Results account for schedule exceptions and studio closures.",
        "parameters": [
          { "$ref": "#/components/parameters/tenant" },
          {
            "name": "start",
            "in": "query",
            "required": true,
            "description": "Start date (YYYY-MM-DD)",
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "end",
            "in": "query",
            "required": true,
            "description": "End date (YYYY-MM-DD)",
            "schema": { "type": "string", "format": "date" }
          }
        ],
        "responses": {
          "200": {
            "description": "Schedule slots and tenant info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slots": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Slot" }
                    },
                    "tenant": { "$ref": "#/components/schemas/Tenant" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing start or end date",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/classes": {
      "get": {
        "tags": ["Schedule"],
        "summary": "List classes with optional category filter",
        "description": "Returns a paginated list of active classes for the tenant, optionally filtered by category.",
        "parameters": [
          { "$ref": "#/components/parameters/tenant" },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Filter by class category (e.g. 'drop-in', 'kids', 'course', 'private')",
            "schema": { "type": "string" }
          },
          { "$ref": "#/components/parameters/page" },
          { "$ref": "#/components/parameters/limit" }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of classes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "classes": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Class" }
                    },
                    "total": { "type": "integer" },
                    "page": { "type": "integer" },
                    "limit": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/spaces": {
      "get": {
        "tags": ["Schedule"],
        "summary": "List all spaces/rooms",
        "description": "Returns all spaces (rooms, studios, courts) configured for the tenant.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "List of spaces",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "spaces": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Space" }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/tenant": {
      "get": {
        "tags": ["Schedule"],
        "summary": "Get tenant info and settings",
        "description": "Returns the tenant's public name, slug, and settings (timezone, currency, branding, booking terms, etc.).",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "Tenant info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tenant": { "$ref": "#/components/schemas/Tenant" }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Tenant not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/instructors": {
      "get": {
        "tags": ["Schedule"],
        "summary": "List instructors with optional class filter",
        "description": "Returns a paginated list of instructors. Optionally filter by classId to see only instructors assigned to a specific class.",
        "parameters": [
          { "$ref": "#/components/parameters/tenant" },
          {
            "name": "classId",
            "in": "query",
            "required": false,
            "description": "Filter instructors by class UUID",
            "schema": { "type": "string", "format": "uuid" }
          },
          { "$ref": "#/components/parameters/page" },
          { "$ref": "#/components/parameters/limit" }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of instructors",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "instructors": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Instructor" }
                    },
                    "total": { "type": "integer" },
                    "page": { "type": "integer" },
                    "limit": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/checkout": {
      "post": {
        "tags": ["Booking"],
        "summary": "Create a group class booking",
        "description": "Creates a booking for a group class session. Supports payment via Stripe, class passes, gift cards, or subscriptions. For free bookings the response includes `free: true`. For paid bookings it returns a Stripe `clientSecret` to confirm payment on the frontend.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["classId", "sessionDate", "customer"],
                "properties": {
                  "classId": { "type": "string", "format": "uuid" },
                  "sessionDate": { "type": "string", "format": "date", "description": "YYYY-MM-DD" },
                  "customer": { "$ref": "#/components/schemas/Customer" },
                  "pricingVariant": { "type": "string", "description": "Pricing variant key, e.g. 'standard', 'trial'" },
                  "termsAcceptedAt": { "type": "string", "format": "date-time", "description": "ISO timestamp of when terms were accepted" },
                  "passId": { "type": "string", "format": "uuid", "description": "Class pass to redeem" },
                  "giftCardCode": { "type": "string", "description": "Gift card code to apply" },
                  "subscriptionId": { "type": "string", "format": "uuid", "description": "Active subscription to use" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking created or payment intent prepared",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CheckoutResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error or business rule violation",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Class not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "409": {
            "description": "Session is full (capacity reached)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/checkout/confirm": {
      "post": {
        "tags": ["Booking"],
        "summary": "Confirm a booking after Stripe payment",
        "description": "Called after the Stripe PaymentIntent succeeds on the frontend. Verifies the payment and marks the booking as confirmed.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["paymentIntentId"],
                "properties": {
                  "paymentIntentId": { "type": "string", "description": "Stripe PaymentIntent ID" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bookingId": { "type": "string", "format": "uuid" },
                    "success": { "type": "boolean" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Payment not completed",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Booking not found for this payment intent",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/my-bookings": {
      "get": {
        "tags": ["Booking"],
        "summary": "List the authenticated user's bookings",
        "description": "Returns all bookings for the currently authenticated customer.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "List of bookings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bookings": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Booking" }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/my-bookings/{bookingId}/cancel": {
      "post": {
        "tags": ["Booking"],
        "summary": "Cancel a booking",
        "description": "Cancels an existing confirmed booking for the authenticated user.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/tenant" },
          {
            "name": "bookingId",
            "in": "path",
            "required": true,
            "description": "UUID of the booking to cancel",
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Booking cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": { "type": "boolean" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Booking not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/private-availability": {
      "get": {
        "tags": ["Private Lessons"],
        "summary": "Get available time slots for a private lesson",
        "description": "Returns available time slots for a private lesson on a given date with a specific instructor. Accounts for instructor availability, existing bookings, buffer times, and studio closures.",
        "parameters": [
          { "$ref": "#/components/parameters/tenant" },
          {
            "name": "instructorId",
            "in": "query",
            "required": true,
            "description": "UUID of the instructor",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "classId",
            "in": "query",
            "required": true,
            "description": "UUID of the private lesson class type",
            "schema": { "type": "string", "format": "uuid" }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "description": "Date to check availability (YYYY-MM-DD)",
            "schema": { "type": "string", "format": "date" }
          }
        ],
        "responses": {
          "200": {
            "description": "Available time slots",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slots": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/PrivateSlot" }
                    },
                    "date": { "type": "string", "format": "date" },
                    "instructorId": { "type": "string", "format": "uuid" },
                    "durationMinutes": { "type": "integer" },
                    "bufferMinutes": { "type": "integer" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing required parameters",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/private-checkout": {
      "post": {
        "tags": ["Private Lessons"],
        "summary": "Create a private lesson booking",
        "description": "Books a private lesson with a specific instructor, date, and time. Supports the same payment methods as group checkout (Stripe, passes, gift cards, subscriptions).",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["classId", "instructorId", "date", "startTime", "endTime", "customer"],
                "properties": {
                  "classId": { "type": "string", "format": "uuid" },
                  "instructorId": { "type": "string", "format": "uuid" },
                  "date": { "type": "string", "format": "date", "description": "YYYY-MM-DD" },
                  "startTime": { "type": "string", "description": "HH:MM" },
                  "endTime": { "type": "string", "description": "HH:MM" },
                  "customer": { "$ref": "#/components/schemas/Customer" },
                  "guests": {
                    "type": "array",
                    "items": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } },
                    "description": "Optional guest list for the private lesson"
                  },
                  "pricingVariant": { "type": "string" },
                  "termsAcceptedAt": { "type": "string", "format": "date-time" },
                  "passId": { "type": "string", "format": "uuid" },
                  "giftCardCode": { "type": "string" },
                  "subscriptionId": { "type": "string", "format": "uuid" },
                  "seriesId": { "type": "string", "format": "uuid", "description": "If this booking is part of a recurring series" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking created or payment intent prepared",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CheckoutResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error or time slot unavailable",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Class or instructor not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/private-checkout/confirm": {
      "post": {
        "tags": ["Private Lessons"],
        "summary": "Confirm a private lesson booking after Stripe payment",
        "description": "Same as /checkout/confirm but for private lesson bookings. Verifies the Stripe payment and marks the booking as confirmed.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["paymentIntentId"],
                "properties": {
                  "paymentIntentId": { "type": "string", "description": "Stripe PaymentIntent ID" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bookingId": { "type": "string", "format": "uuid" },
                    "success": { "type": "boolean" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Payment not completed",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Booking not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/booking-series": {
      "post": {
        "tags": ["Private Lessons"],
        "summary": "Create a recurring private lesson series",
        "description": "Creates a series of recurring private lesson bookings on a fixed day and time each week. Returns the series ID and the first booking.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["classId", "instructorId", "dayOfWeek", "startTime", "totalSessions", "startsFrom", "customer"],
                "properties": {
                  "classId": { "type": "string", "format": "uuid" },
                  "instructorId": { "type": "string", "format": "uuid" },
                  "dayOfWeek": { "type": "integer", "description": "0 (Sunday) to 6 (Saturday)", "minimum": 0, "maximum": 6 },
                  "startTime": { "type": "string", "description": "HH:MM" },
                  "totalSessions": { "type": "integer", "minimum": 1, "description": "Number of sessions in the series" },
                  "startsFrom": { "type": "string", "format": "date", "description": "First session date (YYYY-MM-DD)" },
                  "customer": { "$ref": "#/components/schemas/Customer" },
                  "guests": {
                    "type": "array",
                    "items": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string", "format": "email" } } }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Series created",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BookingSeries" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/passes": {
      "get": {
        "tags": ["Passes"],
        "summary": "List available class pass templates",
        "description": "Returns all active class pass templates and the tenant's currency.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "Pass templates and currency",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/PassTemplate" }
                    },
                    "currency": { "type": "string", "description": "ISO 4217 currency code, e.g. 'gbp', 'usd'" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/passes/purchase": {
      "post": {
        "tags": ["Passes"],
        "summary": "Purchase a class pass",
        "description": "Creates a class pass purchase. If the pass is free, returns `free: true` and the pass ID. Otherwise returns a Stripe client secret for payment.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["templateId", "customer"],
                "properties": {
                  "templateId": { "type": "string", "format": "uuid" },
                  "customer": { "$ref": "#/components/schemas/Customer" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pass purchase initiated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "free": { "type": "boolean" },
                    "passId": { "type": "string", "format": "uuid" },
                    "clientSecret": { "type": "string" },
                    "stripeAccount": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Pass template not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/passes/my-passes": {
      "get": {
        "tags": ["Passes"],
        "summary": "List the authenticated user's class passes",
        "description": "Returns all class passes owned by the authenticated customer, including remaining credits and expiry.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "List of passes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "passes": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Pass" }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/gift-cards/purchase": {
      "post": {
        "tags": ["Gift Cards"],
        "summary": "Purchase a gift card",
        "description": "Creates a gift card with a custom amount. Always returns a Stripe client secret for payment. An optional recipient email triggers delivery of the gift card code.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["amount", "buyer"],
                "properties": {
                  "amount": { "type": "integer", "description": "Gift card amount in smallest currency unit (pence/cents)" },
                  "buyer": { "$ref": "#/components/schemas/Customer" },
                  "recipientName": { "type": "string", "description": "Name of the gift card recipient" },
                  "recipientEmail": { "type": "string", "format": "email", "description": "If provided, the gift card code is emailed to this address" },
                  "message": { "type": "string", "description": "Personal message included in the recipient email" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Gift card created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "clientSecret": { "type": "string", "description": "Stripe PaymentIntent client secret" },
                    "stripeAccount": { "type": "string" },
                    "giftCardCode": { "type": "string", "description": "Unique gift card redemption code" },
                    "giftCardId": { "type": "string", "format": "uuid" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/gift-cards/check": {
      "get": {
        "tags": ["Gift Cards"],
        "summary": "Check a gift card balance",
        "description": "Validates a gift card code and returns its current balance, status, and expiry.",
        "parameters": [
          { "$ref": "#/components/parameters/tenant" },
          {
            "name": "code",
            "in": "query",
            "required": true,
            "description": "Gift card redemption code",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Gift card details",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GiftCardCheck" }
              }
            }
          },
          "400": {
            "description": "Missing code parameter",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/subscriptions": {
      "get": {
        "tags": ["Subscriptions"],
        "summary": "List available subscription plans",
        "description": "Returns all active subscription plans and the tenant's currency.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "Subscription plans and currency",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/SubscriptionPlan" }
                    },
                    "currency": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/subscribe": {
      "post": {
        "tags": ["Subscriptions"],
        "summary": "Subscribe to a plan",
        "description": "Creates a Stripe subscription for the customer. Returns the subscription ID and a Stripe client secret for initial payment setup.",
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["planId", "customer"],
                "properties": {
                  "planId": { "type": "string", "format": "uuid" },
                  "customer": { "$ref": "#/components/schemas/Customer" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptionId": { "type": "string", "format": "uuid" },
                    "stripeSubscriptionId": { "type": "string" },
                    "clientSecret": { "type": "string" },
                    "stripeAccount": { "type": "string" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Plan not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/subscriptions/my-subscription": {
      "get": {
        "tags": ["Subscriptions"],
        "summary": "Get the authenticated user's active subscription",
        "description": "Returns the currently authenticated customer's active subscription details including usage within the current billing period.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [{ "$ref": "#/components/parameters/tenant" }],
        "responses": {
          "200": {
            "description": "Subscription details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscription": { "$ref": "#/components/schemas/Subscription" },
                    "currency": { "type": "string" }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not authenticated",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  }
}