home / skills / jezweb / claude-skills / google-workspace

google-workspace skill

/skills/google-workspace

This skill helps you build and troubleshoot Google Workspace API integrations with OAuth, batch requests, and rate-limit handling.

npx playbooks add skill jezweb/claude-skills --skill google-workspace

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
11.0 KB
---
name: google-workspace
description: |
  Build integrations with Google Workspace APIs (Gmail, Calendar, Drive, Sheets, Docs, Chat, Meet, Forms, Tasks, Admin SDK). Covers OAuth 2.0, service accounts, rate limits, batch operations, and Cloudflare Workers patterns.

  Use when building MCP servers, automation tools, or integrations with any Google Workspace API, or troubleshooting OAuth errors, rate limit 429 errors, scope issues, or API-specific gotchas.
user-invocable: true
---

# Google Workspace APIs

**Status**: Production Ready
**Last Updated**: 2026-01-09
**Dependencies**: Cloudflare Workers (recommended), Google Cloud Project
**Skill Version**: 1.0.0

---

## Quick Reference

| API | Common Use Cases | Reference |
|-----|------------------|-----------|
| Gmail | Email automation, inbox management | [gmail-api.md](references/gmail-api.md) |
| Calendar | Event management, scheduling | [calendar-api.md](references/calendar-api.md) |
| Drive | File storage, sharing | [drive-api.md](references/drive-api.md) |
| Sheets | Spreadsheet data, reporting | [sheets-api.md](references/sheets-api.md) |
| Docs | Document generation | [docs-api.md](references/docs-api.md) |
| Chat | Bots, webhooks, spaces | [chat-api.md](references/chat-api.md) |
| Meet | Video conferencing | [meet-api.md](references/meet-api.md) |
| Forms | Form responses, creation | [forms-api.md](references/forms-api.md) |
| Tasks | Task management | [tasks-api.md](references/tasks-api.md) |
| Admin SDK | User/group management | [admin-sdk.md](references/admin-sdk.md) |
| People | Contacts management | [people-api.md](references/people-api.md) |

---

## Shared Authentication Patterns

All Google Workspace APIs use the same authentication mechanisms. Choose based on your use case.

### Option 1: OAuth 2.0 (User Context)

Best for: Acting on behalf of a user, accessing user-specific data.

```typescript
// Authorization URL
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth')
authUrl.searchParams.set('client_id', env.GOOGLE_CLIENT_ID)
authUrl.searchParams.set('redirect_uri', `${env.BASE_URL}/callback`)
authUrl.searchParams.set('response_type', 'code')
authUrl.searchParams.set('scope', SCOPES.join(' '))
authUrl.searchParams.set('access_type', 'offline')  // For refresh tokens
authUrl.searchParams.set('prompt', 'consent')       // Force consent for refresh token

// Token exchange
async function exchangeCode(code: string): Promise<TokenResponse> {
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      code,
      client_id: env.GOOGLE_CLIENT_ID,
      client_secret: env.GOOGLE_CLIENT_SECRET,
      redirect_uri: `${env.BASE_URL}/callback`,
      grant_type: 'authorization_code',
    }),
  })
  return response.json()
}

// Refresh token
async function refreshToken(refresh_token: string): Promise<TokenResponse> {
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      refresh_token,
      client_id: env.GOOGLE_CLIENT_ID,
      client_secret: env.GOOGLE_CLIENT_SECRET,
      grant_type: 'refresh_token',
    }),
  })
  return response.json()
}
```

**Critical:**
- Always request `access_type=offline` for refresh tokens
- Use `prompt=consent` to ensure refresh token is returned
- Store refresh tokens securely (Cloudflare KV or D1)
- Access tokens expire in ~1 hour

### Option 2: Service Account (Server-to-Server)

Best for: Backend automation, no user interaction, domain-wide delegation.

```typescript
import { SignJWT } from 'jose'

async function getServiceAccountToken(
  serviceAccount: ServiceAccountKey,
  scopes: string[]
): Promise<string> {
  const now = Math.floor(Date.now() / 1000)

  // Create JWT
  const jwt = await new SignJWT({
    iss: serviceAccount.client_email,
    scope: scopes.join(' '),
    aud: 'https://oauth2.googleapis.com/token',
    iat: now,
    exp: now + 3600,
  })
    .setProtectedHeader({ alg: 'RS256', typ: 'JWT' })
    .sign(await importPKCS8(serviceAccount.private_key, 'RS256'))

  // Exchange JWT for access token
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      assertion: jwt,
    }),
  })

  const data = await response.json()
  return data.access_token
}
```

