Skip to main content

Recipes

Worked examples for the pages most integrations need. All of them assume a small api() helper that adds the Authorization header — see Handling a rotated token without downtime for one.


A course catalogue page​

One request. Group client-side by upcoming_class.type if you want in-person and online listings separated.

const { data: courses } = await api("/v2/public/courses");

A "find a class" search page​

const params = new URLSearchParams({ limit: "25", page: String(page) });
if (query) params.set("search", query);
if (fromDate) params.set("start", format(fromDate, "MM-dd-yyyy"));

const { data, total, last_page } = await api(`/v2/public/classes?${params}`);

Build paging controls from last_page; show total as the result count.


A course detail page with its sittings​

Two requests, safe to run in parallel:

const [course, classes] = await Promise.all([
api(`/v2/public/course/${id}`),
api(`/v2/public/course/${id}/classes?limit=50`),
]);

Rendering availability​

Remember that null means uncapped, not sold out — see Seat counts.

function availability(cls) {
if (cls.available_seats === null)
return { label: "Open enrolment", canBook: true };
if (cls.available_seats === 0) return { label: "Class full", canBook: false };
if (cls.available_seats <= 3)
return { label: `Only ${cls.available_seats} seats left`, canBook: true };
return { label: `${cls.available_seats} seats available`, canBook: true };
}

Handling a rotated token without downtime​

Read the token per request from a secret store or environment variable, and treat a 401 as a signal to re-read configuration once before failing:

async function api(path, { retried = false } = {}) {
const res = await fetch(`${API_BASE}${path}`, {
headers: { Authorization: `Bearer ${await getToken()}` },
});

if (res.status === 401 && !retried) {
await refreshTokenFromSecretStore();
return api(path, { retried: true });
}

if (!res.ok) throw new ApiError(res.status, await res.text());
return res.json();
}