Skip to content

Getting started

Quickstart

From no key to a booking on the calendar, in four calls. Every request and every response below is real — the responses are the sandbox’s own answers, printed at build time rather than typed out.

1. Create a key

In the console, open Settings → API and MCP and create a key. Tick only the permissions you need — for this walkthrough that is catalog:read, availability:read and bookings:write.

The key is shown once

Only a fingerprint of it is stored, so it cannot be shown again. Copy it when it appears. If you lose it, revoke it and make another.

Or skip this step for now

Every call below also works against the sandbox with a published demo key and no account at all. Same paths, same envelope, fixtures instead of a database.

2. Confirm who the key belongs to

GET /me is the call to make first. It names the business, the timezone every date parameter is read in, the currency prices are quoted in, and the exact scopes this key carries — so you discover your permissions once instead of by being refused.

curl https://www.lovingbooking.com/api/v1/me \
  -H "Authorization: Bearer lb_sk_YOUR_KEY"
Response
{
  "data": {
    "business": {
      "id": 1,
      "name": "Still Point Yoga",
      "slug": "still-point-yoga",
      "timezone": "Asia/Singapore",
      "currency": "SGD",
      "bookingPageUrl": "/book/still-point-yoga"
    },
    "clock": {
      "now": "2026-09-09T16:00:00.000Z",
      "nowLocal": "2026-09-10T00:00:00+08:00",
      "today": "2026-09-10",
      "timezone": "Asia/Singapore"
    },
    "key": {
      "id": 0,
      "scopes": [
        "catalog:read",
        "availability:read",
        "bookings:read",
        "bookings:write",
        "customers:read"
      ]
    },
    "plan": {
      "name": "Pro",
      "trialEndsAt": null,
      "trialDaysRemaining": null
    },
    "quota": {
      "limit": 10000,
      "used": 1,
      "remaining": 9999,
      "resetsAt": "daily at 00:00 UTC"
    },
    "notes": {
      "payments": "LovingBooking does not process booking payments. Prices are what a booking is WORTH, not money received; there is no card rail or capture in this API, and it takes no deposit: one a business asks for by its own QR goes to the business directly and is checked by hand."
    }
  },
  "sandbox": "Sandbox data. These are fixtures for a made-up business: nothing here is a real booking, and nothing you send is stored."
}

3. Find something to book

A booking needs a service, a team member and a venue. Read them from the catalogue rather than hard-coding ids — they differ per business, and an id from another account is refused with a 404.

curl https://www.lovingbooking.com/api/v1/services \
  -H "Authorization: Bearer lb_sk_YOUR_KEY"

curl https://www.lovingbooking.com/api/v1/staff \
  -H "Authorization: Bearer lb_sk_YOUR_KEY"

curl https://www.lovingbooking.com/api/v1/locations \
  -H "Authorization: Bearer lb_sk_YOUR_KEY"
Response — GET /services
{
  "data": [
    {
      "id": 1010,
      "name": "Morning Vinyasa",
      "description": "A 60-minute flow for all levels. Mats provided.",
      "durationMinutes": 60,
      "priceCents": 3200,
      "currency": "SGD",
      "categoryId": null,
      "locationId": null,
      "maxCapacity": 12,
      "bufferBeforeMinutes": 0,
      "bufferAfterMinutes": 0,
      "isActive": true,
      "hidden": false,
      "options": []
    },
    {
      "id": 1011,
      "name": "Private session (1:1)",
      "description": "One to one, shaped around your body and your week.",
      "durationMinutes": 75,
      "priceCents": 14000,
      "currency": "SGD",
      "categoryId": null,
      "locationId": null,
      "maxCapacity": 1,
      "bufferBeforeMinutes": 0,
      "bufferAfterMinutes": 15,
      "isActive": true,
      "hidden": false,
      "options": []
    }
  ],
  "sandbox": "Sandbox data. These are fixtures for a made-up business: nothing here is a real booking, and nothing you send is stored."
}

4. Ask what times are open

Availability is computed with the same rules as the public booking page. Pass the service you intend to book: without one, the first active service is used and the response says serviceAssumed: true — the times are real, but they are for a service you did not name.

