# Mr Charles integration brief (for the assistant building this site) You are building a website for a music studio / teacher whose bookings run on Mr Charles. Do NOT build a booking system, calendar, OTP verification, or payment flow yourself: all of that exists behind a public API and a JavaScript SDK, and the site only has to call it. Prices, availability, conflicts, codes and card payments are decided by the server; the site never computes them. ## Values provided by the site owner (never hardcode, never guess) - STUDIO_SLUG = # the studio's slug on Mr Charles - TEACHER_USER_ID = # the teacher's user id, if lessons are offered - MC_ENDPOINTS: apiBase= appBase= sitesBase= Put these in environment variables (e.g. PUBLIC_MC_API_BASE, PUBLIC_MC_APP_BASE, PUBLIC_MC_SITES_BASE, PUBLIC_MC_STUDIO_SLUG, PUBLIC_MC_TEACHER_ID) and read them at build time. Do not commit them. If an endpoint value is missing, the SDK defaults to production. ## The SDK Browser bundle (no build step): const mc = MrCharles.createClient({ apiBase, appBase, sitesBase }); ES module / npm: import { createClient } from '@mrcharles/sdk'; // or /sdk/v0/mrcharles.esm.js Zero dependencies, TypeScript types included. Reference: https://developers.mrcharles.app/sdk/reference Languages: createClient({ locale }) with the page's language ('es', 'ca', 'en', 'fr', 'pt', 'it', 'de' or any BCP-47 tag). It sets the hosted screen's language, the guest's SMS/email, and gives you mc.t(), mc.format.* and mc.pick() for your own labels, prices, dates and payload texts. For a multilingual site call mc.setLocale() from the language switcher. Do not hardcode lang: 'es' — pass the current page language. ## What the site must do 1. Rooms page — read the studio payload at build time or on request: const site = await mc.sites.get(STUDIO_SLUG); site.studio (name, city, timezone), site.rooms[] (id, name, hourlyCents, currency, bookable, minMinutes, maxMinutes, stepMinutes, paymentModes, depositPercent, cancelNoticeHours), site.site.sandbox (boolean). Render prices from hourlyCents/currency. Only rooms with bookable=true get a booking button. 2. Booking — the simplest correct integration is the embedded screen: mc.mountRoomBooking('#book', { slug: STUDIO_SLUG, roomId, onBooked: ({ reference, manageUrl }) => { /* thank-you + analytics */ } }); (language comes from createClient({ locale }); pass lang only to override) It is an iframe of the hosted booking page; it handles availability, the 12-minute hold, the one-time code (SMS/email), card deposits (Stripe) or pay-at-studio, legal texts and the manage link. It resizes itself. Alternative: a plain link to mc.links.bookRoom({ slug, roomId }). Only build a custom UI (mc.roomBooking() headless flow) if the owner asks for it explicitly; see https://developers.mrcharles.app/integrate/own-frontend 3. Lessons (only if TEACHER_USER_ID is set): const profile = await mc.teachers.profile(TEACHER_USER_ID); const offers = await mc.teachers.offers(TEACHER_USER_ID); // published only const slots = await mc.teachers.slots({ instructorUserId: TEACHER_USER_ID, offerId, date: 'YYYY-MM-DD', days: 7 }); Show offers (title, instrument, durationMinutes, priceCents, currency, mode) and real bookable times; the booking itself happens in the app: mc.links.bookLesson({ instructorId: TEACHER_USER_ID, offerId }) Students need an account there — do not try to book a lesson from the site. 4. Manage link — show/email the manageUrl you receive on booking; it is the guest's way to see or cancel. Never implement cancellations yourself. ## Rules - No API key exists; the slug is the only identifier. Public endpoints are rate-limited; writes on live sites require a Turnstile captcha token — the embedded screen handles it, a custom UI must include the widget. - Never cache availability or slots; fetch when the user opens the picker. The site payload may be cached for 60 s. - All times are in the studio's/teacher's time zone (site.studio.timezone). - Errors come as { success:false, error:{ code, message } }; the SDK throws MrCharlesError with .code, .slotIsGone, .holdIsDead, .isTransient. - Do not store guest phone numbers or emails on the site. - Keep the site static (Astro is ideal). The SDK runs in the browser; the payload reads can also run at build time in Node (pass fetch if needed). ## Develop against the sandbox, then switch the slug While building, use slug "demo-studio" (always in sandbox mode: verification codes come back in the response as devCode, nothing is sent or charged, bookings are purged after 7 days). Switching to STUDIO_SLUG is the whole go-live. Before that, go through https://developers.mrcharles.app/getting-started/go-live ## Suggested structure (Astro) src/lib/mrcharles.ts createClient() from env; getSite(); getTeacher() src/pages/index.astro hero, rooms grid (prices from the payload), CTA src/pages/salas/[id].astro one room: details +
+ mount script src/pages/clases.astro teacher offers + next free slots + "Book in the app" src/components/Book.astro the script tag + mountRoomBooking call ## Acceptance - Rooms and prices on the site match https://./ exactly. - A booking made on the site appears in the studio's Mr Charles inbox. - The sandbox banner/devCode never appears once STUDIO_SLUG is live. - Lighthouse: no layout shift from the iframe (it reports its height). Full docs: https://developers.mrcharles.app — API: /reference/api