Passa al contenuto principale

Widget API

Auto-generated from the OpenAPI spec. Run node docs-site/scripts/fetch-openapi.js to regenerate.

GET /api/v1/widget/{public_studio_id}/schedule

Get Widget Schedule

Public upcoming-class schedule for one studio's embeddable widget.

IP-keyed rate limit (get_remote_address, same key_func as the unauthenticated calendar feed at calendar_sync.py) since there is no JWT here to key on.

Parameters

NameInRequiredTypeDescription
public_studio_idpathYesstring

Responses

  • 200 — Successful Response
  • 422 — Validation Error

GET /api/v1/widget/{public_studio_id}/membership-types

Get Widget Membership Types

Public membership-plan catalog for one studio's embeddable widget.

Only plans a manager has explicitly marked sellable_online are exposed — the same gate create_checkout_session (app/routers/ stripe_billing.py) enforces at purchase time via MEMBERSHIP_TYPE_NOT_ONLINE, so this list only ever shows plans that can actually be bought.

Parameters

NameInRequiredTypeDescription
public_studio_idpathYesstring

Responses

  • 200 — Successful Response
  • 422 — Validation Error

GET /api/v1/widget/{public_studio_id}/appointment-services

Get Widget Appointment Services

Public appointment-service catalog for one studio's embeddable widget.

Scoped via AppointmentService.location_id — the multi-tenancy partition key, same as get_widget_schedule's ScheduledClass.location_id filter. Not to be confused with establishment_ids (AppointmentServiceLocation, app/models/appointment_service_location.py): that is a within-studio branch concept used for instructor-availability matching. A service with zero establishment links is still offered "everywhere" within its own studio, so that linkage is never used here to filter a service out.

Parameters

NameInRequiredTypeDescription
public_studio_idpathYesstring

Responses

  • 200 — Successful Response
  • 422 — Validation Error

GET /api/v1/widget/{public_studio_id}/products

Get Widget Products

Public retail-product catalog for one studio's embeddable widget.

Filtered to active, in-stock products only — a client browsing the widget before logging in should never see something they can't actually buy right now (app/routers/products.py's manager-only catalog has no such filter since managers need to see everything).

Parameters

NameInRequiredTypeDescription
public_studio_idpathYesstring

Responses

  • 200 — Successful Response
  • 422 — Validation Error

GET /api/v1/widget/{public_studio_id}/products/{product_id}/photo

Get Widget Product Photo

Public product-photo file for one studio's embeddable widget.

GET /api/v1/photos/{filename} (app/routers/photos.py) requires Depends(require_authenticated) because it also serves client/instructor profile photos, which must stay non-public. Rather than loosen that endpoint's auth (a privacy regression for profile photos), this is a separate, narrowly-scoped public endpoint for product photos only.

The filename is never taken from the request — it is resolved from the studio's own Product row (scoped by public_studio_id -> location_id and product_id), same pattern as every other endpoint in this router. As defense in depth (docs/SECURITY_GUIDELINES.md §4.1), the resolved filename must both pass resolve_safe_photo_path's sanitization/ containment check (shared with photos.py) AND start with the "product_" prefix that validate_and_save_photo (app/routers/products.py's upload_product_photo) always gives product photos — in case photo_path was ever set to something unexpected. Any mismatch is a generic 404, not a 500.

Parameters

NameInRequiredTypeDescription
public_studio_idpathYesstring
product_idpathYesinteger

Responses

  • 200 — Successful Response
  • 422 — Validation Error

POST /api/v1/widget/{public_studio_id}/products/{product_id}/checkout-session

Create Widget Product Checkout Session

Create a Stripe Checkout Session for a logged-in client to buy one retail product from the public widget.

Auth: client JWT required — mirrors create_checkout_session (app/routers/stripe_billing.py) at a smaller scale: decode the token, require type == "access". Unlike create_checkout_session there is no "manager buying on behalf of a client" case here and no client_id in the request body at all — the purchasing client is always the token's own sub, so there is nothing for a malicious caller to substitute (IDOR angle, docs/SECURITY_GUIDELINES.md §2).

No StripeCheckoutSession pending-row is created (unlike the membership flow, whose StripeCheckoutSession.membership_type_id FK is NOT NULL and not applicable here). Instead, like the gift-card self-purchase flow (_handle_gift_card_checkout_completed in app/routers/stripe_billing.py), everything needed to complete the sale on webhook confirmation travels in the Stripe session's own metadata (purchase_type="product") — see _handle_product_checkout_completed for the matching webhook handler.

Parameters

NameInRequiredTypeDescription
public_studio_idpathYesstring
product_idpathYesinteger

Request body

FieldRequiredTypeDescription
success_urlYesstring
cancel_urlYesstring

Responses

  • 200 — Successful Response
  • 422 — Validation Error