**Domain-Wide Delegation** (impersonate users):
```typescript
const jwt = await new SignJWT({
  iss: serviceAccount.client_email,
  sub: '[email protected]',  // User to impersonate
  scope: scopes.join(' '),
  aud: 'https://oauth2.googleapis.com/token',
  iat: now,
  exp: now + 3600,
})
```

**Setup Required:**
1. Create service account in Google Cloud Console
2. Download JSON key file
3. Enable domain-wide delegation in Admin Console (if impersonating)
4. Store key as Cloudflare secret (JSON stringified)

---

## Common Rate Limits

All Google Workspace APIs enforce quotas. These are approximate - check each API's specific limits.

### Per-User Limits (OAuth)

| API | Reads | Writes | Notes |
|-----|-------|--------|-------|
| Gmail | 250/user/sec | 250/user/sec | Aggregate across all methods |
| Calendar | 500/user/100sec | 500/user/100sec | Per calendar |
| Drive | 1000/user/100sec | 1000/user/100sec | |
| Sheets | 100/user/100sec | 100/user/100sec | Lower than others |

### Per-Project Limits

| API | Daily Quota | Per-Minute | Notes |
|-----|-------------|------------|-------|
| Gmail | 1B units | Varies | Unit-based (send = 100 units) |
| Calendar | 1M queries | 500/sec | |
| Drive | 1B queries | 1000/sec | |
| Sheets | Unlimited | 500/user/100sec | |

### Handling Rate Limits

```typescript
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 5
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error: any) {
      const status = error.status || error.code

      if (status === 429 || status === 503) {
        // Rate limited or service unavailable
        const retryAfter = error.headers?.get('Retry-After') || Math.pow(2, i)
        await new Promise(r => setTimeout(r, retryAfter * 1000))
        continue
      }

      if (status === 403 && error.message?.includes('rateLimitExceeded')) {
        // Quota exceeded - exponential backoff
        await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000))
        continue
      }

      throw error
    }
  }
  throw new Error('Max retries exceeded')
}
```

---

## Batch Requests

Most Google APIs support batching multiple requests into one HTTP call.

```typescript
async function batchRequest(
  accessToken: string,
  requests: BatchRequestItem[]
): Promise<BatchResponse[]> {
  const boundary = 'batch_boundary'

  let body = ''
  requests.forEach((req, i) => {
    body += `--${boundary}\r\n`
    body += 'Content-Type: application/http\r\n'
    body += `Content-ID: <item${i}>\r\n\r\n`
    body += `${req.method} ${req.path} HTTP/1.1\r\n`
    body += 'Content-Type: application/json\r\n\r\n'
    if (req.body) body += JSON.stringify(req.body)
    body += '\r\n'
  })
  body += `--${boundary}--`

  const response = await fetch('https://www.googleapis.com/batch/v1', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': `multipart/mixed; boundary=${boundary}`,
    },
    body,
  })

  // Parse multipart response...
  return parseBatchResponse(await response.text())
}
```

**Limits:**
- Max 100 requests per batch (most APIs)
- Max 1000 requests per batch (some APIs like Drive)
- Each request in batch counts toward quota

---

## Cloudflare Workers Configuration

```jsonc
// wrangler.jsonc
{
  "name": "google-workspace-mcp",
  "main": "src/index.ts",
  "compatibility_date": "2026-01-03",
  "compatibility_flags": ["nodejs_compat"],

  // Store OAuth tokens
  "kv_namespaces": [
    { "binding": "TOKENS", "id": "xxx" }
  ],

  // Or use D1 for structured storage
  "d1_databases": [
    { "binding": "DB", "database_name": "workspace-mcp", "database_id": "xxx" }
  ]
}
```

**Secrets to set:**
```bash
echo "your-client-id" | npx wrangler secret put GOOGLE_CLIENT_ID
echo "your-client-secret" | npx wrangler secret put GOOGLE_CLIENT_SECRET
# For service accounts:
cat service-account.json | npx wrangler secret put GOOGLE_SERVICE_ACCOUNT
```

---

## Common Errors

### Error: "invalid_grant" on Token Refresh
**Cause**: Refresh token revoked or expired (6 months of inactivity)
**Fix**: Re-authenticate user, request new refresh token

### Error: "access_denied" on OAuth
**Cause**: App not verified, or user not in test users list
**Fix**: Add user to OAuth consent screen test users, or complete app verification

### Error: "insufficientPermissions" (403)
**Cause**: Missing required scope
**Fix**: Check scopes in authorization URL, re-authenticate with correct scopes

### Error: "rateLimitExceeded" (403)
**Cause**: Quota exceeded
**Fix**: Implement exponential backoff, reduce request frequency, request quota increase

### Error: "notFound" (404) on Known Resource
**Cause**: Using wrong API version, or resource in trash
**Fix**: Check API version in URL, check trash for deleted items

