Agent integration
Your AI agent holds the conversation. Booker identifies the caller, decides what they may book and books it. This guide walks through a call from start to finish.
The flow
1. Start the session: conversation-init
Set POST /api/v1/agent/conversation-init as your agent platform's conversation-initiation webhook, with your API key. Booker answers in ElevenLabs' conversation-initiation format. When a call starts, the platform sends the caller's number, the number they called and the call's SID (empty for a text thread, see Texting with the agent):
{ "caller_id": "+13035550142", "called_number": "+17205550100", "call_sid": "CA5f0c3b7e9d2a41c6b8e0f1a2d3c4b5a6" }
Booker answers with variables for the conversation:
{
"type": "conversation_initiation_client_data",
"dynamic_variables": {
"session_id": "BU7Wp_xnB8e5b6LEyCERvMX6scsZHglpIkQIB36S6IU",
"is_new_customer": false,
"can_self_register": true,
"customer_first_name": "Dana",
"customer_last_name": "Whitfield",
"customer_full_name": "Dana Whitfield",
"upcoming_appointments": "Site Survey on Tue Nov 3 at 10:00 AM",
"open_requests": "",
"bookable_types": "Site Survey, Panel Inspection",
"has_address": true,
"channel": "voice"
}
}
Use the names and appointments in your agent's prompt. Pass session_id to every other tool: it is how Booker knows who the caller is. A call's session lasts 2 hours (a text thread's lasts longer, see below); after that a tool that answers 401 Session expired means the conversation needs to start again.
Never give conversation-init to the model as a tool. Your API key can pass any caller number, so a model that could call it could be talked into looking up someone else.
2. Identify the caller
If is_new_customer is true and can_self_register is true, ask for the caller's name (and optionally an email) and call Create the caller as a customer. Their calling number becomes their verified phone. If the company doesn't let new callers register, Booker answers 403 New customers are not allowed; the agent should explain that the company will need to set them up.
3. What can they book?
List what the caller may book returns the appointment types open to this caller now. A by_request type appears only when the company has opened a scheduling request for the caller. Whenever a type's scheduling_request is set, for open types too, pass its id as scheduling_request_id when searching and booking, so the request's length and dates apply. When a type has needs_address and has_address was false, ask for the address first (step 6). A scheduling request may also set the appointment's length (for example a half-day install instead of a full day): duration_minutes is then that length and scheduling_request.duration_label names it, so the agent can say it. Slots and bookings already use it. The agent never chooses the length.
4. Find slots
Find slots takes the type and a date range (up to 31 days, end date exclusive) and returns every open slot from the providers who serve the caller, plus recommended: up to five slots to offer first, best first. Offer up to three of those rather than reading out a list.
When nothing can be offered, providers is empty and reason says why: no_address (save the address, then search again), out_of_service_area, provider_does_not_offer_type (the caller's own provider doesn't do this type: say the company will follow up, and don't try other dates) or no_provider_offers_type.
Choosing a location or provider
When the caller names a place or a person ("the downtown salon, with Maria"), look them up rather than guessing ids:
- List locations by name returns the locations this caller can be offered, as ids and names. Pass the
appointment_type_id: for a type matched by address it lists only the locations that serve the caller, andneeds_addressmeans save their address first. - List providers by name takes the
appointment_type_idand, optionally, alocation_id. Whenprovider_choiceis false the company doesn't let customers choose who comes: the list is empty, so don't offer a choice. List what the caller may book showsprovider_choicefor each type too, so you know before asking. - Match the caller's words against the names and pass the ids to Find slots as
location_idandprovider_id. Filters only narrow what the caller could book anyway: one the caller can't use is a 400 with a reason you can say (for example "That location doesn't serve this customer's address"), and an unknown id is a 404. Don't send them when searching for a reschedule. - Some companies assign each customer a provider. Then every search and booking uses that provider, and the lookups list only them.
5. Book
Book a slot with the slot's provider_id and start_time, plus the appointment_type_id (and the scheduling_request_id whenever the type has a scheduling_request). Booker checks the provider's calendar again at that moment:
- 201: booked. With
receive_sms_confirmation, Booker texts the details to the customer's phone on file (which may not be the number they're calling from), andsmsin the response says whether the text went. - 409: the time was just taken, or the provider doesn't serve the caller's address. Search again and offer something else.
- 503: the calendar couldn't be checked and nothing was booked. Try once more, then apologise.
To reschedule, search with appointment_id instead of a type, then call Reschedule an appointment with only the new start_time: the appointment keeps its length, so leave end_time out (if you send one, it must be exactly the appointment's current length after start_time, the length the search offered). It has the same live calendar check as booking (503 is safe to retry, 409 means the time was taken). Use List the caller's upcoming appointments to find the appointment's id. To cancel, confirm with the caller, then call Cancel an appointment, sending session_id in a JSON body or, for clients that can't send a DELETE body, in the query string. If the appointment was booked under a scheduling request, the request opens again, or is cancelled when the customer already has another open request for that type.
Multi-day jobs
Some jobs take more than one working day, such as a solar install with a battery. The company sets that up as a length made of several days; the agent never chooses it. List what the caller may book shows each type's blocks (the minutes of each day, one entry for an ordinary appointment), and every slot from Find slots lists its days in blocks: the provider's next working days, all at the same start time, with the same provider.
Offer the start and say every day: "I can start on Friday the 11th at 9 AM, and the crew comes back Monday the 14th at 9." Book with the slot's start_time only; Booker books every day as one appointment. Rescheduling moves every day (search with appointment_id, then send the new start_time), and cancelling cancels every day. upcoming_appointments already names each day, for example "Install, Fri Jan 11 and Mon Jan 14 at 9:00 AM".
6. Address, contacts and opting out
- Address. Save the caller's address answers only whether the company serves it (
inorout). Sendline1and either apostal_codeor bothcityandstate; anything less is a 400. An address the company set is never replaced (409), and a conversation can save at most three (429). - New contact details. Start adding a contact texts a code to the caller's verified phone, never to the new number or email. Ask them to read the code back and call Confirm a contact with the
verification_idand the code. A code expires after 10 minutes or 5 wrong attempts, andCode expiredis also the answer for an unknown, already-used or another conversation'sverification_id; then start again. A conversation can request at most 3 codes, and a customer 5 an hour (429). - Opting out. When a caller asks not to be called or texted again, call Opt the caller out of outreach straight away. It records the opt-out for
voiceorsms, for the number they're calling or texting from; without achannelit is the conversation's own (smsin a text thread,voiceon a call). It is one channel per call: a caller who asks not to be called or texted needs two calls,voiceand thensms.
7. Texting with the agent
Customers can also text the company's number and book with the same ElevenLabs agent that answers its calls. Every tool works the same way in a text thread, under the same rules.
- Set up the number. Re-import the Twilio number in ElevenLabs with SMS enabled, and assign your agent to it. In Booker, register the number twice: as a
voicechannel and as ansmschannel. A text is looked up among thesmschannels only, so a number registered only for voice answers404 Unknown numberto a text. - Keep conversation-init as the webhook. ElevenLabs calls it for each new text thread too, with
call_sidempty: that is how Booker knows it's a text. It is still never a tool. - Write for the channel. The
channelvariable isvoiceorsms. Use{{channel}}in the prompt to keep text replies short, for example: "If {{channel}} is sms, reply in one or two short sentences and offer at most three times." - How long a text session lasts. ElevenLabs keeps a thread's variables, so the same
session_idcarries on when the customer replies hours later. A text session lasts 24 hours from the agent's last tool call, and at most 7 days from the first message. After that, tools answer 401Session expired: ask the customer to call, or to text again later (a new thread starts a new session). - STOP. Once the number's texts go to ElevenLabs, a STOP reply no longer reaches Booker. Twilio blocks later texts to that customer from that number (error 21610), and Booker records the opt-out from texts when Twilio refuses one sent from the company's own SMS number: outreach texts, and verification codes sent from it. Booking confirmations come from Booker's shared platform number, so a STOP to that number isn't recorded for the company. When a customer asks in the thread to stop texting them, still call Opt the caller out of outreach (in a text thread it opts out of texts by default).
- START. A customer who later replies START unblocks texts at Twilio, but Booker can't see that reply either, so their texts stay opted out in Booker. The company records it from its own systems (a company endpoint, not an agent tool):
POST /api/v1/customers/{id}/contacts/{contactId}/consentwith{"channel": "sms", "event": "granted", "source": "START reply"}. - Replies to outreach. A customer who replies to an outreach text starts a new thread and can book in it. Outreach text such as "Reply here or call {phone} to schedule." invites that.
- WhatsApp isn't supported.
What the agent can see
The agent tools act only on the session's caller. There is no customer id, company id or phone number parameter to choose whose data is touched, so a model can't be talked into touching someone else's. Agent responses never include the caller's email or phone number, their address or coordinates, how their address was matched to locations, distances, a provider's start address or anyone else's appointments.
Two things are never accepted from the agent: a customer id and override_eligibility. Overriding eligibility is a company decision, made with the company's API key on the company endpoints; the agent acts only on the session's caller, so it can't be talked into booking something the company hasn't allowed.
Describing a tool to the model
Give each tool a description that says when to use it and what to do with each answer. For example, for Find slots:
Name: find_slots
Method and URL: POST https://api.example.com/api/v1/agent/slots
Header: Authorization: Bearer <your API key, stored by the platform>
Description: Find open appointment times for the caller. Use after the caller has
chosen what to book. Offer up to three of the "recommended" slots, saying times in
the slot's timezone. If "providers" is empty, check "reason": "no_address" means ask
for their address and save it first; "out_of_service_area" means the company doesn't
serve that address. "provider_does_not_offer_type" means their own provider doesn't do this; "no_provider_offers_type" means nobody can. For both, say the company will follow up.
Parameters:
session_id (string, required): always {{session_id}} from the conversation start
appointment_type_id (integer, required unless appointment_id is given): from list_bookable
start_date, end_date (YYYY-MM-DD, end exclusive, at most 31 days apart)
scheduling_request_id (integer, optional): the type's scheduling_request.id from
list_bookable; always pass it when that is set
location_id (integer, optional): from list_locations, when the caller names a place
provider_id (integer, optional): from list_providers, only when provider_choice is true
appointment_id (integer, optional): only when rescheduling
Fill session_id from the conversation's variables, not from the model, where your platform allows it. Every endpoint's parameters and answers are in the Agent reference.