An AI setter that books real appointments is not one thing you buy. It is about six pieces you wire together, and once you have seen the shape of it you can build one in an afternoon.
I built mine in CloseBot, pointed at my own CRM's REST API. Twelve custom tools, one agent node, one source per sub-account, and a bot that can quote a price, read genuinely open times, take the booking, then move or cancel it later for the same caller.
This is the build, in order.
The shape of it before you start
Six pieces, and it helps to know what each one is for before you touch anything.
An API key in your CRM, scoped down to only what the bot needs. A source in CloseBot, which is the connection between one CRM sub-account and the bot. A set of custom tools, each one a single HTTP call the model is allowed to make. An agent node on the flow canvas, which is the thing that actually holds a conversation. Instructions on that node, telling it the order to do things in. And the channel wiring that decides where customers meet it.
The reason this works at all is that CloseBot's custom tools will point at ANY endpoint you hand them. Most conversational AI in this space integrates with one specific CRM and stops there. CloseBot had never heard of mine and did not need to, because I could hand it a URL, a method and a header.
The other half is that the REST API on the other end is mine. That matters more than it sounds and I will come back to it at the end, once you have seen where it bites.
Step 1, cut an API key and choose what it can reach
Do this first, in the CRM, because every tool you build afterwards authenticates with it.

Mine is called CloseBot CRM Booking Tools and it holds contacts read and write, availability read, service types read, estimates read, appointments write, and the verified customer booking permissions. Eight or so boxes.
The interesting box is the one I left EMPTY. Appointments Read is off, and the permission screen tells you exactly why in its own words. Read gives you "booked appointments, including every customer name, email address and phone number." That is the whole roster. Grant it so your bot can cancel one visit and you have handed a chatbot your customer list.
Availability Read, right underneath it, reads "open times, the calendar list and appointment types. No customer details." That is the one you want. The bot needs to know what is FREE, not who is booked. It also reads the same calendar my public booking page does, so the widget and the bot can never disagree about what is open.
If you take one thing from this post, take that. Scope the key by what the bot has to accomplish, not by what would make the errors go away. When a call 403s later, the fix is the narrow permission it actually needed, never the broad one sitting next to it.
Keep the key value somewhere safe when it is shown to you. You will paste it into headers shortly.
Join the Seedly owners community.
Owners trade setups, share add-ons, and swap playbooks. See what people are building before you commit.
Step 2, create a source and connect it to a sub-account
A source in CloseBot is the link between the bot and one CRM account. Sources live under Sources in the left nav, and the list shows you what is connected and how many job flows are hanging off each one.

Add New Source, give it a name that matches the account it serves, and it appears here as Connected.
Here is the part that matters if you run an agency. You make one source per sub-account, not one for the whole agency. Each client gets their own source, and each source carries its own webhook endpoint and its own access token. That is what keeps one client's conversations, contacts and bookings from leaking into another's, and it is also what lets you give one client a bot and not the next one.
Then you wire that source into the CRM sub-account. In mine it lives under Automation, on the Livechat tab, in a panel called AI Auto-Reply.

Two values go in, and both come from the same place in CloseBot, which is Sources, then your source, then the Webhook tab. The source webhook URL is the first box there. The access token is the second one, the long base64 looking string. Save once with a token in place and the CRM generates its own outbound callback URL back to you.
Underneath that is a Channels picker, and this is where the bot stops being a science project. Turn on live chat and the widget on the client's website is now answered by the agent. Turn on SMS and a text conversation is too. Same bot, same tools, same booking, whichever way the customer turned up.
One behaviour worth knowing before you switch it on. When a contact replies on a selected channel the message goes to CloseBot, the reply comes back through that same channel, and the bot pauses automatically the moment a teammate replies in the thread. So a human stepping into a conversation does not have to fight the bot for it. That is the difference between something you can leave on and something you babysit.
Step 3, build a custom tool
Now the fun part. Each tool is one HTTP call, built through a four step wizard.

