# Booking API for developers: how to integrate appointment scheduling into your app

> How booking APIs work — authentication, availability, creating bookings — with a working integration on the Opencals Storefront API.

URL: https://opencals.com/blog/booking-api
Author: Stanislav Tyshchenko
Published: 2026-08-29
Updated: 2026-09-05
Categories: engineering, tutorial

Adding booking to an app sounds simple until you start building. Real-time availability across multiple staff and locations, cart state, payment handling, post-booking management — each piece is more complex than it looks from the outside. A booking API lets you hand off that complexity to a specialist backend and focus on the frontend.

This guide covers what a booking API actually does, what to look for when choosing one, and how the Opencals Storefront API is structured — with working code examples.

## What a booking API needs to do

Not all booking APIs are equal. A real-time scheduling API needs to handle several distinct domains:

- **Services** — What can be booked — products, variants, pricing
- **Availability** — Real-time slot calculation across staff + locations
- **Cart + Checkout** — Session state, payments, confirmation
- **Self-service** — Customer reschedule, cancel, history

Some APIs cover only availability lookup and booking creation. You have to wire up the cart, payment, and post-booking management yourself. A complete booking API handles the full flow, and that gap is what decides how much your team ends up building.

## What to look for in a booking API

1. **Full journey coverage**: The API should cover the complete customer flow: browse services → pick staff and location → check availability → add to cart → checkout and pay → manage booking. Missing pieces mean you're building or bolting on additional infrastructure.
2. **Real-time availability**: Availability needs to account for existing bookings, staff working hours, buffer time between appointments, and multi-staff conflicts. This logic is hard to get right. Look for an API that exposes clean availability endpoints rather than raw calendar data you have to process yourself.
3. **Multi-staff and multi-location**: If your service business has more than one practitioner or site, the API needs to support per-staff schedules, per-location hours, and service-staff assignments. Availability queries need to filter correctly across these dimensions.
4. **OpenAPI / TypeScript support**: An OpenAPI spec and a typed TypeScript SDK mean you don't have to guess at request/response shapes. Type safety from the API call to the UI is worth a lot in a domain with complex nested objects (booking slots, cart line items, payment states).
5. **Idempotent checkout**: Payment flows fail in the real world. The checkout endpoint should be idempotent — repeating the same request should not create duplicate bookings or double charges.
6. **Webhooks for post-booking events**: Your backend often needs to react to booking events: appointment created, order paid, booking cancelled. Signed webhook events let you update your database, send custom notifications, or trigger downstream workflows without polling.

## The Opencals Storefront API

The Opencals Storefront API is a REST API for building custom booking experiences. It covers the complete booking flow and is the same API that powers the Opencals Shopify app and dashboard.

