Quickstart
Four calls take a guest from “when is the room free?” to a confirmed booking. Everything below runs against the demo studio, which is in sandbox mode: verification codes come back in the response, nothing is sent, and bookings vanish after a week — so you can script the whole flow.
-
Read the site and pick a room
Terminal window curl -s https://api.mrcharles.app/api/v1/public/sites/demo-studio{"success": true,"data": {"studio": { "name": "Studio Uno", "slug": "demo-studio", "timezone": "Europe/Madrid", "city": "Madrid" },"rooms": [{"id": "d9a36c09-d151-5d4a-a077-296293b07be4","name": "Rehearsal B","hourlyCents": 2500, "currency": "EUR","bookable": true,"minMinutes": 60, "maxMinutes": 240, "stepMinutes": 60,"paymentModes": ["on_site"], "depositPercent": 100, "cancelNoticeHours": 24}],"bookingOnline": true,"cardPayments": true}}Only rooms with
"bookable": trueaccept online bookings. Full shape: Site payload. -
Ask when it is free
Terminal window curl -s "https://api.mrcharles.app/api/v1/public/sites/demo-studio/rooms/d9a36c09-d151-5d4a-a077-296293b07be4/availability?from=2026-10-08&to=2026-10-10"{"success": true,"data": {"roomId": "d9a36c09-d151-5d4a-a077-296293b07be4","timezone": "Europe/Madrid","hourlyCents": 2500, "currency": "EUR","minMinutes": 60, "maxMinutes": 240, "stepMinutes": 60,"minNoticeMin": 60, "maxAdvanceDays": 90,"days": [{ "date": "2026-10-08", "free": [{ "start": "09:00", "end": "22:00" }] },{ "date": "2026-10-09", "free": [{ "start": "09:00", "end": "23:00" }] }]}}freeholds continuous open spans in the studio’s time zone, already excluding other bookings, live holds, buffers, the minimum notice and the horizon. Slice them into start times withstepMinutes. See Availability. -
Hold the slot and send the code
Terminal window curl -s -X POST https://api.mrcharles.app/api/v1/public/sites/demo-studio/holds \-H 'Content-Type: application/json' \-d '{"roomId": "d9a36c09-d151-5d4a-a077-296293b07be4","date": "2026-10-08","startTime": "17:00","durationMinutes": 120,"guestName": "Marta Ruiz","channel": "email","contact": "marta@example.com","locale": "es","idempotencyKey": "6f1c0e3a-6b0e-4f0c-9f1a-2a8f1b7c4d55"}'{"success": true,"data": {"holdId": "b59b9109-1beb-44fa-a679-c2d612844c62","status": "pending","roomName": "Rehearsal B","date": "2026-10-08", "startTime": "17:00", "endTime": "19:00","timezone": "Europe/Madrid","price": { "priceCents": 5000, "depositCents": 5000, "currency": "EUR", "minutes": 120 },"channel": "email","contactMasked": "m••••@example.com","expiresAt": "2026-10-06T12:26:23+02:00","resendsLeft": 2,"paymentModes": ["on_site"],"devCode": "181989"}}The slot is now yours for 5 minutes while the guest enters the code; verifying extends it to 12 minutes after creation (count down from
expiresAt). The price came back from the server — never compute it yourself.devCodeappears only on sandbox sites; on a live site the code is sent by SMS or email and the field is absent. -
Verify the code, then confirm
Terminal window curl -s -X POST https://api.mrcharles.app/api/v1/public/holds/b59b9109-.../verify \-H 'Content-Type: application/json' -d '{"code":"181989"}'curl -s -X POST https://api.mrcharles.app/api/v1/public/holds/b59b9109-.../confirm \-H 'Content-Type: application/json' \-d '{"paymentMode":"on_site","notes":"Trio, we bring our own cymbals.","email":"marta@example.com"}'{"success": true,"data": {"booking": {"id": "96f52eb7-3360-486a-8d04-47b1663c049d","reference": "MC-TGRMY5","status": "confirmed","paymentMode": "on_site", "paymentStatus": "unpaid","studioName": "Studio Uno", "roomName": "Rehearsal B","date": "2026-10-08", "startTime": "17:00", "endTime": "19:00","startsAt": "2026-10-08T17:00:00+02:00","priceCents": 5000, "currency": "EUR","cancelNoticeHours": 24, "mayCancel": true},"manageToken": "1000bb60…47da3a0","manageUrl": "https://demo-studio.sites.mrcharles.app/r/1000bb60…"}}Done. Show the reference, and give the guest the
manageUrl— it is the only way back to the booking.
The same flow in JavaScript
Section titled “The same flow in JavaScript”const API = 'https://api.mrcharles.app/api/v1';const SLUG = 'demo-studio'; // sandbox — swap for the real slug at launch
const json = async (res) => { const body = await res.json(); if (!res.ok) throw Object.assign(new Error(body.error?.message ?? res.statusText), { code: body.error?.code }); return body.data;};
// 1 · availability for the next two weeksconst from = new Date().toISOString().slice(0, 10);const to = new Date(Date.now() + 13 * 864e5).toISOString().slice(0, 10);const avail = await fetch(`${API}/public/sites/${SLUG}/rooms/${roomId}/availability?from=${from}&to=${to}`).then(json);
// 2 · hold (one idempotency key per attempt — reuse it when retrying)const idempotencyKey = crypto.randomUUID();const hold = await fetch(`${API}/public/sites/${SLUG}/holds`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roomId, date: '2026-10-08', startTime: '17:00', durationMinutes: 120, guestName, channel: 'phone', contact: '+34600111222', locale: 'es', idempotencyKey, captchaToken, }),}).then(json);
// 3 · verify the 6-digit code the guest receivedawait fetch(`${API}/public/holds/${hold.holdId}/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }),}).then(json);
// 4 · confirmconst { booking, manageUrl } = await fetch(`${API}/public/holds/${hold.holdId}/confirm`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentMode: 'on_site', notes, email }),}).then(json);// A hold from your server counts against your server's address: every// guest of your site shares that one rate-limit bucket.const res = await fetch(`${API}/public/sites/${SLUG}/holds`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload),});Rate limits are counted per connecting address, and forwarding headers such as X-Forwarded-For are ignored. If every call arrives from one server address, your busiest hour will hit the limit for everyone, so prefer the browser tab for availability and holds. See Errors & rate limits.
- Choose your integration — hosted, embedded or custom.
- Build your own booking UI — the complete flow with error handling.
- Errors & rate limits — what each code means and what to show.