About is the name and the description. The description is not decoration, it is what the model reads when it decides whether this is the right tool, so write it as an instruction rather than a label. Mine say things like "use this to retrieve real available appointment times before offering booking options to the contact."
API Connection is the call itself. Method and URL at the top, then Custom Parameters, Headers and Query Parameters.
Authentication goes in Headers, and this is the same for every tool. One header, Authorization, with the value Bearer followed by the key you cut in step one. JSON endpoints get a Content-Type of application/json as well. Do not put the key in a query parameter, and keep it out of any screenshot you ever share, which is why it is blurred out of the one above.
Query Parameters is where the model's input meets the request. A parameter can be a fixed value you type once, like an appointment type ID that never changes, or it can be a variable chip fed by the conversation. In the shot above, date is wired to Tool Parameters.date, which means the model supplies it.
Two things will bite you here and both cost me time.
The variable chips can leave a trailing space after them. An exact email lookup then compares against [email protected] with a space on the end and silently finds nothing, and the tool reports no error because nothing went wrong, there was simply no match. Delete the space immediately after the chip.
Second, prefer a JSON body with explicit parameters over interpolating an ID into the URL path. Dynamic values in a path were unreliable enough that I moved every mutating tool to a body.
Test Response runs the call live and learns the shape of what comes back. Run it against data that actually exists. A zero result sample only exposes the empty top level, so CloseBot learns an incomplete schema and your agent never sees the fields it needs. Create one disposable record first if you have to.
Restrict View decides what the model is allowed to see out of that response, and it is more powerful than it looks. If your endpoint returns both a single record and an array containing the same record, hide the array. Given two versions of the same answer a model will eventually pick from the wrong one.
Then repeat. Twelve times, in an order that makes sense. Appointment types and availability before booking, find contact before reading that contact's estimates.

Step 4, place the agent node and bind the tools
On the flow canvas you get a Start node. Drag an agent node next to it and connect them. That node is the conversation.
Open it and you get a tool list with a checkbox against everything available, including CloseBot's own built in ones. Tick the tools this agent is allowed to use.

Mine reports thirteen tools. Twelve are mine and the thirteenth is CloseBot's built in FAQ tool, which I left on.
Ticking is only half of it. Inside the instructions you reference a tool with the @@ mention syntax, which is how you talk about a specific tool in a sentence the model will follow. A tool that is ticked but never mentioned tends not to get used at the moment you wanted it, and a tool mentioned but not ticked is not available at all. Do both.
Step 5, write the instructions
The instruction block is where the conversation gets its rules. The counter along the bottom of the panel tracks its length, and the block below runs about 2,300 characters.