---

## API-Specific Guides

Detailed patterns for each API are in the `references/` directory. Load these when working with specific APIs.

### Gmail API
See [references/gmail-api.md](references/gmail-api.md)
- Message CRUD, labels, threads
- MIME handling, attachments
- Push notifications (Pub/Sub)

### Calendar API
See [references/calendar-api.md](references/calendar-api.md)
- Events CRUD, recurring events
- Free/busy queries
- Calendar sharing

### Drive API
See [references/drive-api.md](references/drive-api.md)
- File upload/download
- Permissions, sharing
- Search queries

### Sheets API
See [references/sheets-api.md](references/sheets-api.md)
- Reading/writing cells
- A1 notation, ranges
- Batch updates

### Chat API
See [references/chat-api.md](references/chat-api.md)
- Bots, webhooks
- Cards v2, interactive forms
- Spaces, members, reactions

*(Additional API references added as MCP servers are built)*

---

## Package Versions (Verified 2026-01-09)

```json
{
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20260109.0",
    "wrangler": "^4.58.0",
    "jose": "^6.1.3"
  }
}
```

---

## Official Documentation

- **Google Workspace APIs**: https://developers.google.com/workspace
- **OAuth 2.0**: https://developers.google.com/identity/protocols/oauth2
- **Service Accounts**: https://cloud.google.com/iam/docs/service-accounts
- **API Explorer**: https://developers.google.com/apis-explorer
- **Quotas Dashboard**: https://console.cloud.google.com/iam-admin/quotas

---

## Skill Roadmap

APIs documented as MCP servers are built:

- [ ] Gmail API
- [ ] Calendar API
- [ ] Drive API
- [ ] Sheets API
- [ ] Docs API
- [x] Chat API (migrated from google-chat-api skill)
- [ ] Meet API
- [ ] Forms API
- [ ] Tasks API
- [ ] Admin SDK
- [ ] People API

Overview

This skill provides practical patterns and utilities for building integrations with Google Workspace APIs (Gmail, Calendar, Drive, Sheets, Docs, Chat, Meet, Forms, Tasks, Admin SDK). It includes recommended authentication flows (OAuth2 and service accounts), rate-limit handling, batch request helpers, and Cloudflare Workers deployment guidance. Use it to accelerate reliable server-to-server and user-context automations in TypeScript.

How this skill works

The skill documents shared authentication strategies: OAuth 2.0 for user-scoped access and JWT-based service account flows for server-to-server or domain-wide delegation. It supplies code patterns for token exchange, refresh handling, creating signed JWTs, batching multiple API calls, and robust retry/backoff logic for 429/503 and quota errors. Deployment notes cover storing secrets and tokens in Cloudflare Workers KV or D1 and configuring wrangler for Workers compatibility.

When to use it

  • Building MCP servers that interact with Gmail, Calendar, Drive, Sheets, or other Workspace APIs
  • Automating backend workflows with service accounts or domain-wide delegation
  • Implementing user-facing features that require OAuth consent and refresh tokens
  • Handling 429/503 rate-limit and quota errors with exponential backoff and retries
  • Batching requests to reduce HTTP overhead and stay within per-request quotas
  • Deploying Workspace integrations on Cloudflare Workers or similar edge environments

Best practices

  • Always request access_type=offline and prompt=consent to obtain refresh tokens for long-lived user access
  • Store refresh tokens and service account keys securely (KV, D1, or encrypted secrets) and rotate them periodically
  • Implement exponential backoff and honor Retry-After headers for 429/503 and rateLimitExceeded errors
  • Use service accounts with domain-wide delegation when automating across users; impersonate via the sub claim
  • Batch when appropriate but keep per-batch limits in mind (commonly 100 requests) and count batches toward quota
  • Validate scopes at auth time and re-authenticate users when scope changes are required

Example use cases

  • Send transactional emails, label management, and inbox automation using the Gmail API with OAuth2
  • Create and update calendar events and query free/busy for scheduling assistants
  • Upload, search, and share files on Drive from a backend service account
  • Read and write Sheets for reporting pipelines and batch updates to large ranges
  • Build Chat bots and interactive cards deployed as Cloudflare Workers for team workflows
  • Use service accounts to provision users and groups via Admin SDK in automated provisioning systems

FAQ

Which auth flow should I pick, OAuth2 or service account?

Use OAuth2 when acting on behalf of an individual user and service accounts for backend automation or domain-wide tasks. For impersonation combine service accounts with the sub claim.

How do I handle persistent refresh token failures like invalid_grant?

invalid_grant typically means the refresh token was revoked or expired from inactivity. Prompt the user to re-authenticate and ensure access_type=offline and prompt=consent during authorization.