Back to all articles
Engineering11 min read·July 22, 2026

We Built a Chatbot That Actually Books the Appointment

A behind-the-scenes look at the assistant powering Tecton Rescheduler — how it answers questions, fills its own booking form, and stays sane doing it.

EngineeringTecton Rescheduler
01

Closing the loop on booking

Most “AI chatbot” features on booking sites are glorified FAQ bots: they can tell you the business's hours, and then they hand you off to a contact form the moment you actually want to book something. We wanted ours to close the loop — a visitor should be able to land on a clinic's site, ask a real question, get a straight answer, and walk out the other side with a confirmed, paid appointment, all inside the same chat window. Here's how we built it.

02

One widget, every tenant, none of it shared

Every business on Tecton Rescheduler can flip on a chatbot for their own site. It's embeddable with a couple lines of script, and under the hood each tenant gets their own public key, their own knowledge base, their own colors and greeting, and — importantly — their own AI provider API key.

There's no shared brain here. If Clinic A uploads its cancellation policy, Clinic B never sees a trace of it. We built it multi-tenant from day one rather than retrofitting isolation later, which saved us from a rewrite we've seen other teams have to do.

03

Two brains, one dispatcher

The first design decision we made was to not build one giant agent that tries to do everything. Instead there are two: a general agent that answers questions, and a booking agent that runs the scheduling flow. A small intent router sits in front of both and decides, message by message, who should answer.

We kept the router deliberately cheap. Most booking requests use predictable language — “book,” “schedule,” “reserve,” “slot” — so a plain regex catches those instantly with zero AI calls involved. Everything else goes to a tiny classification call: temperature 0, a 4-token budget, one word back (BOOKING or GENERAL). It looks at the last few messages too, so if someone's mid-booking and just types “yes, tomorrow at 10,” the router doesn't get confused and bounce them back to the FAQ bot. And if that classification call ever fails for any reason, we fall back to “general” — worst case, the visitor gets a helpful answer instead of a broken conversation.

04

Teaching it the business, not just the internet

The general agent's answers are only as good as what it's been given to read, so we built a small ingestion pipeline: a tenant drops in PDFs, Word docs, HTML, or plain text, and it gets extracted, chunked, embedded, and stored automatically. We track each document's status end to end — pending, processing, ready, or failed — so if a scanned PDF comes back garbled, the dashboard says so instead of the bot just quietly knowing less.

We're doing that similarity search in plain application code right now rather than with a dedicated vector database — simple, easy to reason about, and plenty fast for the size of knowledge bases our tenants actually have. It's the kind of thing we'll swap for something heavier once a business uploads a truly enormous manual, and we built the retrieval function so that swap won't touch anything calling it.

One thing we were strict about: the bot doesn't get to make things up. Its instructions are explicit — answer from what you've been given, and if it's not in there, say so and point the visitor to the business instead of guessing at an answer that might be wrong.

Ingestion pipeline
StepWhat actually happens
ExtractPDF, DOCX, and HTML files are normalized into plain text.
ChunkText is split into roughly 500-token chunks with overlap so answers do not cut off mid-thought.
EmbedEach chunk is converted into a vector with OpenAI embeddings, using the tenant's own key.
RetrieveAt question time, the top five relevant chunks are retrieved and sent as model context.
05

Answers that stream, providers that swap

Replies type out in real time rather than appearing all at once after a pause — small thing, but it's the difference between a chatbot that feels alive and one that feels like a support ticket. That streaming runs through a provider layer we designed to be swappable: OpenAI is fully wired up today, and Gemini and Anthropic already show up as options in the settings screen, ready for their adapters to be dropped in behind the same interface. Nothing about the booking agent, the RAG pipeline, or the widget needs to change when that happens.

06

The part everyone said would be the hard part: booking

Booking isn't a form with extra chat bubbles bolted on — it's a real conversation that fills in a form behind the scenes. Every turn, the booking agent takes whatever the visitor just did (clicked a service, picked a time slot, typed something in plain English) and figures out what changed: a service selected, a date mentioned, a name given. It saves progress as it goes, so a visitor can wander off and come back without losing their place, and it tells the widget exactly what to show next — a row of service buttons, a calendar, a grid of open times, a contact form, or a payment card.

The unglamorous edge cases ended up being most of the work. Someone types “never mind” at the payment screen — we let them cancel right there rather than pretending we didn't see it. Someone asks about the cancellation policy in the middle of booking — we answer that inline without losing their spot in line. Someone mentions “next month” three messages ago and then asks something unrelated — we don't let that old date sneak back in and get booked by mistake. None of that shows up in a demo, but it's the difference between a booking flow people trust and one they abandon.

Payment itself hands off to Square for the actual card charge, and a confirmed booking lands straight in the business's calendar — so from the visitor's side, it really is just a conversation that ends with a confirmation, not a redirect to somewhere else.

07

Keeping it safe across every tenant

Multi-tenant and AI-powered is a combination that invites trouble if you're not careful, so we built in guardrails from the start rather than bolting them on after an incident.

Tenant safety controls
GuardrailWhat it protects against
Encrypted API keysProvider keys are encrypted at rest and never exposed to the browser.
Embed allowlistWidgets can be restricted to tenant-owned domains, preventing lookalike embeds.
Rate limitingPer-IP and per-tenant limits reduce abuse and flood attempts.
Feature flag checksAgent responses are enforced only for plans that include AI agent access on every request.
08

What it's built on

Next.js and React on the frontend, Prisma over a relational database, Tailwind for styling, deployed on Netlify. Deliberately, there's no chatbot framework or vector database underneath any of this — every piece, from chunking to retrieval to the provider adapters, is plain code we wrote and can read top to bottom. That's slower to bootstrap than reaching for a framework, but it means when something behaves oddly at 2am, we can actually trace why.