Bowmark AIdocs

ClassPass

ClassPass — fitness, wellness and beauty classes across gyms, studios, spas and salons.

ClassPass — fitness, wellness and beauty classes across gyms, studios, spas and salons. getStudio reads one studio's whole profile in a single request: what it does, where it is, its rating, amenities, photos, contact routes and the practical booking prose. getSchedule reads that studio's bookable timetable for a day or a week: every session with its start time, instructor, duration, credit price and whether it is still open. Studio and class search, per-slot availability and membership pricing are declared but not built yet.

Domain: classpass.com

Also known as: ClassPass, Class Pass, ClassPass Inc.

Call it directly

bowmark.providers.classpass.search(query: ClasspassSearchQuery): Promise<ClasspassSearchResult>
bowmark.providers.classpass.getStudio(studio: number | string): Promise<ClasspassStudio>
bowmark.providers.classpass.getSchedule(studio: number | string, options?: ClasspassScheduleOptions): Promise<ClasspassSchedule>

Functions

FunctionWhat it does
searchClassPass's own location search — read back the fitness, wellness AND beauty venues within query.radius of query.lat/query.lon, with their identity, coordinates, the activities each…
getStudioReads ONE ClassPass studio's whole profile in a single request — the page a person reads to decide whether a result is worth booking.
getScheduleReads ONE ClassPass studio's bookable timetable — what a person can actually book there, and when.

Types

/** Everything /v2/venues publishes about one studio — a superset of
 *  ClasspassVenue, so anything holding one can hold the other. */
interface ClasspassStudio extends ClasspassVenue {
  /** What the studio does. On THIS function it comes from the venue record's own
   *  tag block, so it is correct even on a day the studio publishes no classes. */
  activities: string[];
  /** ISO-3166 alpha-2, e.g. "US", "GB". */
  country: string | null;
  /** The site's neighbourhood label ("South Charlotte"). Null where it publishes none. */
  neighborhood: string | null;
  /** The metro area the studio is sold in ("Charlotte Metro", "London Metro"). */
  metroArea: string | null;
  description: string | null;
  /** The studio's OWN site, not its ClassPass page. Null where it publishes none. */
  website: string | null;
  phone: string | null;
  instagram: string | null;
  facebook: string | null;
  twitter: string | null;
  /** Real photographs, largest first. ClassPass's generic fallback placeholders are
   *  dropped — a placeholder passed off as the studio is worse than nothing. */
  photos: string[];
  logo: string | null;
  /** Only the attributes the studio asserts, e.g. ["lgbtq_friendly",
   *  "wheelchair_accessible"]. A false flag means "not claimed" and is dropped. */
  inclusivity: string[];
  bookingWindow: string | null;
  whenToArrive: string | null;
  whatToBring: string | null;
  howToGetThere: string | null;
  proTip: string | null;
  cancellationPolicy: string | null;
  /** null where the site holds no policy (it sends the sentinel "UNKNOWN"). */
  lateCancellation: string | null;
  demandSignals: string[];
  /** ClassPass's own published fraction (0-1). Returned under the site's own name
   *  and NOT relabelled a saving — the site does not document its basis. There is
   *  no credit price on this record; per-class cost is getSchedule's `credits`. */
  averageDiscount: number | null;
  outOfNetwork: boolean;
  spots: number | null;
}

interface ClasspassSchedule {
  venue: ClasspassVenue;
  /** Dates covered, YYYY-MM-DD in the venue's zone, ascending. A day with no
   *  sessions still appears — "nothing published" is an answer. */
  dates: string[];
  sessions: ClasspassSession[];
}

interface ClasspassVenue {
  id: number;
  alias: string;
  name: string;
  /** The qualifier the site prints after the name ("Charlotte") — how a chain's
   *  branches are told apart. */
  subtitle: string | null;
  /** IANA zone, e.g. "America/New_York". Every startTime is an absolute instant;
   *  this is what renders one as the wall-clock time a person would read. */
  timeZone: string | null;
  street: string | null;
  city: string | null;
  state: string | null;
  postalCode: string | null;
  latitude: number | null;
  longitude: number | null;
  activities: string[];
  amenities: string[];
  ratingAverage: number | null;
  ratingCount: number | null;
}

