A podcast studio booking flow on the Opencals API

Stanislav TyshchenkoTutorial9 min readSep 29, 2026
Squared notebook page: five studios feed availability, add-ons and cart to pay, with a crossed-out note about three products per studio

A podcast studio sells time in a room by the hour, plus extras that cost more the longer the session runs. On Opencals that is one product per studio with custom duration switched on, and add-ons priced per hour or per booking. You build the customer-facing flow yourself against the Storefront API, on a development store that costs nothing until you take real bookings. A London studio group with five studios built theirs in an evening. If you don't need a custom flow, the hosted storefront handles the same model without any code.

A London studio group runs five studios, each with its own hourly rate, and sells extras on top: a second lens, an on-screen logo, cloud storage. Customers choose a room, choose how long, tick the extras they want and pay. The group built that booking widget on the Opencals Storefront API in an evening, inside their own site design, without writing a single availability rule. This article is the model they would have needed to know first, and the calls that make it work.

I'm using the studio case because it is a clean example of something most booking tools handle badly: one resource, many lengths, and extras that depend on the length.

One product per studio, and the hour is the unit

Each studio is its own product in the dashboard. That matters because of how Opencals decides what blocks what. A product with no staff member and no pool attached only blocks its own slots, so five studios book in parallel and a booking in Studio Two never touches Studio Four.

Session length is the part people model wrongly. You might reach for three products per studio, a one-hour, a two-hour and a half-day version. Don't. Those are three different product IDs, so under the same rule booking the one-hour version would not block the two-hour version on the same room, and the room double-books. The fix is one product per studio with allowCustomDuration on. You set the base duration to one hour (in seconds, so 3600), a maxDuration for the longest session you'll sell, or -1 for no cap, and a price for the base hour.

The customer then books a whole multiple of the base unit. The backend prices it as the number of units, rounded up, times the unit price, and validates the multiple. Two hours in a £150 studio is £300, and nothing on your side calculates that. Each studio carries its own hourly price, so five rates need no special handling.

One dead end for anyone hoping to skip a step: a development store comes with seeded sample businesses, and none of them is a studio. The datasets are salons, a barbershop, a medical clinic, a gym and a spa. You enter the five rooms and their rates by hand. It takes about as long as typing a rate card usually does.

Start on a development store, and stay there

You create a development store in the dashboard with no card and no trial clock. The pricing page describes it as a full store in test mode with every feature and every API endpoint, and plans switch on instantly there without billing. You pay nothing until you move to production, which is the sensible time to find out that your add-on logic is wrong.

For a studio this is the actual selling point. The three unknowns in a custom flow are whether the model fits, whether the flow feels right to a customer, and whether it survives a real booking. All three can be answered before any money changes hands, and the third one needs the least effort: put yourself through checkout once.

The availability grid is one call per studio

The API has no endpoint that returns availability for several products at once. To draw a grid with studios down one side and hours across the top, you make one call per studio for the chosen date and cache each result under its own key. It sounds wasteful and isn't much code:

ts
import '@/lib/opencals'; // setupOpencals({ apiKey: process.env.OPENCALS_API_KEY, ... }) import { ProductService } from '@opencals/storefront-sdk'; export async function studioSlots( studioIds: string[], date: string, // 'YYYY-MM-DD' hours: number, // what the customer picked, 1–4 ) { return Promise.all( studioIds.map(async (productId) => { const { data, error } = await ProductService.getCurrentAvailabilities({ path: { productId }, query: { date, timezone: 'Europe/London', duration: String(hours * 3600), // seconds, passed as a string }, }); if (error) throw error; return { productId, slots: data ?? [] }; }), ); }

Passing duration is what makes the grid honest. Slots come back only if they're long enough for the session length the customer asked for, so a three-hour booking never shows a start time with two hours free behind it. All times are UTC. Send the timezone so slots line up with the local day, and convert for display.

I haven't run this exact function against a store for this article. The method names, parameters and the string-typed duration come from the SDK's own type definitions and reference notes, but treat it as a sketch, and try it on a development store before you trust it.

Add-ons that scale with the session

Add-ons are configured once in the dashboard and attached to the studios where they apply. There are two pricing modes. A fixed add-on takes a quantity, optionally capped, like an on-screen logo at one per booking. A duration-multiplied add-on has its quantity set for you from the number of hours booked, so an add-on charged per hour on a three-hour session is three units without the customer doing the sum.

Fetch the extras for whichever studio the customer picked, and pass their choices when you create the appointment:

ts
import { ProductService, AppointmentService } from '@opencals/storefront-sdk'; const { data: addOns } = await ProductService.listAddOns({ path: { productId } }); // render these however the design wants: toggles, cards, a single upsell row const { data: appointment } = await AppointmentService.create({ body: { slot: { productId, fromDate, fromTime, toDate, toTime }, // straight from the slot cartId, addOns: [ { addOnId: logoId, quantity: 1 }, // fixed add-on { addOnId: storageId }, // quantity ignored if duration-multiplied ], }, });

The cart, checkout and payment steps are the standard ones, and I've left them out because they don't change for a studio. The documented sequence is create-or-get the cart, then start, saveCustomer, saveAnswers and submit. A cart expires after a few minutes and releases its held slots, so a customer who wanders off for coffee should see a countdown, not a surprise. That behaviour is why the reference templates carry a cart context with extend-on-activity.

The design is entirely yours, and that is the difference from a hosted widget. Show the rooms as photographs, put the add-ons in a drawer, hide the second-lens option unless the customer chose the video package. The API returns data. It has no opinion about how a room should look.

Handing the build to an AI tool

This is where most of the evening goes. There are official Opencals skills for AI builders that carry the API's real call shapes, so the tool doesn't invent method names. The walkthroughs for Lovable, v0 and Base44 show the setup, and the overview of building a booking site with AI explains what to hand the tool and where it still needs you.

For a studio, describe the business, not the mechanics: five studios, hourly rates, sessions from one to four hours, these four extras. Leave availability out of the prompt. The tool doesn't compute it. The API does.

The sibling case is worth reading if you want to see the same resource logic under a different business: the VOLT padel club template books courts by duration on one grid, and the same fan-out pattern is behind it.

What isn't solved by any of this

Three things I'd want to know before starting.

Thirty-minute steps and hourly steps behave differently in a grid. If your base unit is an hour, a customer can't book from half past, and some studios do want that. Setting the base unit to thirty minutes fixes it but prices every half-hour, which may not be what your rate card says. I don't have a neat answer, and the padel template ran into the same problem.

The Storefront API is for the customer-facing booking flow. Your engineers' rosters, gear checklists and post-production queue are outside it.

And the hosted API is closed-source. The SDK and the templates are MIT-licensed, so your front end is yours to keep, but availability and payments run on our side.

Build it, or use the hosted storefront

If you have a developer or an AI builder and you want the booking flow to look like your site, build it on a development store. If a studio owner just wants bookings, add-ons and a few show-or-hide rules, the hosted storefront already handles the rooms, durations and add-ons, with no code. I'd start there unless the design of the flow is the thing you're selling. What would change my mind is a customer flow the storefront can't express, such as a package builder that changes the room list as the customer chooses, and that is a job for the API.

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.

No credit card required
Setup in 10 minutes
Cancel anytime