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

Conversation-init starts a session. Then: create the customer if they're new, list what they may book, find slots, and book. Reschedule, cancel, contacts, address and opt-out are side paths. conversation-init webhook customers new callers only bookable slots offer recommended book any time address reschedule cancel contacts opt-out

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, and needs_address means save their address first.
  • List providers by name takes the appointment_type_id and, optionally, a location_id. When provider_choice is 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 shows provider_choice for 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_id and provider_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), and sms in 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 (in or out). Send line1 and either a postal_code or both city and state; 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_id and the code. A code expires after 10 minutes or 5 wrong attempts, and Code expired is also the answer for an unknown, already-used or another conversation's verification_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 voice or sms, for the number they're calling or texting from; without a channel it is the conversation's own (sms in a text thread, voice on a call). It is one channel per call: a caller who asks not to be called or texted needs two calls, voice and then sms.

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 voice channel and as an sms channel. A text is looked up among the sms channels only, so a number registered only for voice answers 404 Unknown number to a text.
  • Keep conversation-init as the webhook. ElevenLabs calls it for each new text thread too, with call_sid empty: that is how Booker knows it's a text. It is still never a tool.
  • Write for the channel. The channel variable is voice or sms. 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_id carries 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 401 Session 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}/consent with {"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.

Let’s build the booking layer.

This local preview doesn’t submit access requests yet. The signup destination will be connected in a later version.