Installation
Setup
CallsetupOpencals() once at app startup — a root layout, an API client singleton, or a module you import from your server code:
OPENCALS_API_KEY environment variable set, setupOpencals() needs no arguments.
Which API key
There is one key setting; the kind of key depends on where the SDK runs:- On a server (Next.js server components, route handlers, server actions, serverless functions): a private storefront key (
spk_). It stays secret and can forward the buyer’s IP. - In the browser (embedded widgets, client-side apps): a public storefront key (
sfk_) with allowed origins set to your domains. Don’t put a private key in browser code: anyone could read and reuse it. The SDK warns if you pass one there.
Forward the buyer’s IP
On a server, every SDK request comes from your server’s IP, so all your visitors share one rate-limit budget and abuse protection can’t tell them apart. PassbuyerIp, a function that returns the IP of the visitor a request is for, and the SDK sends it as X-Opencals-Buyer-Ip. With a private key the API then counts per visitor.
Next.js apps use the ready-made resolver from @opencals/storefront-sdk/next:
lib/opencals.ts
nextBuyerIp reads x-real-ip, else the first x-forwarded-for entry (Vercel sets both on every request and overwrites what the browser sends). It forwards only on mutations (sign-in, cart, checkout), because reading request headers while rendering a GET would make cached pages dynamic. Outside a request it returns nothing. All the open-source templates use it.
Other frameworks or proxies: pass your own function. It gets { method, url } and returns the visitor’s IP from the header your host sets (for example cloudfront-viewer-address on CloudFront), never from the request body:
- The function runs for every request; return nothing to skip one.
- A per-call
headers: { "X-Opencals-Buyer-Ip": ip }takes precedence, and a function that throws never fails the request. - With a public key (
sfk_) the header is ignored.
Services
The SDK mirrors the API’s tags as static service classes:
Every method takes a single options object with typed
path, query, body, and headers fields matching the underlying endpoint:
headers field:
Customer-authenticated requests
For endpoints that act on behalf of a signed-in customer, either passcustomerToken to setupOpencals() or set the header per call:
Error handling
With the default error handler enabled, failed requests throw a typedOpencalsApiError:
error.code carries the API’s machine-readable reason when there is one, for example CODE_EXPIRED or CODE_NOT_FOUND when a discount code is rejected (SDK 0.3.19+). error.toFieldErrors() flattens validation errors into { field: message } for forms.
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):Helpers
The SDK includes small utilities you’d otherwise rewrite in every booking UI:toLocalFromUtc / toUtcFromLocal handle the UTC conversion the API expects for appointment slots, and mergeTimeIntervals is handy when rendering availability ranges.