> ## Documentation Index
> Fetch the complete documentation index at: https://opencals.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Storefront SDK

> Install and use @opencals/storefront-sdk — the type-safe TypeScript client for the Opencals booking API, generated from the OpenAPI spec with Zod schemas and helpers included.

[@opencals/storefront-sdk](https://www.npmjs.com/package/@opencals/storefront-sdk) is the official type-safe TypeScript client for the Storefront API. It's generated from the same OpenAPI specification as the [API reference](/docs/api-reference/introduction), so every request and response is fully typed, and it ships with Zod schemas for runtime validation plus formatting helpers for prices, durations, and timezones. MIT licensed.

## Installation

```bash theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
npm install @opencals/storefront-sdk
```

## Setup

Call `setupOpencals()` once at app startup — a root layout, an API client singleton, or a module you import from your server code:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { setupOpencals } from "@opencals/storefront-sdk";

setupOpencals({
  apiKey: "sfk_…",          // or set the OPENCALS_API_KEY env var
  baseUrl: "https://…",     // optional, defaults to https://api.opencals.com
  logging: true,            // optional request/response logging
  errorHandler: true,       // throw OpencalsApiError on failures (default)
  customerToken: "eyJ…",    // optional customer JWT for authenticated requests
});
```

With the `OPENCALS_API_KEY` environment variable set, `setupOpencals()` needs no arguments.

<Warning>
  The API key is attached to every request automatically — which also means SDK calls belong on the server, where the key is safe.
</Warning>

## Services

The SDK mirrors the API's tags as static service classes:

| Service                    | Methods                                                                                                                               |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `ProductService`           | `list`, `get`, `getBySlug`, `getByExternalId`, `getCurrentAvailabilities`, `getCurrentAvailabilitiesMerged`, `getNearestAvailability` |
| `AuthService`              | `signIn`, `signUp`, `oauth`, `refresh`, `requestPasswordReset`, `resetPassword`, `requestEmailVerification`, `verifyEmail`            |
| `CartService`              | `get`, `createOrGet`, `addItem`, `removeItem`, `extendExpiration`, `applyDiscountCode`, `removeDiscountCode`                          |
| `CheckoutService`          | `start`, `submit`, `saveCustomer`, `saveAnswers`, `getCartQuestions`                                                                  |
| `AppointmentService`       | `list`, `find`, `create`, `cancel`, `reschedule`, `feedback`, `getBookingPreferences`                                                 |
| `OrderService`             | `list`, `find`                                                                                                                        |
| `SelfService`              | `getProfile`, `updateProfile`, `changePassword`                                                                                       |
| `LocationService`          | `list`, `get`, `getBySlug`                                                                                                            |
| `StaffMemberService`       | `list`, `getBySlug`                                                                                                                   |
| `PaymentService`           | `getAvailableProviders`, `getSettings`                                                                                                |
| `StoreService`             | `getStorePublicSettings`                                                                                                              |
| `ProductCollectionService` | `list`, `getBySlug`                                                                                                                   |
| `ImageService`             | `get`                                                                                                                                 |
| `CheckoutQuestionService`  | `listTranslations`                                                                                                                    |
| `FeedbackQuestionService`  | `listTranslations`                                                                                                                    |

Every method takes a single options object with typed `path`, `query`, `body`, and `headers` fields matching the underlying endpoint:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { ProductService } from "@opencals/storefront-sdk";

// GET /storefront/products/slug/{slug}
const { data: product } = await ProductService.getBySlug({
  path: { slug: "haircut" },
});

// GET /storefront/products/{productId}/current-availabilities
const { data: slots } = await ProductService.getCurrentAvailabilities({
  path: { productId: product!.id },
  query: { date: "2026-06-15", timezone: "America/New_York" },
});
```

Cart and checkout calls pass the cart ID through the typed `headers` field:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { CartService } from "@opencals/storefront-sdk";

const { data: cart } = await CartService.createOrGet({
  headers: { "X-Cart-Id": cartId ?? "" },
});

await CartService.addItem({
  body: { appointmentId },
  headers: { "X-Cart-Id": cart!.id },
});
```

## Customer-authenticated requests

For endpoints that act on behalf of a signed-in customer, either pass `customerToken` to `setupOpencals()` or set the header per call:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { AppointmentService } from "@opencals/storefront-sdk";

const { data: appointments } = await AppointmentService.list({
  headers: { Authorization: `Bearer ${accessToken}` },
});
```

See [Authentication](/docs/authentication) for how to obtain and refresh customer tokens.

## Error handling

With the default error handler enabled, failed requests throw a typed `OpencalsApiError`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { OpencalsApiError, ProductService } from "@opencals/storefront-sdk";

try {
  await ProductService.get({ path: { productId: "missing" } });
} catch (error) {
  if (error instanceof OpencalsApiError) {
    console.error(error.message);
  }
}
```

## Zod validation

Response types come with auto-generated Zod schemas, useful when data crosses a trust boundary (for example, after passing through your own API routes):

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import { zProductListResponse } from "@opencals/storefront-sdk";

const result = zProductListResponse.safeParse(apiResponse);
```

## Helpers

The SDK includes small utilities you'd otherwise rewrite in every booking UI:

```ts theme={"theme":{"light":"github-light","dark":"github-dark-default"}}
import {
  formatPrice,
  formatDuration,
  toLocalFromUtc,
  toUtcFromLocal,
  mergeTimeIntervals,
} from "@opencals/storefront-sdk";

formatPrice(4500, "USD");                              // "$45.00"
formatDuration(3600);                                  // "1h"
toLocalFromUtc("2026-06-15T14:00:00Z", "America/New_York");
```

`toLocalFromUtc` / `toUtcFromLocal` handle the UTC conversion the API expects for appointment slots, and `mergeTimeIntervals` is handy when rendering availability ranges.

## Putting it together

The [Build a booking page](/docs/guides/build-a-booking-page) tutorial uses the SDK end-to-end — services, availability, cart, and checkout — and the open-source [booking templates](https://opencals.com/docs/open-source/templates) are complete Next.js apps built on it.
