# Booking SDK: a typed TypeScript SDK for custom booking flows

> A typed TypeScript booking SDK for building custom appointment flows — services, availability, cart, and checkout with full type safety.

URL: https://opencals.com/blog/booking-sdk
Author: Stanislav Tyshchenko
Published: 2026-07-19
Updated: 2026-09-05
Categories: engineering

Adding booking to an app looks like a weekend project until you start. Real-time availability across staff and locations, cart state that survives a page refresh, a payment flow that fails gracefully, booking statuses that move through their lifecycle — every piece is more nested and more stateful than it looks from the outside.

A booking SDK is what turns that from raw HTTP plumbing into typed method calls. This is a look at what a booking SDK does, when it's worth using over the raw API, and how the Opencals storefront SDK is structured — with code you can actually run.

## Why a booking flow earns an SDK

Plenty of APIs don't need an SDK. A booking API does, and the reason is the shape of the data. A single booking flow touches:

- **Availability** — Nested slots computed across staff, hours, duration, capacity
- **Cart state** — Line items, quantities, selected times — held across steps
- **Payment intents** — Stripe client secrets, confirmation, idempotency
- **Booking status** — Pending, confirmed, rescheduled, cancelled lifecycles

Wire that up with hand-rolled `fetch` calls and you're maintaining a private, untyped copy of the API's mental model — one that silently drifts every time the backend adds a field. A typed SDK gives you autocomplete for every request, compile-time errors when a shape changes, and runtime validation at the boundary. In a domain this nested, that's the difference between a booking flow you trust and one you babysit.

> **Key takeaway**
>
> Use a booking SDK when the flow is stateful and the types are nested — availability, carts, and payments. Reach for the raw REST API for one-off scripts, server-to-server jobs, or non-JavaScript stacks.

## Install and set up

The Opencals storefront SDK is a TypeScript package:

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

Initialize it once with your store API key. Keys are scoped to a single store, carry the `sfk_` prefix, and come from the dashboard under Settings → API. Keep the key server-side — it's a secret, not a publishable client key.

```ts
import { setupOpencals } from "@opencals/storefront-sdk";

const opencals = setupOpencals({
  apiKey: process.env.OPENCALS_API_KEY, // sfk_...
});
```

That's the whole setup. The base URL (`https://api.opencals.com`) and the OpenAPI 3.0 contract are baked in, so you're calling typed methods, not memorizing routes. There's an interactive spec at [opencals.com/docs](https://opencals.com/developers) if you want to see the raw endpoints underneath.

## The five things every booking flow does

Almost every custom booking flow is the same five steps. Here's each one through the SDK.

### 1. Load the service catalog

Your services page is populated from the product catalog. It rarely changes, so cache it.

```ts
import { ProductService } from "@opencals/storefront-sdk";

const products = await ProductService.list();
// products: fully typed — name, duration, price, currency, staff, locations
```

### 2. Fetch real-time availability

This is the endpoint that earns the platform. You ask for a service, optionally a staff member and location, and a date range; you get back the slots that are genuinely bookable right now — after existing bookings, working hours, buffers, and capacity are all accounted for.

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

const slots = await AppointmentService.getAvailability({
  productId,
  staffId,        // optional — omit for "any available"
  locationId,     // optional
  from: "2026-07-20",
  to: "2026-07-27",
});
```

You render `slots` straight into a calendar. You are not reconstructing availability logic on the client — the hard part stays on the server where it belongs.

### 3. Create a cart and add the booking

Carts hold the selected slot as a line item and survive across steps. Store the returned `cartId` client-side.

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

const cart = await CartService.create();
await CartService.addItem(cart.id, {
  productId,
  staffId,
  locationId,
  startsAt: selectedSlot.startsAt,
});
```

### 4. Initiate and confirm checkout

Checkout returns a Stripe payment intent client secret. You hand that to Stripe Elements on the frontend to collect the card, then confirm.

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

const { clientSecret } = await CheckoutService.initiate(cart.id);
// → render Stripe Elements with clientSecret, collect payment

await CheckoutService.confirm(cart.id); // idempotent
```

The confirm step is idempotent: a retried request after a flaky network won't double-book or double-charge. On success the booking is created, the slot is blocked everywhere, and confirmation emails fire.

### 5. Customer self-service

Viewing, rescheduling, and cancelling a customer's own bookings uses a second credential — a customer JWT issued by _your_ auth flow, not the store key. This dual-auth model keeps the store key server-side while letting a signed-in customer act on their own bookings.

```ts
// customer token from your own auth, attached per-request
const bookings = await AppointmentService.listForCustomer({ token });
await AppointmentService.reschedule(bookingId, { startsAt, token });
```

1. **Catalog**: ProductService.list() — populate services. Cache it.
2. **Availability**: AppointmentService.getAvailability(...) — render the calendar from real bookable slots.
3. **Cart**: CartService.create() then addItem() — hold the selection, store cartId client-side.
4. **Checkout**: CheckoutService.initiate() for the Stripe client secret, then confirm() after payment (idempotent).
5. **Self-service**: Customer JWT endpoints for view, reschedule, cancel.

## Type safety and runtime validation

Static types catch shape mismatches at compile time. But data crossing a network boundary can still surprise you at runtime, so the SDK ships Zod schemas that validate responses as they come in. A malformed or unexpected payload fails loudly at the edge instead of leaking a `undefined` three components deep into your UI.

It also includes the small formatting helpers you'd otherwise rewrite on every project:

```ts
import { formatPrice, formatDuration } from "@opencals/storefront-sdk";

