Build an AI scheduling agent with Retell AI that can check availability and schedule jobs using Zenbooker's API with step-by-step instructions.
Overview
In this tutorial, you'll build a voice agent that books jobs end-to-end using the Zenbooker API and Retell AI.
Service Area Check
Validate customer location and check coverage
Live Availability
Fetch real-time timeslots for scheduling
End-to-End Booking
Create complete job with custom services
The agent will:
- Ask for the customer's ZIP code
- Check if that location is in your service area
- Ask what they need done and estimate the duration
- Fetch real-time availability
- Collect their contact info and address
- Create a new job in Zenbooker using a custom service (defined from their description)
Prerequisites
Required Accounts & Credentials
- A Zenbooker API key
- A Retell AI account
- (Optional) A phone number in Retell for testing
Step 1 — Create the Agent
In Retell, click Create Agent → Voice.
Response engine: Conversation Flow
Voice: Choose your preferred TTS (e.g., OpenAI Alloy, ElevenLabs, etc.)
Language: English (US)
You are a helpful scheduling assistant for [YOUR COMPANY NAME].
Your job is to help customers book service appointments by:
1. Collecting their location (ZIP code)
2. Understanding what service they need
3. Finding available appointment times
4. Gathering their contact information
5. Confirming and creating their booking
Be friendly, professional, and efficient. Always confirm details before creating the appointment.
Help collect customer details and help them book their service appointment through Zenbooker.Step 2 — Conversation Flow Design
Here's the high-level conversation flow our agent will follow:
graph TD
%% --- Style Definitions ---
classDef customerNode fill:#e0f2f7,stroke:#26c6da,stroke-width:2px,color:#212121;
classDef agentNode fill:#e8f5e9,stroke:#66bb6a,stroke-width:2px,color:#212121;
classDef apiNode fill:#fff3e0,stroke:#ffb74d,stroke-width:2px,color:#212121;
classDef dataNode fill:#ede7f6,stroke:#7e57c2,stroke-width:2px,color:#212121;
classDef decisionNode fill:#cfd8dc,stroke:#607d8b,color:#212121,font-weight:bold;
classDef startNode fill:#c8e6c9,stroke:#388e3c,font-weight:bold;
classDef endNode fill:#ffcdd2,stroke:#d32f2f,font-weight:bold;
%% --- Flow Start ---
START("Call Initiated"):::startNode --> A("Agent: Greets & asks for ZIP code"):::agentNode;
%% --- 1. Service Area & Data Storage ---
A --> B("Customer: Provides ZIP"):::customerNode;
B --> C["API Call: /service_area_check"]:::apiNode;
C --> D{"Serviceable?"}:::decisionNode;
D -->|No| E("Agent: Sorry, not in area. New ZIP?"):::agentNode;
E --> A;
D -->|Yes| F("Agent: Stores territory_id, lat, & lng"):::dataNode;
%% --- 2. Define Custom Service ---
F --> G("Agent: What needs to be hauled away?"):::agentNode;
G --> H("Customer: Describes Items"):::customerNode;
H --> I("Agent: Extracts items & Estimates duration"):::agentNode;
%% --- 3. Find & Select Timeslot ---
I --> J["API Call: Fetch available timeslots"]:::apiNode;
J --> K("Agent: Presents available times"):::agentNode;
K --> L{"Customer selects a time?"}:::decisionNode;
L -->|Yes| S("Customer: Confirms Chosen Slot"):::customerNode;
L -->|"Wants more options"| M{"From currently fetched slots?"}:::decisionNode;
M -->|Yes| K;
M -->|No, wants later date| N["API Call: Fetch more timeslots"]:::apiNode;
N --> K;
%% --- 4. Finalize Booking ---
S --> T("Agent: Collects Name, Address, Phone, Email"):::agentNode;
T --> U("Customer: Provides Contact Info"):::customerNode;
U --> V("Agent: Reviews all details for confirmation"):::agentNode;
V --> W{"Customer Confirms Booking?"}:::decisionNode;
W -->|No / Correct Info| T;
W -->|Yes| X["API Call: /jobs (Create Job)"]:::apiNode;
X --> Y{"Job Created Successfully?"}:::decisionNode;
Y -->|Failure| Z("Agent: Issue booking. Please call back."):::agentNode;
Y -->|Success| AA("Agent: Job booked! Confirmation sent."):::agentNode;
%% --- Flow End ---
AA --> END("End Call"):::endNode;
Z --> END;
Conversation Flow Breakdown
Welcome → ZIP Collection
- Greet the customer warmly
- Ask for their ZIP code to check service coverage
- Extract
postal_codeparameter
Service Area Validation
- Call
service_area_checkwith the postal code - If
in_service_area = true: Continue to service inquiry - If
in_service_area = false: Politely explain coverage limits, optionally retry
Service Details
- Ask "What do you need help with?" or similar
- Extract
service_description(free text) - Estimate or ask for
duration_minutes
Scheduling
- Set
dateto today's date (YYYY-MM-DD format) - Call
get_timeslotsto fetch available appointments - Present 2-3 options to the customer
- Extract chosen
timeslot_id
Customer Information
- Collect name, phone, email
- Collect complete service address
- Confirm notification preferences (SMS/Email)
Final Confirmation
- Review all details with customer
- Call
create_jobto book the appointment - Provide confirmation number and next steps
Step 3: Add Zenbooker API Functions in Retell
You'll connect your agent to Zenbooker's API by adding three custom functions.
graph LR
A[ZIP Code] --> B[service_area_check]
B --> C[territory_id + coordinates]
C --> D[get_timeslots]
D --> E[timeslot_id]
E --> F[create_job]
1. Service Area Coverage Check
Purpose: Check if the customer's location is within any of your service territories and get the territory_id that services that area.
Method: GET
URL: https://api.zenbooker.com/v1/scheduling/service_area_check
Authorization: Bearer YOUR_ZENBOOKER_API_KEY postal_code: {{postal_code}} in_service_area: in_service_area
territory_id: service_territory.id
lat: customer_location.coordinates.lat
lng: customer_location.coordinates.lngAgent Logic: Call this after extracting a valid ZIP code. If
in_service_areais true, continue to "What do you need done?"; otherwise, respond politely that the area isn't serviced. Save the territory_id — you'll need it for the next step.
2. Get Available Timeslots
Purpose: Fetch available appointment times for the specific territory that services the customer's location.
Method: GET
URL: https://api.zenbooker.com/v1/scheduling/timeslots
Authorization: Bearer YOUR_ZENBOOKER_API_KEY date: {{date}}
territory_id: {{territory_id}}
days: 10
duration: {{duration_minutes}}
lat: {{lat}}
lng: {{lng}} date: next_dateAgent Logic: The agent reads the first few timeslots and offers them to the customer. If the customer wants to hear more, the agent uses
next_dateto fetch additional results.
3. Create Job Booking
Purpose: Create the booking in Zenbooker once the customer confirms their time and details.
Method: POST
URL: https://api.zenbooker.com/v1/jobs
Authorization: Bearer YOUR_ZENBOOKER_API_KEY
Content-Type: application/json{
"description": "Create a Zenbooker job using ONLY custom services and a timeslot_id.",
"additional_properties": false,
"type": "object",
"title": "create_job_params_custom_service_only",
"properties": {
"address": {
"description": "Service address.",
"additionalProperties": false,
"type": "object",
"properties": {
"country": {
"type": "string",
"const": "US",
"description": "Two-letter country code. Must be \"US\"."
},
"state": {
"type": "string",
"description": "State/Province code"
},
"postal_code": {
"type": "string"
},
"line2": {
"type": "string",
"description": "Apt/Unit/Floor"
},
"city": {
"type": "string"
},
"line1": {
"type": "string",
"description": "Street address"
}
},
"required": ["line1", "city", "state", "postal_code", "country"]
},
"notes": {
"type": "string",
"description": "Freeform notes (e.g., gate code, pet info)."
},
"email_notifications": {
"type": "boolean",
"description": "Enable email notifications."
},
"timeslot_id": {
"type": "string",
"description": "ID returned by the timeslots API (e.g., slot_1762178400_1762189200)."
},
"services": {
"minItems": 1,
"description": "At least one service; each item must define a custom service.",
"type": "array",
"items": {
"additionalProperties": false,
"type": "object",
"properties": {
"custom_service": {
"description": "Ad-hoc service definition.",
"additionalProperties": false,
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the custom service."
},
"duration": {
"type": "integer",
"description": "Duration in minutes."
},
"description": {
"type": "string",
"description": "Optional description."
},
"taxable": {
"type": "boolean",
"description": "Whether this service is taxable."
},
"price": {
"type": "number",
"description": "Total price for this service."
}
},
"required": ["name", "price", "duration"]
}
},
"required": ["custom_service"]
}
},
"assignment_method": {
"type": "string",
"const": "auto",
"description": "Provider assignment method. This flow always uses auto-assign."
},
"sms_notifications": {
"type": "boolean",
"description": "Enable SMS notifications (collect consent first)."
},
"territory_id": {
"type": "string",
"description": "Zenbooker territory ID that owns this job."
},
"customer": {
"description": "New customer details.",
"additionalProperties": false,
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Full name of the customer."
},
"phone": {
"type": "string",
"description": "Customer phone number (E.164 or local format)."
},
"email": {
"type": "string",
"description": "Customer email address."
}
},
"required": ["name", "phone"]
}
},
"required": ["territory_id", "timeslot_id", "customer", "services", "assignment_method"]
}Next Steps
Test Your Agent
Use Retell's testing interface to simulate the booking flow
Configure Webhooks
Set up webhooks to track booking status changes