interface ClasspassSession {
  /** Stable id of THIS session. */
  id: number;
  /** The recurring class it is an instance of — shared by every occurrence, so it
   *  is not a session key. */
  classId: number | null;
  name: string;
  classAlias: string | null;
  activities: string[];
  level: string | null;
  description: string | null;
  /** Absolute instant, ISO-8601 UTC. */
  startTime: string;
  endTime: string | null;
  /** The calendar date AT THE VENUE, YYYY-MM-DD. */
  localDate: string;
  durationMinutes: number | null;
  instructor: string | null;
  /** Credits to book. null where the site withholds a price — never 0-as-free. */
  credits: number | null;
  /** The site's own word: "available", "waitlist", "full", … */
  availability: string | null;
  isLivestream: boolean;
  demandSignals: string[];
}

interface ClasspassScheduleOptions {
  /** First day, YYYY-MM-DD. Defaults to today AT THE VENUE. */
  date?: string;
  /** Consecutive days from date, 1-7 (each day is one live request). Default 1. */
  days?: number;
}

interface ClasspassSearchQuery {
  /** Centre of the search, decimal degrees. */
  lat: number;
  lon: number;
  /** Search radius (default 1, clamped to 1-50); turned into a bounding box —
   *  the origin takes no radius field of its own on this route. */
  radius: number;
  /** "mi" (default) or "km". */
  radiusUnits?: "mi" | "km";
  /** First day, YYYY-MM-DD. Defaults to TODAY (UTC date). */
  date?: string;
}

/** One row of a location search — NOT a ClasspassVenue. This endpoint publishes
 *  a smaller, differently-shaped record (no address, no time zone, no
 *  amenities); follow up with getStudio(alias) for the full profile. */
interface ClasspassSearchVenue {
  id: number;
  alias: string;
  name: string;
  /** The qualifier for a chain branch ("South Charlotte"). Absent on most
   *  independent studios in this response — a gap in what the endpoint
   *  publishes, not a parsing miss. */
  locationName: string | null;
  description: string | null;
  activities: string[];
  /** Null when the site has nothing to show yet. */
  ratingAverage: number | null;
  /** The site's OWN display string ("30000+", "3", "0") — never coerced to a
   *  number, since past a threshold the site itself only publishes a floor. */
  ratingCountDisplay: string | null;
  latitude: number;
  longitude: number;
  /** Largest real photo; ClassPass's shared placeholder is dropped. */
  photo: string | null;
}

interface ClasspassSearchResult {
  results: ClasspassSearchVenue[];
  /** The origin's own session id for this search. */
  searchId: string;
  /** What this function did to the caller's query (defaults applied). */
  warnings: string[];
}

Examples

// Nearby yoga studios rated 4.7+, with their alias for a getStudio follow-up.
const { results } = await bowmark.providers.classpass.search({
  lat: 35.2068515,
  lon: -80.8616703,
  radius: 5,
});
return results
  .filter((v) => v.activities.some((a) => /yoga/i.test(a)) && (v.ratingAverage ?? 0) >= 4.7)
  .map((v) => ({ name: v.name, alias: v.alias, rating: v.ratingAverage, reviews: v.ratingCountDisplay }));
// Is this studio worth booking? The whole profile, one request.
const s = await bowmark.providers.classpass.getStudio("barrys-charlotte");
return {
  studio: `${s.name} ${s.subtitle ?? ""}`.trim(),
  does: s.activities,
  where: [s.street, s.city, s.state, s.postalCode].filter(Boolean).join(", "),
  neighborhood: s.neighborhood,
  rated: s.ratingAverage ? `${s.ratingAverage.toFixed(2)} from ${s.ratingCount}` : null,
  amenities: s.amenities,
  arrive: s.whenToArrive,
  booking: s.bookingWindow,
  photo: s.photos[0] ?? null,
};