formatPrice(4500, "USD");   // "$45.00"
formatDuration(90);          // "1h 30m"
```

Prices live in minor units (cents) and durations in minutes across the API, which is the correct way to avoid floating-point money bugs — the helpers just save you from formatting them by hand every time.

## Using it with Next.js and React

The SDK is framework-agnostic; it runs wherever JavaScript runs. The only real rule is credential placement.

Keep the store API key on the server — route handlers, server actions, or server components. Fetch there, then pass typed data (or a customer token) down to the client. Never ship `sfk_` keys to the browser.

```ts
// app/api/availability/route.ts (Next.js route handler)
import { AppointmentService, setupOpencals } from "@opencals/storefront-sdk";

setupOpencals({ apiKey: process.env.OPENCALS_API_KEY });

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url);
  const slots = await AppointmentService.getAvailability({
    productId: searchParams.get("productId")!,
    from: searchParams.get("from")!,
    to: searchParams.get("to")!,
  });
  return Response.json(slots);
}
```

The client calls your route, not Opencals directly. That's the whole [headless booking](https://opencals.com/blog/headless-booking-system) pattern: your frontend, your framework, your domain — the booking engine and payments handled underneath.

## SDK or raw REST — which to reach for

Both are supported; they're not competitors.

**Pros**

- SDK: typed methods, autocomplete, and compile-time safety across the whole flow
- SDK: Zod runtime validation and money/duration helpers included
- SDK: less boilerplate for the stateful cart-and-checkout sequence

**Cons**

- Raw REST: better for non-JavaScript stacks (Python, Go, PHP) — no SDK to depend on
- Raw REST: leaner for server-to-server jobs or one-off scripts
- Raw REST: nothing to update when you just need a single endpoint

If you're building a production booking experience in TypeScript, the SDK is the shorter path. If you're integrating from another language or writing a quick automation, the [REST API](https://opencals.com/blog/booking-api) is right there with the same guarantees.

> The SDK and the website templates are open source and free; the Opencals platform underneath is a managed cloud service. You get open, inspectable client code running against an API you don't have to operate.

## Where to go from here

- [Booking API guide](https://opencals.com/blog/booking-api): The endpoint-level reference: auth model, core routes, and the full flow in raw REST.
- [Headless booking system](https://opencals.com/blog/headless-booking-system): The architecture for building a custom booking frontend on any stack.
- [Opencals for developers](https://opencals.com/developers): API docs, the OpenAPI spec, and how to get a storefront key.
- [Build a booking website](https://opencals.com/blog/build-a-booking-website): Deploy a free open-source template on your own domain in an afternoon.
- [Open-source booking system](https://opencals.com/blog/open-source-booking-system): What's open (SDK + templates) and what's managed (the platform), and why.

## FAQ

## Frequently asked questions

### What is a booking SDK?

A booking SDK is a client library that wraps a booking platform's API in typed methods, so you can build a custom appointment flow — loading services, checking availability, adding to a cart, taking payment — without writing raw HTTP calls or guessing at request and response shapes. The Opencals storefront SDK is a TypeScript package (@opencals/storefront-sdk) that covers the full booking flow with type safety and runtime validation built in.

### Why use an SDK instead of calling the REST API directly?

You can call the REST API directly — the SDK is optional. But a booking flow has a lot of nested, stateful objects: availability slots, cart line items, payment intents, booking statuses. A typed SDK gives you autocomplete, compile-time errors when a shape changes, and runtime validation, which removes a whole class of bugs. For quick scripts or non-JS stacks, raw REST is fine; for a production TypeScript app, the SDK saves real time.

### Does the booking SDK work with Next.js and React?

Yes. The SDK is framework-agnostic — it runs anywhere JavaScript runs. In Next.js, keep the store API key server-side (route handlers, server actions, or server components) and pass only the data or a customer token to the client. In React, fetch on the server or in a data-loading hook and render from typed results. The SDK doesn't force a framework or a state library on you.

### Is the Opencals booking SDK open source?

The SDK and the website templates are open source and free to use; the Opencals platform itself (the booking engine, dashboard, and hosted infrastructure) is a managed cloud service, not open source. So you get open, inspectable client code and templates you own, running against a managed API you don't have to operate. The distinction matters if you're evaluating self-hosting versus a managed backend.

### How do I authenticate with the booking SDK?

There are two credentials. A store API key (prefix sfk\_, from the dashboard under Settings → API) scopes all storefront calls to one store and must stay server-side. A customer JWT — issued by your own auth flow — is required for customer self-service endpoints like viewing, rescheduling, or cancelling their own bookings. You initialize the SDK with the store key; you attach the customer token for the self-service calls.

### Can the SDK handle payments?

The SDK drives the checkout flow, and payment is collected through Stripe. Calling checkout initiate returns a Stripe payment intent client secret; you render that with Stripe Elements or Stripe.js on the frontend to collect card details, then call checkout confirm. The confirm step is idempotent, so a retried request won't create a duplicate booking or double charge.
