AKVS runs a lot of recurring appointments — the same slot, every week or every other week, for months at a time. Instead of making someone click through a calendar picker date by date, I built a system that takes a plain-English request like “every other Tuesday at 2pm starting next month,” has an AI model turn that into a structured recurrence rule, previews every resulting date, and then books the whole batch into GoHighLevel — without one rejected slot taking the rest of the batch down with it.
Three separate problems stacked on top of each other
Recurring bookings sound simple until you have to actually compute them. “Every other Tuesday starting next month” isn't one appointment, it's an open-ended set of them, and turning a sentence like that into exact calendar dates means parsing language a form field can't anticipate every phrasing of. Then there's the calendar math itself: a series that spans a daylight-saving transition needs each date computed with its own correct UTC offset, not one offset applied to the whole series. And once the dates are confirmed, booking isn't a single API call — it's a batch of them against a real calendar, where any individual date can be rejected (double-booked, invalid, whatever GoHighLevel's side decides), and the system has to handle that without either silently dropping a failed date or taking the rest of the batch down with it.
Two workflows, split where the job actually changes: parse & preview, then confirm & book
A webhook takes the plain-English request and sends it to Claude, which extracts a structured recurrence rule — the interval, the day, the start date, the end condition — out of however the client happened to phrase it. A code step then computes the literal dates from that rule, calculating each one's correct UTC offset individually so a date on the far side of a daylight-saving change doesn't come out an hour off. The result is returned as a preview. Nothing has touched GoHighLevel yet.
Once the dates are confirmed, a second workflow splits them into individual items and looks up the contact by email first. A new contact only gets created if none exists — the same client rebooking a second series doesn't end up with two contact records in GoHighLevel.
Each booking call to GoHighLevel's calendar-events API is explicitly configured not to throw on a non-2xx response, so a single rejected date comes back as normal data — with its real statusCode — instead of halting everything after it. A logging step immediately after classifies every item as ok or error, and the workflow hands that full per-item array back to whatever called it, so a partial failure is visible and specific instead of silent.
Not every failure in this workflow gets the same treatment — that's deliberate
A rejected booking is treated as recoverable: that one item fails, the rest of the batch keeps running, and the caller gets back an explicit ok/error status for every date instead of a vague overall success or failure. Contact creation is not given the same leniency — if creating the contact fails, there's no valid contact to book an appointment against, so that branch is set to stop outright rather than continuing on and returning a result that looks complete but isn't. Deciding which failures get isolated and which get allowed to stop the item is the actual design decision here, not just wiring up the API calls.
Honest caveat: this books into a live production calendar, so I verified this behavior from the workflow's own configuration — the booking call's “never error” response setting, and the logging step's classification logic, both read directly off the live nodes — rather than by triggering a real execution against real appointment data to watch it happen.
The live canvases
GHL Parse & Preview Dates — four nodes: webhook in, Claude extracts the recurrence rule, a code step computes the dates, respond with the preview.
GHL Recurring Appointment Booking — splits the confirmed dates into items, resolves the contact, books each date, logs a per-item result, and responds with the full array.
Both workflows are live in production for AKVS. The parse-and-preview step is the user-facing half, live at book.mountaintopvirtual.com; once a client confirms the dates it returns, the booking workflow runs server-side against GoHighLevel's calendar.