Google Calendar API: Service Account Setup

How to access Google Calendar API without OAuth token refresh headaches.

Current Setup (Ilya)

ParameterValue
Google Cloud Projectcalendar-mcp-487708
Service Accountcalendar-mcp@calendar-mcp-487708.iam.gserviceaccount.com
Credentials File~/.takopi/credentials/google-calendar-service-account.json
Calendar Accessccherya@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

  1. Open Google Cloud Console
  2. Click project dropdown → New Project
  3. Enter name (e.g., calendar-bot)
  4. Click Create

Step 2: Enable Calendar API

  1. Go to APIs & ServicesLibrary
  2. Find Google Calendar API
  3. Click Enable

Step 3: Create Service Account

  1. Go to APIs & ServicesCredentials
  2. Click Create CredentialsService account
  3. Fill in:
    • Service account name: calendar-bot
    • Service account ID: auto-fills
  4. Click Create and Continue
  5. Roles can be skipped → Continue
  6. Click Done

Step 4: Create Key

  1. In Service Accounts list, click on the created account
  2. Go to Keys tab
  3. Click Add KeyCreate new key
  4. Select JSONCreate
  5. 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.

  1. Open Google Calendar
  2. Find the target calendar in the left panel
  3. Click ⋮ → Settings and sharing
  4. Scroll to Share with specific people or groups
  5. Click Add people and groups
  6. Enter client_email from the JSON file (e.g., calendar-bot@your-project.iam.gserviceaccount.com)
  7. Select access level:
    • See all event details — read only
    • Make changes to events — read and write (needed for creating events)
  8. Click Send

Step 6: Get Calendar ID

  • For primary calendar: your email (e.g., user@gmail.com)
  • For other calendars: SettingsIntegrate calendarCalendar 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

ScopeDescription
calendar.readonlyRead-only access
calendarFull read/write access
calendar.eventsRead/write events only
calendar.events.readonlyRead 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