# Google Calendar

> Connect Google Calendar for scheduling, availability checks, and event management

Struere integrates with Google Calendar through OAuth2, giving agents the ability to list, create, update, and delete calendar events and check availability.

## Setup

### 1. Connect Google Calendar

In the dashboard, navigate to **Integrations > Google Calendar** and click **Connect**. This initiates an OAuth2 flow with Google.

### 2. Grant permissions

Authorize Struere to access your Google Calendar. The connection is stored in the `calendarConnections` table, scoped to the current environment.

### 3. Add calendar tools to your agent

```typescript
import { defineAgent } from 'struere'

export default defineAgent({
  name: "Scheduler",
  slug: "scheduler",
  tools: [
    "calendar.list",
    "calendar.create",
    "calendar.update",
    "calendar.delete",
    "calendar.freeBusy",
    "entity.query",
  ],
  systemPrompt: `You are a scheduling assistant for {{organizationName}}.
Current time: {{currentTime}}

When booking:
1. Check availability with calendar.freeBusy
2. Create the event with calendar.create
3. Record the session entity with entity.create`,
  model: { model: "openai/gpt-5-mini" },
})
```

## Footguns

Behaviors that aren't obvious from the type signatures.

### Symptom: Calendar tools start failing with "User has not connected Google OAuth"
**Cause:** OAuth token expired in Clerk but `calendarConnections.status` is still "connected" -- no refresh logic.

**Fix:** Have the user reconnect Google in Clerk. Consider monitoring this and prompting users on persistent failures.

### Symptom: `calendar.create` fails with 403 Forbidden
**Cause:** User selected a shared calendar they later lost access to; the stored `calendarId` is stale.

**Fix:** Re-pick a calendar in the dashboard.

### Symptom: Updating an event clears all attendee RSVPs
**Cause:** `calendar.update` accepts `attendees` and PATCHes the full list -- Google replaces existing acceptances.

**Fix:** Pass only the additions/removals you intend; don't include `attendees` if you only want to update other fields.

### Symptom: Updating one instance of a recurring event modifies the whole series
**Cause:** `calendar.update` doesn't expose `recurrenceId`.

**Fix:** Today, modify recurring events outside Struere (Google web UI) until the tool exposes per-instance updates.

### Symptom: Two duplicate events created from the same agent run
**Cause:** `calendar.create` has no idempotency key.

**Fix:** Include unique data in the title/description so you can detect dupes downstream; or wrap creation in your own dedup check.

See [Platform Gotchas](/platform/gotchas) for cross-cutting silent failures across the platform.

## Available Tools

### calendar.list

List calendar events within a time range.

**Parameters:**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | `string` | Yes | User ID (Convex or Clerk) whose calendar to query |
| `timeMin` | `string` | Yes | Start of range (ISO 8601) |
| `timeMax` | `string` | Yes | End of range (ISO 8601) |
| `maxResults` | `number` | No | Maximum events to return |

### calendar.create

Create a new calendar event.

**Parameters:**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | `string` | Yes | User ID (Convex or Clerk) whose calendar to create the event on |
| `summary` | `string` | Yes | Event title |
| `description` | `string` | No | Event description |
| `startTime` | `string` | Yes | Start time (ISO 8601) |
| `endTime` | `string` | No | End time (ISO 8601). Provide either `endTime` or `durationMinutes` |
| `durationMinutes` | `number` | No | Duration in minutes. Used to calculate `endTime` if not provided |
| `attendees` | `string[]` | No | Email addresses of attendees |
| `timeZone` | `string` | No | IANA timezone (e.g., `America/Santiago`) |
| `addGoogleMeet` | `boolean` | No | Set to `true` to auto-create a Google Meet link for the event |

### calendar.update

Update an existing calendar event.

**Parameters:**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | `string` | Yes | User ID (Convex or Clerk) whose calendar contains the event |
| `eventId` | `string` | Yes | Google Calendar event ID |
| `summary` | `string` | No | Updated title |
| `description` | `string` | No | Updated description |
| `startTime` | `string` | No | Updated start time |
| `endTime` | `string` | No | Updated end time |
| `attendees` | `string[]` | No | Updated attendees |

### calendar.delete

Delete a calendar event.

**Parameters:**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | `string` | Yes | User ID (Convex or Clerk) whose calendar contains the event |
| `eventId` | `string` | Yes | Google Calendar event ID |

### calendar.freeBusy

Check availability across calendars for a time range.

**Parameters:**

| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | `string` | Yes | User ID (Convex or Clerk) whose availability to check |
| `timeMin` | `string` | Yes | Start of range (ISO 8601) |
| `timeMax` | `string` | Yes | End of range (ISO 8601) |

Returns busy time slots within the range, allowing agents to find open slots before booking.

## Common Patterns

### Booking with Availability Check

```
User: "Book a session with Alice on Tuesday at 2 PM"

Agent flow:
1. calendar.freeBusy — check if 2 PM Tuesday is available
2. If busy → suggest alternative times from the free slots
3. If free → calendar.create with the event details
4. entity.create — record the session in Struere
```

### Timezone Handling

All times are in ISO 8601 format. The Google Calendar API respects the timezone in the ISO string. If no timezone offset is provided, the calendar's default timezone is used.

```
"2025-03-15T14:00:00-05:00"  ← Eastern Time
"2025-03-15T14:00:00Z"        ← UTC
"2025-03-15T14:00:00+09:00"  ← Japan Standard Time
```

Instruct your agent about timezone expectations in the system prompt to avoid confusion.

## Environment Scoping

Calendar connections are environment-scoped. A connection created in development is not available in production. Connect Google Calendar separately in each environment where you need it.
