{
  "openapi": "3.1.0",
  "info": {
    "title": "ChronixHub Public API",
    "version": "1.0.0",
    "summary": "Read-only REST API for studios on ChronixHub.",
    "description": "Read-only REST API that lets a tenant's own systems (ETL pipelines, custom dashboards, Zapier-style integrations) read their data from ChronixHub.\n\nAll endpoints are scoped to the tenant the API key was issued for. Soft-deleted records are always excluded. Breaking changes will ship as a new version path with at least 90 days of advance notice — never as silent edits to `/api/v1/*`.\n\nKeys are server-to-server only. CORS is intentionally disabled on `/api/v1/*` — calling these endpoints from a browser will fail.",
    "contact": {
      "name": "ChronixHub Support",
      "email": "info@chronixhub.com",
      "url": "https://chronixhub.com"
    },
    "termsOfService": "https://chronixhub.com/terms",
    "license": {
      "name": "Proprietary",
      "url": "https://chronixhub.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.chronixhub.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Sessions",
      "description": "Class sessions on the schedule. Returns sessions in the authenticated tenant only, ordered by `startTime` ascending."
    },
    {
      "name": "Bookings",
      "description": "Reservations placed against class sessions. Includes member bookings, guest bookings, and historical attendance. Ordered by `createdAt` descending."
    },
    {
      "name": "Clients",
      "description": "The studio's CRM records. Stripped of sensitive PII (date of birth, internal notes, marketing opt-out). Ordered by `createdAt` descending."
    },
    {
      "name": "Class Types",
      "description": "Reusable class templates (Vinyasa Flow, Reformer Beginner, etc.). Ordered by `name` ascending."
    },
    {
      "name": "Packages",
      "description": "Client packages (credit packs and memberships). Status is server-derived from credits + expiry. Ordered by `createdAt` descending."
    },
    {
      "name": "Staff",
      "description": "Tenant memberships (owners, admins, instructors). Stripped of payroll columns and authentication identity. Ordered by `createdAt` ascending."
    },
    {
      "name": "Rooms",
      "description": "Physical or virtual rooms in which classes run. Ordered by `name` ascending."
    }
  ],
  "paths": {
    "/api/v1/sessions": {
      "get": {
        "tags": [
          "Sessions"
        ],
        "summary": "List class sessions",
        "description": "Returns class sessions on the studio's schedule. Filter by date range or class type, and optionally restrict to portal-visible sessions.\n\n**Sort:** `startTime` ascending.\n\n**Stripped fields:** staff/room fee snapshots, fee waiver reasons.",
        "operationId": "listSessions",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "$ref": "#/components/parameters/FromParam"
          },
          {
            "$ref": "#/components/parameters/ToParam"
          },
          {
            "name": "classTypeId",
            "in": "query",
            "description": "Filter to a specific class type. Exact match.",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "clu1abc2def3ghi4jkl5mn"
          },
          {
            "name": "showOnPortal",
            "in": "query",
            "description": "When `true`, only returns sessions visible on the client booking portal.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of sessions.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicSession"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmoh1z612003dcg6jq9yqlixz",
                      "classTypeId": "cmoh1z4o9000kcg6jje55cf8m",
                      "instructorId": "cmoh1z3uv0005cg6jkuy66lb3",
                      "roomId": "cmoh1z4f3000gcg6jhf7m9cym",
                      "startTime": "2026-03-30T04:00:00.000Z",
                      "endTime": "2026-03-30T04:45:00.000Z",
                      "capacity": 20,
                      "showOnPortal": true,
                      "recurrenceGroupId": "ae87625c-cbfc-48e3-888f-2bbb09f43829",
                      "createdAt": "2026-04-27T10:27:11.561Z",
                      "updatedAt": "2026-04-27T10:27:11.561Z"
                    },
                    {
                      "id": "cmoh1z612003ecg6jsz4xhdz2",
                      "classTypeId": "cmoh1z4oa000mcg6jidd9ds7i",
                      "instructorId": "cmoh1z3yj0007cg6jnyltje07",
                      "roomId": "cmoh1z4f3000hcg6jrwgmwz3w",
                      "startTime": "2026-03-30T06:00:00.000Z",
                      "endTime": "2026-03-30T07:00:00.000Z",
                      "capacity": 15,
                      "showOnPortal": true,
                      "recurrenceGroupId": "c7b3f934-4bae-4fa9-a729-fb13eb34485a",
                      "createdAt": "2026-04-27T10:27:11.561Z",
                      "updatedAt": "2026-04-27T10:27:11.561Z"
                    },
                    {
                      "id": "cmoh1z612003fcg6jxx2dqnvd",
                      "classTypeId": "cmoh1z4oa000ncg6jfy393eg9",
                      "instructorId": "cmoh1z4280009cg6jitdh6suw",
                      "roomId": "cmoh1z4f3000gcg6jhf7m9cym",
                      "startTime": "2026-03-30T09:00:00.000Z",
                      "endTime": "2026-03-30T09:50:00.000Z",
                      "capacity": 18,
                      "showOnPortal": true,
                      "recurrenceGroupId": "3ba5a695-a0d7-4004-92de-98e306659761",
                      "createdAt": "2026-04-27T10:27:11.561Z",
                      "updatedAt": "2026-04-27T10:27:11.561Z"
                    }
                  ],
                  "total": 173,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/bookings": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "List bookings",
        "description": "Returns bookings placed against the studio's class sessions. Filter by session, client, status, or date range.\n\n**Sort:** `createdAt` descending.\n\n**Stripped fields:** payment amounts, discount snapshots, drop-in price, late-cancel flags. Revenue data will live behind a future `/api/v1/payments` endpoint.",
        "operationId": "listBookings",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "$ref": "#/components/parameters/FromParam"
          },
          {
            "$ref": "#/components/parameters/ToParam"
          },
          {
            "name": "sessionId",
            "in": "query",
            "description": "Filter to bookings for a specific class session. Exact match.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "clientId",
            "in": "query",
            "description": "Filter to bookings for a specific client. Exact match.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by booking status.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "CONFIRMED",
                "ATTENDED",
                "CANCELLED",
                "NO_SHOW"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of bookings.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicBooking"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmojz0eq80001bw6jpikp1v0n",
                      "clientId": "cmojyw9aa0000bw6jxjcb9kls",
                      "sessionId": "cmoh1z6150072cg6jeikdl2ei",
                      "status": "CONFIRMED",
                      "isGuest": false,
                      "guestName": null,
                      "source": "portal",
                      "createdAt": "2026-04-29T11:27:29.168Z",
                      "updatedAt": "2026-04-29T11:27:29.168Z"
                    },
                    {
                      "id": "cmoh21tov01o7cg6j9u732qw6",
                      "clientId": null,
                      "sessionId": "cmoh1z615006tcg6jk7kej1y5",
                      "status": "CONFIRMED",
                      "isGuest": true,
                      "guestName": "Nour Bou Khalil",
                      "source": "admin",
                      "createdAt": "2026-04-27T10:29:15.535Z",
                      "updatedAt": "2026-04-27T10:29:15.535Z"
                    },
                    {
                      "id": "cmoh21tn101o6cg6jpiitjaba",
                      "clientId": null,
                      "sessionId": "cmoh1z615007ycg6j44ot5a6t",
                      "status": "CONFIRMED",
                      "isGuest": true,
                      "guestName": "Rola Salameh",
                      "source": "admin",
                      "createdAt": "2026-04-27T10:29:15.469Z",
                      "updatedAt": "2026-04-27T10:29:15.469Z"
                    }
                  ],
                  "total": 1869,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/clients": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "List clients",
        "description": "Returns the studio's CRM records. Filter by active status or look up by email (exact match, case-insensitive).\n\n**Sort:** `createdAt` descending.\n\n**Stripped fields:** `dateOfBirth`, `notes`, `emailOptOut`, `userId`. PII not strictly needed for integrations is intentionally absent.",
        "operationId": "listClients",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "Filter by active flag.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Look up by email. Exact match, case-insensitive.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "jane@example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of clients.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicClient"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmojyw9aa0000bw6jxjcb9kls",
                      "name": "Jane Doe",
                      "email": "jane@example.com",
                      "phone": "+96171123456",
                      "isActive": true,
                      "createdAt": "2026-04-29T11:24:15.490Z",
                      "updatedAt": "2026-04-29T11:24:15.490Z"
                    },
                    {
                      "id": "cmoh1z5k1001xcg6j6deahkg5",
                      "name": "Sam Khalil",
                      "email": "sam.k@example.com",
                      "phone": "+96170987654",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:10.945Z",
                      "updatedAt": "2026-04-27T10:27:10.945Z"
                    },
                    {
                      "id": "cmoh1z5ao0015cg6j7wclx7ui",
                      "name": "Maria Schmidt",
                      "email": "maria.s@example.com",
                      "phone": "+442079460958",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:10.609Z",
                      "updatedAt": "2026-04-27T10:27:10.609Z"
                    }
                  ],
                  "total": 43,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/class-types": {
      "get": {
        "tags": [
          "Class Types"
        ],
        "summary": "List class types",
        "description": "Returns reusable class templates (e.g. Vinyasa Flow, Reformer Beginner). Filter by active status.\n\n**Sort:** `name` ascending.\n\n**Stripped fields:** `dropInPriceCents`, `lateCancelWindowMinutes`. Pricing and cancellation policy are configuration knobs owned by staff.",
        "operationId": "listClassTypes",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "Filter by active flag.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of class types.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicClassType"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmoh1z4oa000qcg6ja17qk5qi",
                      "name": "Barre Sculpt",
                      "description": null,
                      "durationMinutes": 50,
                      "color": "#EC4899",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:09.802Z",
                      "updatedAt": "2026-04-27T10:27:09.802Z"
                    },
                    {
                      "id": "cmoh1z4oa000rcg6j6zdjswi5",
                      "name": "Boxing Basics",
                      "description": null,
                      "durationMinutes": 45,
                      "color": "#F97316",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:09.802Z",
                      "updatedAt": "2026-04-27T10:27:09.802Z"
                    },
                    {
                      "id": "cmoh1z4o9000kcg6jje55cf8m",
                      "name": "HIIT Power",
                      "description": null,
                      "durationMinutes": 45,
                      "color": "#EF4444",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:09.802Z",
                      "updatedAt": "2026-04-27T10:27:09.802Z"
                    }
                  ],
                  "total": 8,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/packages": {
      "get": {
        "tags": [
          "Packages"
        ],
        "summary": "List client packages",
        "description": "Returns client packages (credit packs and memberships). The `status` filter is server-derived from credits and expiry:\n\n- `active` — not soft-deleted, payment not cancelled, has credits, not expired.\n- `expired` — `expiresAt` is in the past.\n- `exhausted` — no remaining credits but not yet expired.\n\n**Sort:** `createdAt` descending.\n\n**Stripped fields:** `paymentStatus`, `discountId`, `discountPercent`, `discountLabel`.",
        "operationId": "listPackages",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "name": "clientId",
            "in": "query",
            "description": "Filter to packages owned by a specific client.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by derived status.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "expired",
                "exhausted"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of client packages.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicClientPackage"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmoh1z5re0029cg6j42yj7h6v",
                      "clientId": "cmoh1z5ao0013cg6jt3osjp53",
                      "packageTypeId": "cmoh1z4zk000wcg6j0g1747vw",
                      "remainingCredits": 999,
                      "expiresAt": "2026-05-27T10:27:11.209Z",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:11.211Z",
                      "updatedAt": "2026-04-27T10:27:11.211Z"
                    },
                    {
                      "id": "cmoh1z5re002acg6juzdbr2f7",
                      "clientId": "cmoh1z5ao0015cg6j7wclx7ui",
                      "packageTypeId": "cmoh1z4zk000wcg6j0g1747vw",
                      "remainingCredits": 999,
                      "expiresAt": "2026-05-27T10:27:11.209Z",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:11.211Z",
                      "updatedAt": "2026-04-27T10:27:11.211Z"
                    },
                    {
                      "id": "cmoh1z5re002bcg6jwruy4a7w",
                      "clientId": "cmoh1z5ao0017cg6je335b0t5",
                      "packageTypeId": "cmoh1z4zk000wcg6j0g1747vw",
                      "remainingCredits": 999,
                      "expiresAt": "2026-05-27T10:27:11.209Z",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:11.211Z",
                      "updatedAt": "2026-04-27T10:27:11.211Z"
                    }
                  ],
                  "total": 21,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/staff": {
      "get": {
        "tags": [
          "Staff"
        ],
        "summary": "List staff",
        "description": "Returns tenant memberships (owners, admins, instructors). Filter by role or active status.\n\n**Sort:** `createdAt` ascending.\n\n**Stripped fields:** `sessionFeeCents`, `perClientFeeCents`, `viewFullSchedule`, `bio`, `avatarUrl`, `User.authId`. Payroll and authentication identity never appear in the public API.",
        "operationId": "listStaff",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "name": "role",
            "in": "query",
            "description": "Filter by tenant role.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "OWNER",
                "ADMIN",
                "INSTRUCTOR"
              ]
            }
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "Filter by active flag.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of staff.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicStaff"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmoh1z3r00003cg6jych27yiu",
                      "name": "Maya Owner",
                      "role": "OWNER",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:08.605Z",
                      "updatedAt": "2026-04-27T10:27:08.605Z"
                    },
                    {
                      "id": "cmoh1z3uv0005cg6jkuy66lb3",
                      "name": "Sam Trainer",
                      "role": "INSTRUCTOR",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:08.743Z",
                      "updatedAt": "2026-04-27T10:27:08.743Z"
                    },
                    {
                      "id": "cmoh1z3yj0007cg6jnyltje07",
                      "name": "Alex Instructor",
                      "role": "INSTRUCTOR",
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:08.875Z",
                      "updatedAt": "2026-04-27T10:27:08.875Z"
                    }
                  ],
                  "total": 7,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/rooms": {
      "get": {
        "tags": [
          "Rooms"
        ],
        "summary": "List rooms",
        "description": "Returns physical or virtual rooms in which classes run. Filter by active status.\n\n**Sort:** `name` ascending.\n\n**Stripped fields:** `sessionFeeCents`, `perClientFeeCents`, `parallelCapacity`, `icon`, `photoUrl`.",
        "operationId": "listRooms",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/OffsetParam"
          },
          {
            "name": "isActive",
            "in": "query",
            "description": "Filter by active flag.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of rooms.",
            "headers": {
              "X-Public-API-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-Public-API-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-Public-API-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/PaginatedEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PublicRoom"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "cmoh1z4f3000icg6jnp3ykkct",
                      "name": "Cardio Zone",
                      "capacity": 25,
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:09.471Z",
                      "updatedAt": "2026-04-27T10:27:09.471Z"
                    },
                    {
                      "id": "cmoh1z4f3000jcg6jaj232i3u",
                      "name": "Rooftop Deck",
                      "capacity": 12,
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:09.471Z",
                      "updatedAt": "2026-04-27T10:27:09.471Z"
                    },
                    {
                      "id": "cmoh1z4f3000gcg6jhf7m9cym",
                      "name": "Studio A",
                      "capacity": 20,
                      "isActive": true,
                      "createdAt": "2026-04-27T10:27:09.471Z",
                      "updatedAt": "2026-04-27T10:27:09.471Z"
                    }
                  ],
                  "total": 4,
                  "limit": 3,
                  "offset": 0
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "chx_live_<keyId>_<secret>",
        "description": "All requests must include `Authorization: Bearer chx_live_<keyId>_<secret>`. Keys are server-to-server only — CORS is intentionally disabled. Keys are never accepted via query string. The full key is shown exactly once at creation; only `keyId` and `SHA-256(secret)` are stored on our side."
      }
    },
    "parameters": {
      "LimitParam": {
        "name": "limit",
        "in": "query",
        "description": "Maximum number of records to return. Defaults to 50, capped at 200.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "OffsetParam": {
        "name": "offset",
        "in": "query",
        "description": "Number of records to skip. Defaults to 0.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "FromParam": {
        "name": "from",
        "in": "query",
        "description": "Inclusive lower bound for the date filter. ISO 8601 datetime.",
        "required": false,
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "example": "2026-05-01T00:00:00Z"
      },
      "ToParam": {
        "name": "to",
        "in": "query",
        "description": "Inclusive upper bound for the date filter. ISO 8601 datetime. Must be on or after `from`.",
        "required": false,
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "example": "2026-06-01T00:00:00Z"
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Per-key request quota for the current window.",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current window for this key.",
        "schema": {
          "type": "integer",
          "example": 47
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the per-key window resets.",
        "schema": {
          "type": "integer",
          "example": 38
        }
      },
      "RetryAfter": {
        "description": "Seconds the client must wait before retrying.",
        "schema": {
          "type": "integer",
          "example": 38
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, malformed, expired, or revoked API key.",
        "headers": {
          "WWW-Authenticate": {
            "description": "Standards-compliant challenge header.",
            "schema": {
              "type": "string",
              "example": "Bearer realm=\"api\", error=\"invalid_token\""
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": {
                "code": "unauthorized",
                "message": "Missing or invalid API key. Send `Authorization: Bearer chx_live_<keyId>_<secret>`."
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "Key is valid but the `public_api` feature is not on the tenant's subscription plan.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": {
                "code": "payment_required",
                "message": "The Public API is not enabled on this tenant's plan."
              }
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "Malformed query parameter (date format, status enum, pagination bounds, etc.).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": {
                "code": "validation_failed",
                "message": "Invalid `from` — must be an ISO 8601 date."
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-key rate limit exceeded.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-Public-API-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-Public-API-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-Public-API-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Rate limit exceeded. Retry after 38 seconds."
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server-side failure.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "An internal error occurred."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "ApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unauthorized",
                  "payment_required",
                  "forbidden",
                  "not_found",
                  "validation_failed",
                  "rate_limited",
                  "internal_error"
                ],
                "description": "Stable, machine-parseable error code. New codes are added; existing codes never change meaning."
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation. Format may change without notice — always switch on `code`."
              }
            }
          }
        }
      },
      "PaginatedEnvelope": {
        "type": "object",
        "required": [
          "data",
          "total",
          "limit",
          "offset"
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "The page of records. Element shape depends on the endpoint."
          },
          "total": {
            "type": "integer",
            "description": "Total number of records matching the query (across all pages).",
            "example": 1240
          },
          "limit": {
            "type": "integer",
            "description": "The `limit` echoed back from the request.",
            "example": 50
          },
          "offset": {
            "type": "integer",
            "description": "The `offset` echoed back from the request.",
            "example": 0
          }
        }
      },
      "PublicSession": {
        "type": "object",
        "required": [
          "id",
          "classTypeId",
          "instructorId",
          "roomId",
          "startTime",
          "endTime",
          "capacity",
          "showOnPortal",
          "recurrenceGroupId",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "ClassSession id (CUID)."
          },
          "classTypeId": {
            "type": "string",
            "description": "ClassType id this session is an instance of."
          },
          "instructorId": {
            "type": [
              "string",
              "null"
            ],
            "description": "TenantMembership id of the assigned instructor. Nullable on unstaffed sessions."
          },
          "roomId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Room id. Nullable on virtual or pop-up sessions."
          },
          "startTime": {
            "type": "string",
            "format": "date-time",
            "description": "Scheduled start (ISO 8601 UTC)."
          },
          "endTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Scheduled end (ISO 8601 UTC). Nullable on open-ended sessions."
          },
          "capacity": {
            "type": "integer",
            "description": "Maximum number of bookings the session accepts.",
            "minimum": 0
          },
          "showOnPortal": {
            "type": "boolean",
            "description": "Whether this session is visible on the public client portal."
          },
          "recurrenceGroupId": {
            "type": [
              "string",
              "null"
            ],
            "description": "If the session is part of a recurring series, the shared group id; null for one-off sessions."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicBooking": {
        "type": "object",
        "required": [
          "id",
          "clientId",
          "sessionId",
          "status",
          "isGuest",
          "guestName",
          "source",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "clientId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null on guest bookings."
          },
          "sessionId": {
            "type": "string",
            "description": "ClassSession the booking is held against."
          },
          "status": {
            "type": "string",
            "enum": [
              "CONFIRMED",
              "ATTENDED",
              "CANCELLED",
              "NO_SHOW"
            ]
          },
          "isGuest": {
            "type": "boolean"
          },
          "guestName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name captured for guest bookings; null otherwise."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Channel that placed the booking (`portal`, `admin`, etc.)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicClient": {
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "phone",
          "isActive",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "E.164 formatted phone number."
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicClassType": {
        "type": "object",
        "required": [
          "id",
          "name",
          "description",
          "durationMinutes",
          "color",
          "isActive",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMinutes": {
            "type": "integer",
            "minimum": 0
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hex color used to render the class type in calendars."
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicClientPackage": {
        "type": "object",
        "required": [
          "id",
          "clientId",
          "packageTypeId",
          "remainingCredits",
          "expiresAt",
          "isActive",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "clientId": {
            "type": "string"
          },
          "packageTypeId": {
            "type": "string"
          },
          "remainingCredits": {
            "type": "integer",
            "description": "Credits left on the package. `0` for unlimited memberships once consumed; check the linked PackageType for unlimited semantics."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null for non-expiring packages."
          },
          "isActive": {
            "type": "boolean",
            "description": "Server-derived: not soft-deleted, payment not cancelled, has credits (or unlimited), and not expired."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicStaff": {
        "type": "object",
        "required": [
          "id",
          "name",
          "role",
          "isActive",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "TenantMembership id."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "OWNER",
              "ADMIN",
              "INSTRUCTOR"
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicRoom": {
        "type": "object",
        "required": [
          "id",
          "name",
          "capacity",
          "isActive",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "capacity": {
            "type": "integer",
            "minimum": 0
          },
          "isActive": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      }
    }
  }
}
