Scheduling is a tax on time — every back-and-forth email chain and manual calendar block is time not spent on actual work. AI agents connected to Google Calendar eliminate this overhead by finding availability, creating events with all the right details, and managing calendar hygiene automatically.
For teams with heavy meeting loads, executives with complex calendars, and anyone who handles scheduling for multiple people, Google Calendar AI integration delivers immediate and measurable productivity gains.
What AI Agents Can Do With Google Calendar Access#
Intelligent Scheduling
- Parse natural language scheduling requests: "set up a 30-minute call with Alex next Tuesday afternoon"
- Check availability for all attendees before proposing a time
- Find optimal meeting times that minimize context-switching
- Create events with video conference links, meeting agendas, and prep materials
Calendar Management
- Block focus time automatically based on productivity patterns
- Reschedule conflicting events with minimal disruption
- Send pre-meeting reminders with relevant documents and context
- Detect and clean up duplicate or expired calendar events
Schedule Intelligence
- Generate daily briefings: what's on the agenda, who you're meeting, what to prepare
- Identify meeting-heavy days and suggest protection for deep work blocks
- Summarize recurring meeting attendance and suggest which to cancel
- Alert when back-to-back meetings leave no buffer time
Setting Up Google Calendar API Access#
The Calendar API uses the same Google Cloud project and OAuth setup as Gmail:
pip install google-api-python-client google-auth google-auth-oauthlib python-dotenv langchain langchain-openai
import os
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from datetime import datetime, timedelta, timezone
# Include both scopes if using Gmail + Calendar together
SCOPES = ['https://www.googleapis.com/auth/calendar']
def get_calendar_service():
creds = None
if os.path.exists('token_calendar.json'):
creds = Credentials.from_authorized_user_file('token_calendar.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token_calendar.json', 'w') as token:
token.write(creds.to_json())
return build('calendar', 'v3', credentials=creds)
service = get_calendar_service()
Option 1: No-Code with n8n#
Meeting Request Automation Workflow#
- Gmail Trigger: Detect emails with scheduling keywords ("schedule a call", "available for a meeting")
- OpenAI: "Extract meeting details: proposed date/time, duration, attendees, purpose"
- Google Calendar node: Check availability for the proposed time
- Switch: If available → create event; if not → find next available slot
- Google Calendar: Create event with attendees and Meet link
- Gmail: Reply to the original email confirming the meeting with calendar link
Option 2: LangChain with Python#
Build Google Calendar Tools#
from langchain.tools import tool
service = get_calendar_service()
TIMEZONE = "America/New_York" # Set to your timezone
CALENDAR_ID = "primary"
@tool
def check_availability(date: str, duration_minutes: int = 30,
attendee_emails: list = None) -> str:
"""
Check availability for a given date. date format: 'YYYY-MM-DD'.
Returns available time slots for scheduling meetings.
"""
# Build freebusy query
day_start = datetime.fromisoformat(f"{date}T09:00:00").astimezone(timezone.utc)
day_end = datetime.fromisoformat(f"{date}T18:00:00").astimezone(timezone.utc)
calendars = [{"id": CALENDAR_ID}]
if attendee_emails:
calendars.extend([{"id": email} for email in attendee_emails])
freebusy = service.freebusy().query(body={
"timeMin": day_start.isoformat(),
"timeMax": day_end.isoformat(),
"timeZone": TIMEZONE,
"items": calendars
}).execute()
# Collect all busy periods
busy_periods = []
for cal_data in freebusy.get("calendars", {}).values():
for period in cal_data.get("busy", []):
start = datetime.fromisoformat(period["start"].replace("Z", "+00:00"))
end = datetime.fromisoformat(period["end"].replace("Z", "+00:00"))
busy_periods.append((start, end))
busy_periods.sort(key=lambda x: x[0])
# Find available slots
available = []
current = day_start
for busy_start, busy_end in busy_periods:
if current + timedelta(minutes=duration_minutes) <= busy_start:
slot_end = min(busy_start, current + timedelta(hours=2))
available.append(f"{current.strftime('%I:%M %p')} – {slot_end.strftime('%I:%M %p')}")
current = max(current, busy_end)
if current + timedelta(minutes=duration_minutes) <= day_end:
available.append(f"{current.strftime('%I:%M %p')} – {day_end.strftime('%I:%M %p')}")
if not available:
return f"No available {duration_minutes}-minute slots on {date}"
return f"Available slots on {date}:\n" + "\n".join(f" • {slot}" for slot in available[:5])
@tool
def create_event(title: str, start_datetime: str, duration_minutes: int = 30,
attendee_emails: list = None, description: str = "",
add_meet_link: bool = True) -> str:
"""
Create a Google Calendar event. start_datetime format: 'YYYY-MM-DDTHH:MM:SS'.
attendee_emails: list of email strings.
"""
start = datetime.fromisoformat(start_datetime)
end = start + timedelta(minutes=duration_minutes)
event_body = {
"summary": title,
"description": description,
"start": {"dateTime": start.isoformat(), "timeZone": TIMEZONE},
"end": {"dateTime": end.isoformat(), "timeZone": TIMEZONE},
}
if attendee_emails:
event_body["attendees"] = [{"email": email} for email in attendee_emails]
if add_meet_link:
event_body["conferenceData"] = {"createRequest": {"requestId": f"meet-{int(start.timestamp())}"}}
params = {"calendarId": CALENDAR_ID, "sendUpdates": "all", "body": event_body}
if add_meet_link:
params["conferenceDataVersion"] = 1
event = service.events().insert(**params).execute()
meet_link = event.get("conferenceData", {}).get("entryPoints", [{}])[0].get("uri", "No Meet link")
return f"Event created: '{title}'\nTime: {start.strftime('%B %d, %Y at %I:%M %p')}\nMeet: {meet_link}\nLink: {event.get('htmlLink')}"
@tool
def get_todays_schedule() -> str:
"""Get all calendar events for today."""
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + timedelta(days=1)
events = service.events().list(
calendarId=CALENDAR_ID,
timeMin=today.astimezone(timezone.utc).isoformat(),
timeMax=tomorrow.astimezone(timezone.utc).isoformat(),
singleEvents=True,
orderBy="startTime"
).execute().get("items", [])
if not events:
return "No events scheduled for today"
lines = [f"Today's schedule ({today.strftime('%A, %B %d')}):"]
for event in events:
start = event.get("start", {}).get("dateTime", event.get("start", {}).get("date", ""))
if "T" in start:
start_time = datetime.fromisoformat(start.replace("Z", "+00:00")).strftime("%I:%M %p")
else:
start_time = "All day"
attendees = [a.get("email", "") for a in event.get("attendees", []) if not a.get("self")]
attendee_str = f" | With: {', '.join(attendees[:3])}" if attendees else ""
lines.append(f" {start_time}: {event.get('summary', 'No title')}{attendee_str}")
return "\n".join(lines)
@tool
def block_focus_time(date: str, start_hour: int = 10, duration_hours: int = 2,
title: str = "Focus Time — No Meetings") -> str:
"""Block focus time on the calendar to protect deep work periods."""
start_dt = f"{date}T{start_hour:02d}:00:00"
result = create_event.run({
"title": title,
"start_datetime": start_dt,
"duration_minutes": duration_hours * 60,
"add_meet_link": False,
"description": "Protected focus time — scheduled by AI assistant"
})
return result
Personal Scheduling Agent#
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [check_availability, create_event, get_todays_schedule, block_focus_time]
prompt = ChatPromptTemplate.from_messages([
("system", """You are a scheduling assistant with access to Google Calendar.
When handling scheduling requests:
1. Always check availability before creating events
2. Default to 30-minute meetings unless specified
3. Add Google Meet links for remote meetings
4. Confirm the event details before creating (title, time, attendees)
5. Block focus time when requested to protect deep work
Today's date context: use the check_availability and get_todays_schedule tools to understand the calendar state before making decisions.
Timezone: America/New_York — convert user's natural language times to this timezone."""),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# Natural language scheduling
result = executor.invoke({
"input": "Schedule a 45-minute product review with alice@company.com and bob@company.com tomorrow afternoon. Add a Google Meet link."
})
print(result["output"])
Rate Limits and Best Practices#
| Google Calendar API limit | Value |
|---|---|
| Requests per day | 1,000,000 |
| Requests per 100 seconds | 100 |
| Events per calendar | No limit |
Best practices:
- Batch freebusy checks: Use the freebusy API for multiple attendees in one call rather than checking calendars individually
- Use incremental sync: For agents that monitor calendar changes, use the sync token pattern to only fetch changed events
- Handle all-day events separately: All-day events use
dateformat instead ofdateTimein the API response - Confirm before creating: For externally visible events with attendees, have the agent confirm details before calling the API
Next Steps#
- AI Agents Gmail Integration — Parse scheduling emails and auto-create calendar events
- AI Agents Slack Integration — Post daily calendar briefs to Slack
- Build an AI Agent with LangChain — Complete framework tutorial
- Human-in-the-Loop Agents — When to confirm before taking calendar actions