How to Build a Claude-Powered Meeting Scheduler Agent
Building a Claude-powered meeting scheduler agent means wiring Claude's tool use capability to your calendar API so the model can read a request like "find 30 minutes with Sarah next week, avoid Mondays" and turn it into an actual calendar event — without you writing a rules engine for every possible phrasing. The core pattern is always the same: Claude interprets intent, calls functions you define (check availability, create event, send invite), and you execute those functions against real calendar data.
This article walks through the architecture, the tool definitions you need, a working code example, and the production details (timezones, confirmations, idempotency) that separate a demo from something you'd actually ship.
Why tool use is the right approach
A naive version of this agent would try to get Claude to output a date and time directly in free text, then regex-parse it. That breaks constantly — ambiguous phrasing, timezone confusion, double-bookings. The reliable approach is function calling: you give Claude a small set of tools with strict JSON schemas, and Claude decides when and how to call them based on the conversation. Claude never touches your calendar directly — it just returns structured tool calls, your backend executes them, and you feed the results back.
If you haven't used Claude's tool use before, it's worth reading through a function calling tutorial first, but here's the scheduler-specific version.
The three tools you actually need
For most scheduler agents, three tools cover almost every use case:
- check_availability — takes a list of participant emails, a date range, and a duration, returns open slots.
- create_event — takes a confirmed time, title, participants, and optional location/link, creates the calendar event.
- send_invite — optional, if your calendar provider doesn't auto-notify attendees on event creation.
Keep the schemas narrow. Don't let Claude guess timezones — require an explicit IANA timezone string in every tool input, and normalize everything server-side to UTC before hitting your calendar API.
{
"name": "check_availability",
"description": "Find open time slots for a list of participants within a date range.",
"input_schema": {
"type": "object",
"properties": {
"participants": { "type": "array", "items": { "type": "string" } },
"start_date": { "type": "string", "description": "ISO 8601 date" },
"end_date": { "type": "string", "description": "ISO 8601 date" },
"duration_minutes": { "type": "integer" },
"timezone": { "type": "string", "description": "IANA timezone, e.g. Europe/Paris" }
},
"required": ["participants", "start_date", "end_date", "duration_minutes", "timezone"]
}
}
Wiring it up with a system prompt
The system prompt is where you encode the rules Claude should never break: always confirm before booking, never assume a timezone, never double-book. Something like:
You are a scheduling assistant. You can check calendar availability and
create events, but you must never call create_event without first
presenting the proposed time to the user and getting explicit confirmation
in the conversation. Always ask for or confirm the timezone before
checking availability. If no duration is given, default to 30 minutes.
This single paragraph eliminates most of the failure modes you'd otherwise have to catch in code.
Running the loop
The agent loop is a standard tool-use cycle: send the conversation, check if Claude returned a tool_use block, execute it, append the result, send again. Here's a minimal version using SubToAPI, which exposes Claude as a plain HTTPS endpoint with your sub_live_... key:
async function runSchedulerTurn(messages, tools) {
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system: SCHEDULER_SYSTEM_PROMPT,
tools,
messages
})
});
return res.json();
}
When the response contains a tool_use block, run your real calendar logic (e.g. a call to Google Calendar's freebusy API), then push a tool_result message back with the output before calling again. Repeat until Claude returns a plain text turn — that's the point where it's either asking the user a clarifying question or confirming the booked event.
Handling multi-turn negotiation
Scheduling is rarely one shot. "Does 2pm work?" → "No, I have something then, how about Thursday morning?" → "Thursday 9am works." Because you're sending the full message history on every call, Claude naturally tracks this negotiation without extra state management on your side — you just keep appending to the messages array and let the model reconcile constraints across turns.
For agents that need to show live progress (e.g. "checking calendars... found 3 slots... booking..."), streaming responses make the UI feel responsive instead of waiting on a single blocking call — covered in the streaming docs.
Production considerations
A few things that matter once this leaves your dev environment:
- Idempotency: tag each
create_eventcall with a request ID so a retried tool call doesn't create duplicate events. - Confirmation gate: never let the model book without an explicit "yes" in the transcript — enforce this in your tool executor too, not just the prompt.
- Timezone normalization: store and compare everything in UTC; only convert to local time for display.
- Rate limits and cost visibility: scheduling agents can run several tool-use round trips per booking, which adds up across users. If you're running this for a team or multiple customers, having per-request usage metadata matters for knowing what each conversation actually costs.
SubToAPI handles the API layer for this: application keys per project, streaming, tool use support, and usage metadata on every response so you can see token and cost breakdowns without building your own logging. If you're prototyping this agent, the quickstart gets you a working key and your first request in a few minutes.
Getting started
Start small: one tool (check_availability), one calendar, one user. Get the confirmation flow right before adding create_event and multi-participant logic. The agent pattern scales cleanly once the core loop — tool call, execute, feed back, repeat — is solid.
If you want the full request/response shapes for messages and tool calls before wiring up your calendar integration, the docs cover both in detail, and pricing is predictable per seat if you're building this for a team rather than a single side project.
questions
Do I need a separate calendar API, or does Claude handle scheduling directly? Claude only generates structured tool calls — it has no access to real calendars. You still need a calendar provider's API (Google Calendar, Outlook, Cal.com, etc.) behind your check_availability and create_event tool executors.
How do I stop the agent from booking without permission? Enforce it in two places: the system prompt should instruct Claude to confirm before calling create_event, and your tool executor should independently require a confirmation flag in the request before writing to the calendar.
What model should I use for a scheduling agent? A mid-tier model like Claude Sonnet is usually sufficient — scheduling involves structured reasoning over a handful of constraints, not deep creative generation, so you don't need the largest available model for good results.