**Base URL:** `https://api.opencals.com`\
**Auth:** `Authorization: Bearer sfk_your_api_key`\
**Format:** JSON throughout\
**Spec:** [OpenAPI 3.0 — interactive playground at opencals.com/docs](https://opencals.com/docs/api-reference/introduction)

### Authentication

API keys are scoped to a single store. You get one from the Opencals dashboard under Settings → API. The key prefix is `sfk_` (storefront key).

```bash
curl https://api.opencals.com/storefront/products \
  -H "Authorization: Bearer sfk_live_abc123"
```

> **Customer JWT tokens**
>
> Some endpoints (customer self-service: view bookings, reschedule, cancel) require a customer JWT issued by your own auth flow, not the store API key. See the Authentication docs for details on the dual-auth model.

### Core endpoints

**List services**

```bash
GET /storefront/products
```

Returns the service catalog — name, description, duration, pricing, images, associated staff. This is your booking homepage data.

```typescript
import { setupOpencals, ProductService } from "@opencals/storefront-sdk";

const opencals = setupOpencals({ apiKey: process.env.OPENCALS_API_KEY });
const { data: products } = await ProductService.listProducts({ opencals });

// products is typed: Product[]
// product.variants holds per-variant pricing and duration
```

**Check availability**

```bash
GET /storefront/appointments/availability
  ?productId=prod_123
  &staffMemberId=staff_456   # optional — omit to show all available staff
  &locationId=loc_789        # optional for single-location stores
  &from=2026-07-01
  &to=2026-07-07
```

Returns available time slots grouped by day. The API computes availability server-side — you get back a clean list of `{ date, slots: [{ start, end, staffMemberId }] }` objects.

```typescript
import { AppointmentService } from "@opencals/storefront-sdk";

const { data: availability } = await AppointmentService.getAvailability({
  opencals,
  query: {
    productId: "prod_123",
    from: "2026-07-01",
    to: "2026-07-07",
  },
});

// availability.days is typed: AvailabilityDay[]
```

**Create a cart and add a booking**

```bash
POST /storefront/cart          # create cart → returns cartId
POST /storefront/cart/items    # add booking to cart
```

The cart persists server-side. Pass `cartId` in subsequent requests. A cart can hold multiple bookings — useful for "book haircut + colour" flows.

```typescript
import { CartService } from "@opencals/storefront-sdk";

const { data: cart } = await CartService.createCart({ opencals });

const { data: updatedCart } = await CartService.addCartItem({
  opencals,
  body: {
    cartId: cart.id,
    productId: "prod_123",
    variantId: "var_456",
    staffMemberId: "staff_789",
    startAt: "2026-07-03T14:00:00Z",
  },
});
```

**Checkout**

```bash
POST /storefront/checkout/initiate   # returns Stripe payment intent
POST /storefront/checkout/confirm    # confirm after payment
```

The checkout endpoint returns a Stripe Payment Intent client secret. You pass that to Stripe Elements or Stripe.js on the frontend to collect payment. On success, confirm the booking. The API is idempotent on the confirm step.

```typescript
import { CheckoutService } from "@opencals/storefront-sdk";

// Step 1: initiate checkout
const { data: checkout } = await CheckoutService.initiateCheckout({
  opencals,
  body: { cartId: cart.id },
});
// checkout.clientSecret → pass to Stripe Elements

// Step 2: after Stripe confirms payment, confirm the booking
const { data: booking } = await CheckoutService.confirmCheckout({
  opencals,
  body: { cartId: cart.id, paymentIntentId: checkout.paymentIntentId },
});
// booking is confirmed — appointment is locked in the calendar
```

### Full booking flow

1. **Load service catalog**: GET /storefront/products — populate your services page. Cache this; it rarely changes.
2. **Load staff and locations**: GET /storefront/staff-members and GET /storefront/locations — populate the filters on your booking page.
3. **Fetch availability**: GET /storefront/appointments/availability for the selected service, staff, and date range. Render the calendar.
4. **Create cart and add item**: POST /storefront/cart to create a cart, then POST /storefront/cart/items. Store the cartId client-side.
5. **Initiate checkout**: POST /storefront/checkout/initiate. Render the Stripe Elements payment form with the returned clientSecret.
6. **Confirm on payment success**: After Stripe confirms payment, POST /storefront/checkout/confirm. The booking is created, the slot is blocked, and confirmation emails fire.

## TypeScript SDK

Installing the SDK removes the boilerplate:

```bash
npm install @opencals/storefront-sdk
```

Every service maps to a class (`ProductService`, `AppointmentService`, `CartService`, `CheckoutService`, `CustomerService`). Every request and response is typed from the OpenAPI spec, so TypeScript catches mistakes at build time rather than runtime.

The SDK ships with Zod schemas for runtime validation and formatting helpers for prices (`formatPrice(amount, currency)`), durations (`formatDuration(minutes)`), and timezones.

## Open-source example

The fastest way to see the complete API integration in context is the Haar open-source template — a Next.js 15 salon booking website that covers the full flow end to end.

[Deploy to Vercel](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fletsopencals%2Ftemplate-haar)

Clone it from [GitHub](https://github.com/letsopencals/template-haar), add your API key, and you have a working booking site in minutes. The source shows exactly how each part of the API is called in a production Next.js app.

- [Interactive API reference](https://opencals.com/docs/api-reference/introduction): Try every endpoint directly from the browser — no curl required.
- [Build a booking page tutorial](https://opencals.com/docs/guides/build-a-booking-page): Step-by-step walkthrough of the complete booking flow in Next.js.
- [TypeScript SDK guide](https://opencals.com/docs/guides/storefront-sdk): Full SDK documentation — setup, services, error handling, Zod schemas.
- [What is a headless booking system?](https://opencals.com/blog/headless-booking-system): Architecture overview — when headless makes sense and what it actually means.

## Frequently asked questions

### What is a booking API?

A booking API is a REST (or GraphQL) interface that exposes scheduling and availability logic as a service. Instead of embedding a pre-built booking widget, you call the API from your own frontend to get available slots, create bookings, handle payments, and let customers manage their appointments. The API handles the hard parts — real-time availability, conflict detection, payment flows — while you control the UI.

### How is a booking API different from a calendar API like Google Calendar?

Google Calendar is an event storage and sync layer. A booking API is purpose-built for service businesses — it knows about services, staff, working hours, customer-facing availability, cart state, deposits, and payments. You could build a booking system on top of Google Calendar, but you'd be building most of the booking logic yourself. A dedicated booking API gives you the domain logic out of the box.

### What authentication do booking APIs use?

Most booking APIs use API keys (bearer tokens) for server-to-server and client-to-server calls. The Opencals Storefront API uses a store-scoped API key (sfk\_…) for most endpoints, and customer JWT tokens for authenticated customer actions (view, reschedule, cancel their own bookings). Never expose your API key in client-side JavaScript — proxy calls through your backend or use a Next.js API route.

### How does real-time availability work in a booking API?

The API computes available slots server-side by combining staff working hours, existing bookings, service duration, buffer time, and location hours. Your client sends a query (which service, which staff, which date range) and receives back a list of clean available slots. You don't have to fetch and process raw calendar data — the computation is done on the backend and the result is display-ready.

### Does the Opencals API support multi-staff and multi-location?

Yes. You can query availability for a specific staff member and location, or leave both optional to show all available options. The API handles the scheduling constraints per staff and per location correctly — if a staff member has a break or an existing booking, those slots won't appear as available.

### Can I use the Opencals API without using the Shopify app?

Yes. The Storefront API is independent of the Shopify integration. You can use it to build a fully custom booking website, a mobile app, or any other frontend without touching Shopify. The Shopify app and the Storefront API share the same backend, so you can also run both simultaneously if you want a Shopify storefront and a separate custom booking site from the same account.
