Booking SDK: a typed TypeScript SDK for custom booking flows

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 not a nicety; it's the difference between a booking flow you trust and one you babysit.
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:
npm install @opencals/storefront-sdkInitialize 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.
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 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.
import { ProductService } from "@opencals/storefront-sdk";
const products = await ProductService.list();
// products: fully typed — name, duration, price, currency, staff, locations2. 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.
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.
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.
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); // idempotentThe 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.
// customer token from your own auth, attached per-request
const bookings = await AppointmentService.listForCustomer({ token });
await AppointmentService.reschedule(bookingId, { startsAt, token });Catalog
ProductService.list() — populate services. Cache it.
Availability
AppointmentService.getAvailability(...) — render the calendar from real bookable slots.
Cart
CartService.create() then addItem() — hold the selection, store cartId client-side.
Checkout
CheckoutService.initiate() for the Stripe client secret, then confirm() after payment (idempotent).
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:
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.
// 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 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 is right there with the same guarantees.
Note
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
The endpoint-level reference: auth model, core routes, and the full flow in raw REST.
Headless booking system
The architecture for building a custom booking frontend on any stack.
Opencals for developers
API docs, the OpenAPI spec, and how to get a storefront key.
Build a booking website
Deploy a free open-source template on your own domain in an afternoon.
Open-source booking system
What's open (SDK + templates) and what's managed (the platform), and why.
FAQ
Frequently Asked Questions
Early Access — 3 Months Free
Ready to transform your service business?
Join 150+ businesses already using Opencals. Get 3 months completely free with all features unlocked.