Google Calendar API: Service Account Setup
How to access Google Calendar API without OAuth token refresh headaches.
Current Setup (Ilya)
| Parameter | Value |
|---|---|
| Google Cloud Project | calendar-mcp-487708 |
| Service Account | calendar-mcp@calendar-mcp-487708.iam.gserviceaccount.com |
| Credentials File | ~/.takopi/credentials/google-calendar-service-account.json |
| Calendar Access | ccherya@gmail.com (read/write) |
| Status | ✅ Verified working (2026-02-17) |
Related: Secrets and Credentials
Why Service Account?
- No token expiration — key works indefinitely
- No user interaction — no browser needed for authorization
- Server-to-server — ideal for bots and automation
Step 1: Create Google Cloud Project
- Open Google Cloud Console
- Click project dropdown → New Project
- Enter name (e.g.,
calendar-bot) - Click Create
Step 2: Enable Calendar API
- Go to APIs & Services → Library
- Find Google Calendar API
- Click Enable
Step 3: Create Service Account
- Go to APIs & Services → Credentials
- Click Create Credentials → Service account
- Fill in:
- Service account name:
calendar-bot - Service account ID: auto-fills
- Service account name:
- Click Create and Continue
- Roles can be skipped → Continue
- Click Done
Step 4: Create Key
- In Service Accounts list, click on the created account
- Go to Keys tab
- Click Add Key → Create new key
- Select JSON → Create
- File downloads automatically — save it securely
File looks like:
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "calendar-bot@your-project.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token"
}Important: client_email is the service account email, needed for the next step.
Step 5: Share Calendar with Service Account
This is the key step! Service account has no access to your calendars by default.
- Open Google Calendar
- Find the target calendar in the left panel
- Click ⋮ → Settings and sharing
- Scroll to Share with specific people or groups
- Click Add people and groups
- Enter
client_emailfrom the JSON file (e.g.,calendar-bot@your-project.iam.gserviceaccount.com) - Select access level:
- See all event details — read only
- Make changes to events — read and write (needed for creating events)
- Click Send
Step 6: Get Calendar ID
- For primary calendar: your email (e.g.,
user@gmail.com) - For other calendars: Settings → Integrate calendar → Calendar ID
Usage Example (Python)
from google.oauth2 import service_account
from googleapiclient.discovery import build
# Path to service account key
SERVICE_ACCOUNT_FILE = '/path/to/service-account.json'
# Scopes
SCOPES = ['https://www.googleapis.com/auth/calendar']
# Calendar ID (your email or specific calendar ID)
CALENDAR_ID = 'your-email@gmail.com'
# Create credentials
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
# Build service
service = build('calendar', 'v3', credentials=credentials)
# List events
events_result = service.events().list(
calendarId=CALENDAR_ID,
maxResults=10,
singleEvents=True,
orderBy='startTime'
).execute()
events = events_result.get('items', [])
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
print(f"{start}: {event['summary']}")Usage Example (Go)
package main
import (
"context"
"fmt"
"time"
"google.golang.org/api/calendar/v3"
"google.golang.org/api/option"
)
func main() {
ctx := context.Background()
// Create service with credentials file
srv, err := calendar.NewService(ctx,
option.WithCredentialsFile("/path/to/service-account.json"),
option.WithScopes(calendar.CalendarScope),
)
if err != nil {
panic(err)
}
calendarID := "your-email@gmail.com"
// List events
events, err := srv.Events.List(calendarID).
TimeMin(time.Now().Format(time.RFC3339)).
MaxResults(10).
SingleEvents(true).
OrderBy("startTime").
Do()
if err != nil {
panic(err)
}
for _, event := range events.Items {
fmt.Printf("%s: %s\n", event.Start.DateTime, event.Summary)
}
}Creating Events
event = {
'summary': 'Meeting',
'description': 'Discuss project',
'start': {
'dateTime': '2025-02-15T10:00:00+03:00',
'timeZone': 'Europe/Moscow',
},
'end': {
'dateTime': '2025-02-15T11:00:00+03:00',
'timeZone': 'Europe/Moscow',
},
}
created_event = service.events().insert(
calendarId=CALENDAR_ID,
body=event
).execute()
print(f"Created: {created_event.get('htmlLink')}")Common Scopes
| Scope | Description |
|---|---|
calendar.readonly | Read-only access |
calendar | Full read/write access |
calendar.events | Read/write events only |
calendar.events.readonly | Read events only |
Troubleshooting
”Not Found” error
- Make sure calendar is shared with the service account email
- Check Calendar ID is correct
”Insufficient Permission” error
- Check correct access level when sharing
- Make sure you’re using the right scope
”The caller does not have permission”
- Calendar API not enabled in project
- Or service account doesn’t have calendar access