{"openapi":"3.1.0","info":{"title":"HotGlue Data API","version":"1.0.0","summary":"Read-only access to a studio's public catalog.","description":"Classes, schedules, trials, registration seasons, memberships, appointment services and locations for one studio, exactly as that studio shows them to the public.\n\nEvery request carries an API key in the `X-API-Key` header. The key decides which studio you see; there is no studio id to pass. Money is always integer cents. Dates are `YYYY-MM-DD`; timestamps are ISO 8601.\n\nGuides: https://developer.hotglue.app","contact":{"name":"HotGlue","url":"https://hotglue.app","email":"hello@hotglue.app"},"license":{"name":"Proprietary — use under the HotGlue merchant terms","url":"https://go.hotglue.app/legal"}},"servers":[{"url":"https://go.hotglue.app/api/v1","description":"Production"}],"security":[{"ApiKey":[]}],"tags":[{"name":"Keys","description":"About the key you are calling with. No scope needed."},{"name":"Classes","description":"The class catalog, categories, dated sessions and trial offers. Scope `classes:read`."},{"name":"Registrations","description":"Registration seasons and what families can sign up for in each. Scope `registrations:read`."},{"name":"Memberships","description":"Membership tiers and prices. Scope `memberships:read`."},{"name":"Appointments","description":"Bookable services such as private lessons. Scope `appointments:read`."},{"name":"Locations","description":"Physical locations. Scope `locations:read`."}],"paths":{"/me":{"get":{"tags":["Keys"],"operationId":"getMe","summary":"Who am I","description":"The studio this key belongs to and the scopes it carries. Needs no scope — the first call to make with a new key.","responses":{"200":{"description":"The studio and the key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/classes":{"get":{"tags":["Classes"],"operationId":"listClasses","summary":"List classes","description":"Active classes, A→Z by name. Draft and archived classes are never returned.\n\n\"Active\" is the studio's switch, not the calendar: a class whose `end_date` has passed stays listed until the studio archives it. Pass `ends_after` (usually today) to leave finished classes out.\n\nOne-time classes carry their date and time on their sessions, not on the class: `schedule_description` and `day_schedules` are null for them, so call `/classes/{classId}/instances` for the when.\n\n`capacity.enrolled` counts students holding a seat in any future session (enrolled or pending registration); `spots_remaining` is what checkout will honour. Checkout does not sell a seat that is not there, so a class at 0 can still be linked — the family will see the waitlist or a full message.","parameters":[{"name":"category_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Only classes in this category."},{"name":"category_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated category ids. Ignored when `category_id` is set."},{"name":"schedule_type","in":"query","required":false,"schema":{"type":"string","enum":["recurring","one-time","all"],"default":"all"}},{"name":"day_of_week","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":6},"description":"Only classes that meet on this day. 0 = Sunday … 6 = Saturday."},{"name":"ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated class ids — fetch a known set in one call."},{"name":"ends_after","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Only classes still running on this date (`end_date` on or after it) or with no end date. Pass today to hide finished classes."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of classes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/classes/{classId}/instances":{"get":{"tags":["Classes"],"operationId":"listClassInstances","summary":"List a class's sessions","description":"The dated sessions (instances) of one active class, soonest first. Use this for calendars and for per-session (drop-in) booking: each instance carries its own checkout link.","parameters":[{"name":"classId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"First date to include, `YYYY-MM-DD`. Defaults to today in the studio's time zone."},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Last date to include, `YYYY-MM-DD`."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["scheduled","cancelled","completed","all"],"default":"scheduled"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Rows per page, 1–200."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of sessions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassInstanceList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/categories":{"get":{"tags":["Classes"],"operationId":"listCategories","summary":"List class categories","description":"Active categories in the studio's display order. Classes carry a `category` with the same id, name and color.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of categories.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/trial-classes":{"get":{"tags":["Classes"],"operationId":"listTrialClasses","summary":"List trial offers","description":"The studio's active trial offers — \"try a class\" promotions, free or at a small price — each tied to a class and carrying the sign-up link to send a family to. Reading never changes anything.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of trial offers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialClassList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/registrations":{"get":{"tags":["Registrations"],"operationId":"listRegistrations","summary":"List registration seasons","description":"Open and closed registration seasons (for example \"Fall 2026 Competitive Team\"), newest season first. These are enrollment windows, not individual sign-ups. Draft and archived seasons are never returned.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["open","closed","all"],"default":"all"}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["seasonal","unlimited","all"],"default":"all"},"description":"`seasonal` seasons have start and end dates; `unlimited` ones run until closed."},{"name":"season_after","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Seasons starting on or after this date."},{"name":"season_before","in":"query","required":false,"schema":{"type":"string","format":"date"},"description":"Seasons starting on or before this date."},{"name":"open_now","in":"query","required":false,"schema":{"type":"boolean"},"description":"`true` = only seasons accepting public sign-ups right now (open, past their external open time, before their close time)."},{"name":"tags","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated tag names; returns seasons carrying ANY of them. Case-insensitive. Unknown names give an empty page.","example":"competitive,fall"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of seasons.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/registrations/{registrationId}":{"get":{"tags":["Registrations"],"operationId":"getRegistration","summary":"Get a registration season with its catalog","description":"One open or closed season plus its catalog: the groups a family picks from and the classes or memberships in each, with prices. Groups and items the studio has hidden from the public sign-up page are left out here too.","parameters":[{"name":"registrationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The season and its catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationDetailItem"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/membership-types":{"get":{"tags":["Memberships"],"operationId":"listMembershipTypes","summary":"List membership types","description":"Active membership tiers with their active prices — one call is enough to build a memberships page. Each price has its own checkout link.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of membership types.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MembershipTypeList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/appointment-services":{"get":{"tags":["Appointments"],"operationId":"listAppointmentServices","summary":"List appointment services","description":"Active bookable services — private lessons, rentals, auditions. No bookings and no customer data. Only services customers may book themselves carry a `links.book` URL.","parameters":[{"name":"self_service","in":"query","required":false,"schema":{"type":"boolean"},"description":"`true` = only services customers can book themselves online."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of services.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentServiceList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/locations":{"get":{"tags":["Locations"],"operationId":"listLocations","summary":"List locations","description":"Active locations: name, address, contact details and opening hours as the studio entered them.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Rows per page, 1–100."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip. Page through with `offset += limit` while `pagination.has_more` is true."}],"responses":{"200":{"description":"A page of locations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationList"}}}},"400":{"$ref":"#/components/responses/InvalidRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"X-API-Key","description":"A studio's API key. `hg_sk_…` secret keys are for servers; `hg_pk_…` publishable keys may sit in public web pages. Create keys in HotGlue under Settings → API Keys."}},"responses":{"InvalidRequest":{"description":"A parameter is missing, malformed or not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_request","message":"limit must be between 1 and 100"}}}}},"Unauthorized":{"description":"No key, unknown key, or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"unauthorized","message":"Invalid or inactive API key"}}}}},"Forbidden":{"description":"The key does not carry the scope this endpoint needs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"forbidden","message":"This API key does not have the classes:read scope"}}}}},"NotFound":{"description":"No such resource for this studio (also returned for another studio's ids), or no such endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Class not found"}}}}},"MethodNotAllowed":{"description":"The API is read-only. Only GET and OPTIONS are accepted on every path.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"method_not_allowed","message":"The Data API is read-only: use GET."}}}}},"RateLimited":{"description":"Too many requests from this address — 300 per minute per IP. Wait and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"rate_limited","message":"Too many requests"}}}}}},"schemas":{"Error":{"type":"object","description":"Every non-2xx response under /api/v1 has this shape, including unknown paths (404) and write attempts (405).","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_request","unauthorized","forbidden","not_found","method_not_allowed","rate_limited","internal_error"]},"message":{"type":"string","description":"Human-readable, safe to show in logs. Not stable — branch on `code`."}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false},"Me":{"type":"object","properties":{"studio":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string","description":"The studio's slug in its hosted-page URLs."}},"required":["id","name","slug"],"additionalProperties":false},"key":{"type":"object","properties":{"type":{"type":"string","enum":["publishable","secret"]},"prefix":{"type":"string","description":"The start of the key, as shown in Settings → API Keys.","example":"hg_pk_9c5e8a06"},"scopes":{"type":"array","items":{"type":"string","enum":["classes:read","registrations:read","memberships:read","appointments:read","locations:read"]}}},"required":["type","prefix","scopes"],"additionalProperties":false}},"required":["studio","key"],"additionalProperties":false},"MeItem":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Me"}},"required":["data"],"additionalProperties":false},"Pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Rows matching the filters, across all pages."},"limit":{"type":"integer"},"offset":{"type":"integer"},"has_more":{"type":"boolean","description":"True when `offset + limit < total`."}},"required":["total","limit","offset","has_more"],"additionalProperties":false},"CategoryRef":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"color":{"type":["string","null"],"description":"Hex color the studio assigned, e.g. `#FF8800`."}},"required":["id","name","color"],"additionalProperties":false},"LocationRef":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"Instructor":{"type":"object","properties":{"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]}},"required":["first_name","last_name"],"additionalProperties":false},"AgeRange":{"type":"object","properties":{"min":{"type":["integer","null"]},"max":{"type":["integer","null"]}},"required":["min","max"],"additionalProperties":false},"Capacity":{"type":"object","properties":{"max":{"type":["integer","null"],"description":"Seats; null = no cap."},"enrolled":{"type":"integer","description":"Seats held. On a class: students enrolled in any future session plus pending registrations. On a session: students enrolled in or registered for that one session. Show the class number for session-based classes and the session number for drop-ins."},"spots_remaining":{"type":["integer","null"],"description":"`max - enrolled`, never below 0; null when there is no cap. Checkout will not sell past 0."}},"required":["max","enrolled","spots_remaining"],"additionalProperties":false},"DaySchedules":{"type":["object","null"],"description":"Meeting times keyed by day of week (`\"0\"` = Sunday … `\"6\"` = Saturday). Each day lists one or more start times (`HH:MM`, 24-hour) with an optional duration in minutes.","additionalProperties":{"type":"array","items":{"type":"object","properties":{"time":{"type":"string","example":"16:30"},"duration":{"type":["integer","null"]}},"required":["time","duration"],"additionalProperties":false}}},"Class":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"],"description":"Null when the studio left it blank (never an empty string)."},"schedule_type":{"type":"string","enum":["recurring","one-time"]},"schedule_description":{"type":["string","null"],"description":"Ready-to-print weekly schedule, e.g. `Mon & Wed, 4:30 PM`. Null for one-time classes (their date is on the session) and for classes with no weekly pattern."},"day_schedules":{"$ref":"#/components/schemas/DaySchedules"},"duration_minutes":{"type":"integer"},"start_date":{"type":["string","null"],"format":"date","description":"YYYY-MM-DD"},"end_date":{"type":["string","null"],"format":"date","description":"YYYY-MM-DD. May be in the past for a class the studio has not archived yet; filter with `ends_after`."},"enrollment_model":{"type":"string","enum":["session_based","per_instance"],"description":"`session_based`: a family enrolls for the whole run (billed by the class's billing rule). `per_instance`: drop-in, booked session by session — see the instances endpoint."},"enrollment_type":{"type":"string","enum":["open","drop_in","seasonal","closed"],"description":"How a family gets in. `open` and `drop_in`: buy directly via `links.checkout`. `seasonal`: through a registration season (find it under `/registrations`; the class has no checkout link). `closed`: by invitation from the studio only (no checkout link)."},"price_cents":{"type":["integer","null"],"description":"Base price. 0 = free. Integer cents (1999 = $19.99); null when there is no price."},"price_display":{"type":"string","description":"Ready-to-print price, e.g. `$85.00/month` or `Free`."},"billing":{"type":"object","properties":{"is_recurring":{"type":"boolean"},"interval_value":{"type":["integer","null"],"description":"With `interval_unit`, how often a recurring class bills (1 + `months` = monthly). Always set when `is_recurring`; null otherwise."},"interval_unit":{"type":["string","null"],"enum":["weeks","months",null]}},"required":["is_recurring","interval_value","interval_unit"],"additionalProperties":false},"category":{"oneOf":[{"$ref":"#/components/schemas/CategoryRef"},{"type":"null"}]},"location":{"oneOf":[{"$ref":"#/components/schemas/LocationRef"},{"type":"null"}]},"instructors":{"type":"array","items":{"$ref":"#/components/schemas/Instructor"}},"age_range":{"oneOf":[{"$ref":"#/components/schemas/AgeRange"},{"type":"null"}]},"capacity":{"$ref":"#/components/schemas/Capacity"},"featured_image_url":{"type":["string","null"],"format":"uri"},"links":{"type":"object","properties":{"checkout":{"type":["string","null"],"format":"uri","description":"Where to send a family to buy this class. Session-based: hosted checkout for the whole run. Drop-in: the public date picker, which leads to per-session checkout (each session under `/classes/{classId}/instances` also carries its own checkout link). Null when the class is not sold directly (`seasonal`, `closed`)."},"browse":{"type":"string","format":"uri","description":"The studio's public class browser."},"store":{"type":"string","format":"uri","description":"The studio's public store home."}},"required":["checkout","browse","store"],"additionalProperties":false}},"required":["id","name","description","schedule_type","schedule_description","day_schedules","duration_minutes","start_date","end_date","enrollment_model","enrollment_type","price_cents","price_display","billing","category","location","instructors","age_range","capacity","featured_image_url","links"],"additionalProperties":false},"ClassList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Class"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"ClassInstance":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"class_id":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date"},"time":{"type":["string","null"],"description":"`HH:MM:SS` local to the studio."},"duration_minutes":{"type":["integer","null"]},"status":{"type":"string","enum":["scheduled","cancelled","completed"]},"instructor":{"oneOf":[{"$ref":"#/components/schemas/Instructor"},{"type":"null"}]},"capacity":{"$ref":"#/components/schemas/Capacity"},"links":{"type":"object","properties":{"checkout":{"type":["string","null"],"format":"uri","description":"Hosted checkout for this one session. Set only for drop-in classes sold directly; null for session-based, seasonal and closed classes (use the class's own link, or the season)."}},"required":["checkout"],"additionalProperties":false}},"required":["id","class_id","date","time","duration_minutes","status","instructor","capacity","links"],"additionalProperties":false},"ClassInstanceList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ClassInstance"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"Category":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"color":{"type":["string","null"]},"sort_order":{"type":"integer"}},"required":["id","name","color","sort_order"],"additionalProperties":false},"CategoryList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"TrialClass":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"featured_image_url":{"type":["string","null"],"format":"uri"},"price_cents":{"type":"integer","description":"Many trials are free (0); some studios charge a small amount. Integer cents (1999 = $19.99)."},"price_display":{"type":"string"},"session_count":{"type":"integer","description":"Class sessions the trial includes."},"start_restriction":{"type":"string","enum":["any","specific_days"],"description":"`specific_days`: the trial may only start on `valid_start_days`."},"valid_start_days":{"type":["array","null"],"items":{"type":"integer","minimum":0,"maximum":6},"description":"Days of week a trial may start (0 = Sunday), when restricted."},"availability_windows":{"type":"array","items":{"type":"object","properties":{"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"}},"required":["start_date","end_date"],"additionalProperties":false},"description":"Date ranges the offer is available; empty = always."},"class":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"schedule_description":{"type":["string","null"]},"age_range":{"oneOf":[{"$ref":"#/components/schemas/AgeRange"},{"type":"null"}]}},"required":["id","name","schedule_description","age_range"],"additionalProperties":false},{"type":"null"}],"description":"The class the trial attends."},"links":{"type":"object","properties":{"enroll":{"type":["string","null"],"format":"uri","description":"The trial's own sign-up page — the link to put on a \"Book a free trial\" button. Null only if the studio has not generated one yet."},"book":{"type":"string","format":"uri"},"store":{"type":"string","format":"uri"}},"required":["enroll","book","store"],"additionalProperties":false}},"required":["id","name","description","featured_image_url","price_cents","price_display","session_count","start_restriction","valid_start_days","availability_windows","class","links"],"additionalProperties":false},"TrialClassList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TrialClass"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"RegistrationWindow":{"type":"object","properties":{"internal_opens_at":{"type":["string","null"],"format":"date-time","description":"When existing families may register."},"external_opens_at":{"type":["string","null"],"format":"date-time","description":"When the public may register. Null = as soon as the season is open."},"closes_at":{"type":["string","null"],"format":"date-time","description":"Null = until the studio closes it."}},"required":["internal_opens_at","external_opens_at","closes_at"],"additionalProperties":false},"Registration":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"slug":{"type":"string"},"registration_type":{"type":"string","enum":["seasonal","unlimited"]},"status":{"type":"string","enum":["open","closed"]},"season_starts_at":{"type":["string","null"],"format":"date","description":"YYYY-MM-DD"},"season_ends_at":{"type":["string","null"],"format":"date","description":"YYYY-MM-DD"},"registration_window":{"$ref":"#/components/schemas/RegistrationWindow"},"waitlist_enabled":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"},"description":"Tag names the studio put on the season."},"links":{"type":"object","properties":{"register":{"type":"string","format":"uri","description":"The hosted sign-up page for this season."}},"required":["register"],"additionalProperties":false}},"required":["id","name","description","slug","registration_type","status","season_starts_at","season_ends_at","registration_window","waitlist_enabled","tags","links"],"additionalProperties":false},"RegistrationList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Registration"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"CatalogItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"item_type":{"type":"string","enum":["class","membership"]},"name":{"type":"string"},"description":{"type":["string","null"]},"is_required":{"type":"boolean","description":"Every family signing up gets this item."},"price_cents":{"type":["integer","null"],"description":"What the family pays for this item in this season. Integer cents (1999 = $19.99); null when there is no price."},"class":{"oneOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"duration_minutes":{"type":["integer","null"]},"category":{"oneOf":[{"$ref":"#/components/schemas/CategoryRef"},{"type":"null"}]},"location":{"oneOf":[{"$ref":"#/components/schemas/LocationRef"},{"type":"null"}]}},"required":["id","duration_minutes","category","location"],"additionalProperties":false},{"type":"null"}],"description":"Set when `item_type` is `class`."},"membership":{"oneOf":[{"type":"object","properties":{"type_id":{"type":"string","format":"uuid"},"price_id":{"type":["string","null"],"format":"uuid"},"billing_interval":{"type":["string","null"]},"billing_period":{"type":["integer","null"]},"duration_months":{"type":["integer","null"]}},"required":["type_id","price_id","billing_interval","billing_period","duration_months"],"additionalProperties":false},{"type":"null"}],"description":"Set when `item_type` is `membership`."}},"required":["id","item_type","name","description","is_required","price_cents","class","membership"],"additionalProperties":false},"CatalogGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"visibility_mode":{"type":"string","enum":["select","view"],"description":"`select`: the family chooses from these items. `view`: shown for information only."},"sort_order":{"type":"integer"},"items":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}}},"required":["id","name","description","visibility_mode","sort_order","items"],"additionalProperties":false},"RegistrationDetail":{"allOf":[{"$ref":"#/components/schemas/Registration"},{"type":"object","properties":{"billing":{"type":"object","properties":{"interval_value":{"type":["integer","null"]},"interval_unit":{"type":["string","null"]},"first_payment_timing":{"type":["string","null"],"description":"When the first payment is taken relative to sign-up / season start, as configured by the studio."}},"required":["interval_value","interval_unit","first_payment_timing"],"additionalProperties":false},"catalog_groups":{"type":"array","items":{"$ref":"#/components/schemas/CatalogGroup"}}},"required":["billing","catalog_groups"],"additionalProperties":false}]},"RegistrationDetailItem":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RegistrationDetail"}},"required":["data"],"additionalProperties":false},"MembershipPrice":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Monthly"},"amount_cents":{"type":"integer","description":"Amount per charge. Integer cents (1999 = $19.99)."},"currency":{"type":"string","example":"USD"},"price_display":{"type":"string","description":"Ready-to-print, as the studio's store shows it: `$85.00/mo`, `$600.00 one-time`, `$250.00 every 3 months`."},"billing_interval":{"type":"string","enum":["month","year"]},"billing_period":{"type":"integer","description":"How many intervals per charge: 1 + `month` = monthly, 3 + `month` = quarterly, 12 + `month` = annual."},"is_one_time":{"type":"boolean","description":"True = a single charge with no renewal (pay-in-full, or a term shorter than one billing period). False = recurs every `billing_period` intervals until cancelled or the term ends."},"charges":{"type":["integer","null"],"description":"Total charges over the term: 1 for pay-in-full, N for a fixed term, null for ongoing."},"term":{"type":"object","properties":{"mode":{"type":"string","enum":["none","duration","fixed"],"description":"`none` = runs until cancelled. `duration` = ends `months` after it starts. `fixed` = runs between `starts_on` and `ends_on`, the same dates for everyone."},"months":{"type":["integer","null"]},"starts_on":{"type":["string","null"],"format":"date","description":"YYYY-MM-DD"},"ends_on":{"type":["string","null"],"format":"date","description":"YYYY-MM-DD"}},"required":["mode","months","starts_on","ends_on"],"additionalProperties":false},"billing_summary":{"type":"string","description":"One sentence, as checkout shows it, e.g. `$600.00 once, then nothing — 6-month term`."},"links":{"type":"object","properties":{"checkout":{"type":"string","format":"uri","description":"Hosted checkout for this price."}},"required":["checkout"],"additionalProperties":false}},"required":["id","name","amount_cents","currency","price_display","billing_interval","billing_period","is_one_time","charges","term","billing_summary","links"],"additionalProperties":false},"MembershipType":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"benefits":{"type":"array","items":{"type":"string"}},"featured_image_url":{"type":["string","null"],"format":"uri"},"prices":{"type":"array","items":{"$ref":"#/components/schemas/MembershipPrice"}},"links":{"type":"object","properties":{"checkout":{"type":["string","null"],"format":"uri","description":"The first price's checkout, for a single \"Join\" button. Null when the type has no active price."},"detail":{"type":"string","format":"uri","description":"The membership's page in the studio's store."}},"required":["checkout","detail"],"additionalProperties":false}},"required":["id","name","description","benefits","featured_image_url","prices","links"],"additionalProperties":false},"MembershipTypeList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MembershipType"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"AppointmentService":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":["string","null"]},"duration_minutes":{"type":"integer"},"price_cents":{"type":"integer","description":"0 for a free service such as a consultation. Integer cents (1999 = $19.99)."},"price_display":{"type":"string"},"allow_self_service_booking":{"type":"boolean","description":"Customers may book this online themselves."},"booking_approval_required":{"type":"boolean","description":"The studio confirms each booking before it is final."},"min_advance_booking_hours":{"type":["integer","null"]},"max_advance_booking_days":{"type":["integer","null"]},"cancellation_deadline_hours":{"type":["integer","null"]},"featured_image_url":{"type":["string","null"],"format":"uri"},"links":{"type":"object","properties":{"book":{"type":["string","null"],"format":"uri","description":"Hosted booking page. Null when the service is not self-service."}},"required":["book"],"additionalProperties":false}},"required":["id","name","description","duration_minutes","price_cents","price_display","allow_self_service_booking","booking_approval_required","min_advance_booking_hours","max_advance_booking_days","cancellation_deadline_hours","featured_image_url","links"],"additionalProperties":false},"AppointmentServiceList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AppointmentService"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false},"Location":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"address":{"type":["string","null"],"description":"One line, as the studio entered it. Null when blank."},"contact_info":{"oneOf":[{"type":"object","properties":{"email":{"type":["string","null"]},"phone":{"type":["string","null"]}},"required":["email","phone"],"additionalProperties":false},{"type":"null"}]},"operating_hours":{"type":["object","null"],"description":"Free-text hours keyed by weekday name, e.g. `{ \"monday\": \"6:00-22:00\" }`. Shape is whatever the studio entered — render defensively.","additionalProperties":{"type":"string"}}},"required":["id","name","address","contact_info","operating_hours"],"additionalProperties":false},"LocationList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Location"}},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"additionalProperties":false}}}}