curl https://www.lovingbooking.com/api/v1/availability?date=2026-09-10&serviceId=1010 \
  -H "Authorization: Bearer lb_sk_YOUR_KEY"
Response
{
  "data": {
    "date": "2026-09-10",
    "dayName": "Thursday",
    "timezone": "Asia/Singapore",
    "service": {
      "id": 1010,
      "name": "Morning Vinyasa",
      "durationMinutes": 60,
      "maxCapacity": 12
    },
    "location": {
      "id": 1001,
      "name": "River Valley studio"
    },
    "staff": [
      {
        "id": 1020,
        "name": "Amara Sim"
      },
      {
        "id": 1021,
        "name": "Josh Tan"
      }
    ],
    "serviceAssumed": false,
    "slots": [
      {
        "startsAt": "2026-09-09T23:00:00.000Z",
        "startsAtLocal": "2026-09-10T07:00:00+08:00",
        "label": "7:00 AM",
        "spotsLeft": 4,
        "staffIds": [
          1020,
          1021
        ]
      },
      {
        "startsAt": "2026-09-10T01:30:00.000Z",
        "startsAtLocal": "2026-09-10T09:30:00+08:00",
        "label": "9:30 AM",
        "spotsLeft": 12,
        "staffIds": [
          1020,
          1021
        ]
      },
      {
        "startsAt": "2026-09-10T04:00:00.000Z",
        "startsAtLocal": "2026-09-10T12:00:00+08:00",
        "label": "12:00 PM",
        "spotsLeft": 9,
        "staffIds": [
          1020,
          1021
        ]
      },
      {
        "startsAt": "2026-09-10T10:30:00.000Z",
        "startsAtLocal": "2026-09-10T18:30:00+08:00",
        "label": "6:30 PM",
        "spotsLeft": 2,
        "staffIds": [
          1020,
          1021
        ]
      }
    ],
    "dayStatus": "open",
    "beyondPublicBookingHorizon": false,
    "publicBookingHorizonDays": 30,
    "snapshot": "Availability is not held. It is rechecked when the booking is created."
  },
  "sandbox": "Sandbox data. These are fixtures for a made-up business: nothing here is a real booking, and nothing you send is stored."
}

A snapshot, not a hold

Nothing here reserves the slot. The time is checked again when the booking is created, so a time you read and post a minute later can still be refused. Handle the 409 rather than assuming the read was a claim.

Each slot’s staffIds names the team members free at that start. Send the slot’s startsAt and one of those ids when you book, and you never have to ask again per person.

5. Create the booking

Give either customerId for an existing client or customer to create one; give neither for a walk-in with no client attached. A confirmation email is sent by default — pass sendConfirmationEmail: false if your integration sends its own.

curl https://www.lovingbooking.com/api/v1/bookings \
  -X POST \
  -H "Authorization: Bearer lb_sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "serviceId": 1010,
  "staffId": 1020,
  "locationId": 1001,
  "startsAt": "2026-09-09T23:00:00.000Z",
  "customer": {
    "name": "Priya Menon",
    "email": "priya@example.com"
  }
}'

A 201 returns the booking. A refusal writes nothing, and its status says what kind it is: 404 for an id that is not on the account, 422 for ids that do not go together or a start that has already passed, 409 when the diary refuses the time — another booking, a buffer, working hours, blocked time. details.reason names the exact case and details.forceable says whether resending with force: true is an answer; do that only if the owner has actually decided to book over it. A one-to-one double-booking is refused either way.

Response — the conflict, so you have seen one
{
  "error": {
    "code": "conflict",
    "message": "That time cannot be booked: Already booked at this time (appointment #1100)",
    "details": {
      "reason": "staff_busy",
      "forceable": true,
      "parameter": "startsAt",
      "warnings": [
        {
          "kind": "staff_busy",
          "message": "Already booked at this time (appointment #1100)",
          "conflictingAppointmentId": 1100
        }
      ]
    }
  },
  "sandbox": "Sandbox data. These are fixtures for a made-up business: nothing here is a real booking, and nothing you send is stored."
}

Next

Authentication covers scopes and revocation, Errors and limits covers what to retry, and the reference lists every endpoint with a console you can send requests from. To let an assistant do all of this for you, see Connect an AI agent.