Skip to main content

AI Scheduling

Let callers book appointments over the phone without talking to a human. DialStack handles the voice conversation — greeting, slot negotiation, callback collection — and calls your system to read availability and write the booking.

AI Scheduling is powered by the AI Agents API. Each AI agent auto-provisions a voice app and an extension, has a configurable persona and FAQ, and optionally calls your scheduling endpoints during the conversation.

What you build​

Three HTTPS endpoints under a base URL you give us:

PathPurpose
POST {base}/customers/lookupMatch the caller's phone number to an existing customer record, so the agent can personalize the conversation.
POST {base}/availability/searchReturn open slots in a date range.
POST {base}/bookingsCreate a booking for the slot the caller picked.

These are request/response calls made during the conversation, not fire-and-forget notifications: what you return is what the agent works with. They are separate from webhook endpoints, and are not configured through the Webhook Endpoints API.

The Webhook Protocol reference defines each request and the response shape the agent understands best.

Create the agent​

Configure the agent via POST /v1/ai-agents. Point scheduling.webhook_url at the base URL that hosts the three paths above. Scheduling is on for an agent while scheduling.webhook_url is set; each agent has its own.

Bash
curl -X POST https://api.dialstack.ai/v1/ai-agents \
-H 'Authorization: Bearer sk_live_YOUR_KEY' \
-H 'DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41' \
-H 'Content-Type: application/json' \
-d '{
"name": "Front Desk Receptionist",
"extension_number": "200",
"persona_name": "Tony",
"greeting_name": "Jones Family Dental",
"instructions": "You are the receptionist. Be warm and professional. Offer to book an appointment if the caller does not specify a reason.",
"faq_responses": [
{ "question": "What are your business hours?", "answer": "We are open Monday through Friday, 9 AM to 5 PM." },
{ "question": "Do you take walk-ins?", "answer": "Walk-ins are welcome, but appointments are prioritized." }
],
"scheduling": {
"webhook_url": "https://api.clinic.io/dialstack"
}
}'

The response includes the agent's id and voice_app, the voice app it manages for you. Extension 200 now routes callers to the agent.

How a booking call plays out​

The AI handles disambiguation ("which Tuesday?"), date math, and re-prompting. You only see the resolved request.

The requests​

Every request is a POST with a JSON body and these headers:

HeaderDescription
Content-Typeapplication/json
X-DialStack-SignatureHMAC-SHA256 signature of the request
X-DialStack-Account-IdThe account whose caller the agent is talking to

Every body carries account_id, the same account as the header.

  • /customers/lookup sends customer.phone, the caller's number in E.164. Return whether you found the caller and, if so, their name and any upcoming appointment.
  • /availability/search sends query.filter.start_at_range with start_at and end_at, the dates the caller asked about (YYYY-MM-DD). Return the open slots in that range.
  • /bookings sends idempotency_key and a booking with start_at (ISO 8601), duration_minutes, customer (phone, name) and, when the caller gave one, notes with the reason for the visit. Return the confirmed booking.

The full schemas and response shapes are in the Webhook Protocol reference.

Verifying requests​

Requests are signed with the secret of the agent's voice app, using the same scheme as every DialStack webhook: see Signature verification. Read the secret with GET /v1/voice-apps/{voice_app_id}, using the agent's voice_app. If you rotate the voice app's secret, the agent signs with the new one from the next call.

Testing your handler​

Sign a test request yourself and send it to your handler, with the voice app's secret:

Bash
TIMESTAMP=$(date +%s)
PAYLOAD='{"account_id":"ACCOUNT_ID","query":{"filter":{"start_at_range":{"start_at":"2026-04-28","end_at":"2026-04-28"}}}}'
SECRET="your_voice_app_secret"
SIGNATURE=$(printf '%s' "${TIMESTAMP}.${PAYLOAD}" | openssl dgst -sha256 -hmac "${SECRET}" | cut -d' ' -f2)

curl -X POST http://localhost:3000/dialstack/availability/search \
-H "Content-Type: application/json" \
-H "X-DialStack-Signature: t=${TIMESTAMP},v1=${SIGNATURE}" \
-H "X-DialStack-Account-Id: ACCOUNT_ID" \
-d "${PAYLOAD}"

Responses and errors​

  • Return 2xx with a JSON object. The agent reads the object you return as is, so any extra fields you include (account-specific context, a time-sensitive promotion) are available to it in the conversation.
  • Respond within 10 seconds. A timeout, an unreachable URL, or a non-2xx status is reported to the agent as a failed request, with the status code but not your response body.
  • For a booking you can't make, such as a slot taken since the search, return a non-2xx status so the agent doesn't confirm it to the caller.

Idempotency​

Booking requests carry an idempotency_key. The agent sends the same key if it books the same slot for the same caller twice in one call, so treat a repeated key as the same booking:

  1. Store the idempotency key when processing a booking.
  2. If the same key arrives again, return the original booking response.
  3. Keys can be safely expired after 24 hours.

Route calls to the agent​

Callers reach the agent by its extension. To put it in a dial plan, point a voice_app node at the agent's voice app:

TypeScript
{
id: 'ai_receptionist',
type: 'voice_app',
config: { voice_app: 'va_01h2xcejqtf2nbrexx3vqjhp49' }, // the agent's voice_app
}

An agent's voice app always takes control of the call, so use the default control mode: a notify node can't reference it.

Typical patterns:

  • After-hours fallback — schedule node → ring_all_users during business hours, a voice_app node for the agent when closed.
  • Overflow — ring_all_users with a short timeout, next pointing at the agent's node when no one picks up.
  • Direct line — an inbound number routed straight at the agent's extension for a 24/7 booking line.

Updating the agent​

Agent config (persona, instructions, FAQs, scheduling URL) can be updated via POST /v1/ai-agents/{id}. Changes take effect on the next call.

See also​