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
| Function | What it does |
|---|---|
search | ClassPass'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… |
getStudio | Reads ONE ClassPass studio's whole profile in a single request — the page a person reads to decide whether a result is worth booking. |
getSchedule | Reads 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,
};