Do not copy that block. It is a generated example I put in to exercise the flow while testing, it is not the prompt running in production, and it is going to be rewritten anyway. Treat it as a shape, not a script.
Yours has to be built around how YOU want the bot to work. Your tools will be named differently, your business will have its own rules about what a bot may promise a customer, and an instruction block lifted from somebody else's setup will confidently do the wrong thing in yours. (it will also do it politely, which is worse)
What does transfer is the two things a good block is organised around, which are ORDER and proof.
Order, because a model left alone will offer a time before it has checked one. So appointment types and service catalog first, availability second, booking only after the customer confirms one exact slot that availability returned. For field work, services first, then availability, then create the job.
Proof, because the bot is talking to a stranger. Collect an exact email and phone before any lookup, cancellation or reschedule. Never list the appointment roster. Only ever act on an appointment the verified lookup returned. Require an explicit confirmation in the same breath as a cancellation.
Write these as flat imperatives. The model follows "collect both the caller's exact email and phone" far more reliably than it follows a paragraph explaining why identity matters.
Step 6, test the whole lifecycle, then publish
The tool tester lies to you. Not on purpose, it just tests one call in isolation, and three of the four real bugs I hit passed there and only turned up in an actual conversation.
So test through the agent node, as a customer would. Ask for times. Book one. Look it up with email and phone. Move it to another open slot. Confirm the new time. Cancel it with a confirmation. Look it up again and get nothing back. Then check that BOTH slots, the original and the one you moved to, are open again for someone else.
That last step is the one people skip and it is the one that catches a stale calendar hold.
When it passes, Publish. The version number sits next to the button, and the published version is what your channels are actually talking to. Mine went out as 0.0.6.
The bit where owning the API earned its keep
I said I would come back to this.
Partway through, the bot started describing the right appointment to the customer and then acting on a different one. My verified lookup was returning the contact's whole history, six cancelled records and one live one, and CloseBot flattens an array like that into numbered fields. The model was choosing from a list where most of the options were wrong.
I wrote five instructions to fix it. Select only the scheduled record. Never select by array position. Preserve the chosen ID. It still picked a dead one.
On a rented platform that is where the story stops. You file a ticket, you write a sixth guard, you ship something you are not proud of.
Instead I added an endpoint that returns only the appointments the caller can still act on, pointed the tool at it, re-ran Test Response to pick up the new shape, and the bug stopped existing rather than getting worked around. That was an afternoon.
That is the argument for a CRM whose source code you own, and it is not an abstract one. The tools in this post are only as good as the endpoints under them, and if you cannot change those, your bot's ceiling was set by somebody else. The API and webhook reference has the full booking surface if you want to see what the tools are calling, and Dispatch contributes its own endpoints to the same key for field work.
If you got here because you vibe-coded something and want to point a bot at it, the same instinct applies to the app underneath, and I keep a separate pre-launch checklist for that. The security half matters twice as much once an AI is holding your API key.
The twelve tool definitions, the permission recipe, the agent instructions and the troubleshooting notes are packaged up for Seedly CRM owners. Sign in to your account and it is in the sidebar, under CloseBot tool pack. If you want the CRM side of it, the integrations list covers what connects to what.
CloseBot link above is an affiliate link. I used it because its custom tools will point at anything, which is the specific thing that made this build possible. I would have written the same post either way, mostly because the bug was mine.
Common questions about building an AI appointment setter
Can I build my own AI agent that books appointments?
Yes, and it is roughly an afternoon once you know the pieces. You need an API key scoped to what the bot must do, a source connecting the bot to your CRM account, one custom tool per HTTP call you want it to make, an agent node with those tools bound to it, and instructions setting the order of the calls. The wiring is not the hard part. The hard part is deciding what the bot is allowed to see and proving who it is talking to before it changes anything.
What is a source in CloseBot and do I need more than one?
A source is the connection between the bot and one CRM account, and it carries its own webhook endpoint and access token. If you run an agency you want one source per sub-account rather than one for everything, because that is what keeps each client's conversations and bookings separate and lets you enable the bot for one client without enabling it for the next.
How do I authenticate a CloseBot custom tool against my API?
Through the Headers section on the API Connection step of the tool wizard. Add an Authorization header whose value is Bearer followed by your API key, and a Content-Type of application/json for endpoints that take a JSON body. Every tool that hits the same API uses the same header. Keep the key out of query parameters and out of any screenshot you share.
Do I need to give a booking bot access to my whole calendar?
No, and you should not. The permission that returns your appointment roster carries every customer's name, email and phone, so granting it just so the bot can cancel one visit hands over your customer list. Grant availability read instead, which exposes open times and appointment types with no customer details, and use verified customer endpoints for anything that touches one person's booking.
Why does my AI agent pick the wrong record from an API response?
Because you gave it a list and asked it to filter. When a lookup returns an array, CloseBot flattens it into numbered fields, and the model will describe the correct record to the customer while passing a different record's ID to the next tool. Prompt instructions do not reliably fix it. Use Restrict View to hide any duplicate copy of the same data, and where you can, change the endpoint so it only returns records that are still actionable.
Can the bot answer live chat as well as SMS?
Yes, that is the Channels picker on the CRM side of the source wiring. The same agent, tools and instructions serve every channel you enable, so a website live chat visitor and a text message conversation get the same booking behaviour. Most setups also pause the bot automatically once a human teammate replies in the thread, so stepping in does not mean fighting it for the conversation.



