Skip to content

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.

  1. 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": true accept online bookings. Full shape: Site payload.

  2. 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" }] }
    ]
    }
    }

    free holds 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 with stepMinutes. See Availability.

  3. 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. devCode appears only on sandbox sites; on a live site the code is sent by SMS or email and the field is absent.

  4. 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.

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 weeks
const 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 received
await fetch(`${API}/public/holds/${hold.holdId}/verify`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
}).then(json);
// 4 · confirm
const { 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);