google-workspace
Google Workspace
Comprehensive AI agent skill for all Google Workspace document operations — Docs, Sheets, Slides, Drive, Gmail, Calendar, Chat, Forms, Admin SDK, and Apps Script — via official REST APIs.
When to use this skill
- Creating or editing Google Docs, Sheets, Slides
- Uploading, downloading, organizing Google Drive files and folders
- Sending/reading Gmail, managing labels and drafts
- Creating calendar events, inviting attendees, checking availability
- Posting Google Chat messages, managing spaces
- Building and reading Google Forms/surveys
- Provisioning/managing Google Workspace users (Admin SDK)
- Running automated workflows via Apps Script
Quick Setup
Step 1: Enable APIs in Google Cloud Console
# Install gcloud CLI (if not available)
brew install --cask google-cloud-sdk # macOS
# Or: curl https://sdk.cloud.google.com | bash
# Enable all Workspace APIs
gcloud services enable docs.googleapis.com \
sheets.googleapis.com slides.googleapis.com \
drive.googleapis.com gmail.googleapis.com \
calendar-json.googleapis.com chat.googleapis.com \
forms.googleapis.com admin.googleapis.com \
script.googleapis.com
Step 2: Install Python client library
pip install --upgrade \
google-api-python-client \
google-auth-httplib2 \
google-auth-oauthlib
Step 3: Authenticate
# OAuth2 — interactive user auth (for accessing user's own data)
bash scripts/auth-setup.sh --oauth2 credentials.json
# Service Account — server-to-server (for automation/backend)
bash scripts/auth-setup.sh --service-account service-account-key.json
API Reference by Product
Google Docs
Endpoint: https://docs.googleapis.com/v1
Scope: https://www.googleapis.com/auth/documents
from googleapiclient.discovery import build
docs = build('docs', 'v1', credentials=creds)
# Create document
doc = docs.documents().create(body={'title': 'My Document'}).execute()
doc_id = doc['documentId']
# Read document
doc = docs.documents().get(documentId=doc_id).execute()
content = doc.get('body', {}).get('content', [])
# Edit: replace all text matching a pattern
requests = [{
'replaceAllText': {
'containsText': {'text': '{{name}}', 'matchCase': False},
'replaceText': 'Alice'
}
}]
docs.documents().batchUpdate(documentId=doc_id, body={'requests': requests}).execute()
# Insert text at position
requests = [{'insertText': {'location': {'index': 1}, 'text': 'Hello!\n'}}]
docs.documents().batchUpdate(documentId=doc_id, body={'requests': requests}).execute()
Key batchUpdate operations: insertText, deleteContentRange, replaceAllText, updateTextStyle, updateParagraphStyle, insertTable, insertInlineImage, createHeader, createFooter, createNamedRange
Google Sheets
Endpoint: https://sheets.googleapis.com/v4
Scope: https://www.googleapis.com/auth/spreadsheets
sheets = build('sheets', 'v4', credentials=creds)
ss = sheets.spreadsheets()
# Create spreadsheet
spreadsheet = ss.create(body={
'properties': {'title': 'My Sheet'},
'sheets': [{'properties': {'title': 'Data'}}]
}).execute()
sheet_id = spreadsheet['spreadsheetId']
# Write data
ss.values().update(
spreadsheetId=sheet_id,
range='Sheet1!A1',
valueInputOption='USER_ENTERED',
body={'values': [['Name', 'Score'], ['Alice', 95], ['Bob', 87]]}
).execute()
# Read data
result = ss.values().get(spreadsheetId=sheet_id, range='Sheet1!A:B').execute()
rows = result.get('values', [])
# Append rows
ss.values().append(
spreadsheetId=sheet_id,
range='Sheet1!A1',
valueInputOption='USER_ENTERED',
body={'values': [['Charlie', 91]]}
).execute()
# Batch update (format: freeze row 1, bold header)
ss.batchUpdate(spreadsheetId=sheet_id, body={'requests': [
{'updateSheetProperties': {
'properties': {'sheetId': 0, 'gridProperties': {'frozenRowCount': 1}},
'fields': 'gridProperties.frozenRowCount'
}},
{'repeatCell': {
'range': {'sheetId': 0, 'startRowIndex': 0, 'endRowIndex': 1},
'cell': {'userEnteredFormat': {'textFormat': {'bold': True}}},
'fields': 'userEnteredFormat.textFormat.bold'
}}
]}).execute()
Google Slides
Endpoint: https://slides.googleapis.com/v1
Scope: https://www.googleapis.com/auth/presentations
slides = build('slides', 'v1', credentials=creds)
# Create presentation
presentation = slides.presentations().create(
body={'title': 'My Presentation'}
).execute()
pres_id = presentation['presentationId']
# Read presentation
pres = slides.presentations().get(presentationId=pres_id).execute()
slide_ids = [s['objectId'] for s in pres.get('slides', [])]
# Add a new slide
slides.presentations().batchUpdate(presentationId=pres_id, body={'requests': [
{'createSlide': {
'insertionIndex': 1,
'slideLayoutReference': {'predefinedLayout': 'TITLE_AND_BODY'}
}}
]}).execute()
# Replace placeholder text
slides.presentations().batchUpdate(presentationId=pres_id, body={'requests': [
{'replaceAllText': {
'containsText': {'text': '{{title}}', 'matchCase': False},
'replaceText': 'Q1 Report'
}}
]}).execute()
# Get slide thumbnail
page_id = slide_ids[0]
thumb = slides.presentations().pages().getThumbnail(
presentationId=pres_id,
pageObjectId=page_id,
thumbnailProperties_thumbnailSize='LARGE'
).execute()
image_url = thumb['contentUrl']
Google Drive
Endpoint: https://www.googleapis.com/drive/v3
Scope: https://www.googleapis.com/auth/drive
drive = build('drive', 'v3', credentials=creds)
# Create folder
folder = drive.files().create(body={
'name': 'My Folder',
'mimeType': 'application/vnd.google-apps.folder'
}).execute()
folder_id = folder['id']
# Upload file
from googleapiclient.http import MediaFileUpload
media = MediaFileUpload('report.pdf', mimetype='application/pdf')
file = drive.files().create(
body={'name': 'report.pdf', 'parents': [folder_id]},
media_body=media,
fields='id'
).execute()
# Search files
results = drive.files().list(
q="name contains 'report' and mimeType='application/pdf'",
fields='files(id, name, modifiedTime)'
).execute()
# Share file
drive.permissions().create(
fileId=file['id'],
body={'type': 'user', 'role': 'reader', 'emailAddress': 'alice@example.com'},
sendNotificationEmail=True
).execute()
# Export Google Doc to PDF
import io
from googleapiclient.http import MediaIoBaseDownload
request = drive.files().export_media(fileId=doc_id, mimeType='application/pdf')
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
_, done = downloader.next_chunk()
with open('document.pdf', 'wb') as f:
f.write(fh.getvalue())
# Move file
drive.files().update(
fileId=file['id'],
addParents=folder_id,
removeParents='root',
fields='id, parents'
).execute()
# Copy file (e.g., from template)
copy = drive.files().copy(
fileId='TEMPLATE_FILE_ID',
body={'name': 'New Document from Template', 'parents': [folder_id]}
).execute()
Gmail
Endpoint: https://gmail.googleapis.com/gmail/v1
Scope: https://www.googleapis.com/auth/gmail.modify
import base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
gmail = build('gmail', 'v1', credentials=creds)
# Send email
def send_email(to, subject, body):
msg = MIMEText(body)
msg['to'] = to
msg['subject'] = subject
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
gmail.users().messages().send(userId='me', body={'raw': raw}).execute()
send_email('alice@example.com', 'Hello', 'This is the body.')
# Send with attachment
msg = MIMEMultipart()
msg['to'] = 'alice@example.com'
msg['subject'] = 'Report'
msg.attach(MIMEText('Please find the report attached.'))
with open('report.pdf', 'rb') as f:
from email.mime.application import MIMEApplication
part = MIMEApplication(f.read(), Name='report.pdf')
part['Content-Disposition'] = 'attachment; filename="report.pdf"'
msg.attach(part)
raw = base64.urlsafe_b64encode(msg.as_bytes()).decode()
gmail.users().messages().send(userId='me', body={'raw': raw}).execute()
# Search emails
results = gmail.users().messages().list(
userId='me', q='from:boss@company.com subject:urgent is:unread'
).execute()
# Read email
msg_id = results['messages'][0]['id']
msg = gmail.users().messages().get(userId='me', id=msg_id, format='full').execute()
subject = next(h['value'] for h in msg['payload']['headers'] if h['name'] == 'Subject')
# Create label and apply
label = gmail.users().labels().create(
userId='me', body={'name': 'AI-Processed'}
).execute()
gmail.users().messages().modify(
userId='me', id=msg_id,
body={'addLabelIds': [label['id']], 'removeLabelIds': ['UNREAD']}
).execute()
# Create draft
raw_draft = base64.urlsafe_b64encode(MIMEText('Draft body').as_bytes()).decode()
gmail.users().drafts().create(
userId='me', body={'message': {'raw': raw_draft}}
).execute()
# Set vacation responder
gmail.users().settings().updateVacation(
userId='me',
body={
'enableAutoReply': True,
'responseSubject': 'Out of Office',
'responseBodyPlainText': 'I am OOO until Monday.',
'startTime': '1704067200000', # Unix ms
'endTime': '1704326400000'
}
).execute()
Google Calendar
Endpoint: https://www.googleapis.com/calendar/v3
Scope: https://www.googleapis.com/auth/calendar
from datetime import datetime, timedelta
import pytz
calendar = build('calendar', 'v3', credentials=creds)
# Create event
event = calendar.events().insert(
calendarId='primary',
body={
'summary': 'Team Standup',
'description': 'Daily sync',
'start': {'dateTime': '2026-03-15T09:00:00+09:00', 'timeZone': 'Asia/Seoul'},
'end': {'dateTime': '2026-03-15T09:30:00+09:00', 'timeZone': 'Asia/Seoul'},
'attendees': [
{'email': 'alice@example.com'},
{'email': 'bob@example.com'},
],
'conferenceData': {
'createRequest': {'requestId': 'meeting-001', 'conferenceSolutionKey': {'type': 'hangoutsMeet'}}
},
'recurrence': ['RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR']
},
conferenceDataVersion=1
).execute()
meet_link = event.get('hangoutLink')
# List today's events
now = datetime.utcnow().isoformat() + 'Z'
end_of_day = (datetime.utcnow() + timedelta(hours=24)).isoformat() + 'Z'
events_result = calendar.events().list(
calendarId='primary',
timeMin=now, timeMax=end_of_day,
singleEvents=True, orderBy='startTime'
).execute()
events = events_result.get('items', [])
# Check free/busy
body = {
'timeMin': now,
'timeMax': end_of_day,
'items': [{'id': 'alice@example.com'}, {'id': 'bob@example.com'}]
}
freebusy = calendar.freebusy().query(body=body).execute()
# Block time / set OOO
calendar.events().insert(
calendarId='primary',
body={
'summary': 'Out of Office',
'eventType': 'outOfOffice',
'start': {'date': '2026-03-20'},
'end': {'date': '2026-03-22'}
}
).execute()
# Share calendar
calendar.acl().insert(
calendarId='primary',
body={'role': 'reader', 'scope': {'type': 'user', 'value': 'alice@example.com'}}
).execute()
Google Chat
Endpoint: https://chat.googleapis.com/v1
Scope: https://www.googleapis.com/auth/chat.messages
chat = build('chat', 'v1', credentials=creds)
# Send message to a space
space_name = 'spaces/SPACE_ID' # From Chat URL
chat.spaces().messages().create(
parent=space_name,
body={
'text': 'Hello from AI agent! 🤖',
'cards_v2': [{
'cardId': 'card1',
'card': {
'header': {'title': 'Update', 'subtitle': 'Automated report'},
'sections': [{'widgets': [{'textParagraph': {'text': 'Task completed.'}}]}]
}
}]
}
).execute()
# Create a new space
space = chat.spaces().create(
body={
'spaceType': 'SPACE',
'displayName': 'Project Alpha'
}
).execute()
# List spaces
spaces = chat.spaces().list().execute()
# Add member to space
chat.spaces().members().create(
parent=space['name'],
body={'member': {'name': 'users/alice@example.com', 'type': 'HUMAN'}}
).execute()
# Find or create direct message
dm = chat.spaces().findDirectMessage(name='users/alice@example.com').execute()
Google Forms
Endpoint: https://forms.googleapis.com/v1
Scope: https://www.googleapis.com/auth/forms.body
forms = build('forms', 'v1', credentials=creds)
# Create form
form = forms.forms().create(body={
'info': {'title': 'Customer Feedback Survey', 'documentTitle': 'Customer Feedback'}
}).execute()
form_id = form['formId']
# Add questions
forms.forms().batchUpdate(formId=form_id, body={'requests': [
{
'createItem': {
'item': {
'title': 'How satisfied are you?',
'questionItem': {
'question': {
'required': True,
'scaleQuestion': {
'low': 1, 'high': 5,
'lowLabel': 'Not satisfied', 'highLabel': 'Very satisfied'
}
}
}
},
'location': {'index': 0}
}
},
{
'createItem': {
'item': {
'title': 'Any comments?',
'questionItem': {
'question': {
'required': False,
'textQuestion': {'paragraph': True}
}
}
},
'location': {'index': 1}
}
}
]}).execute()
# Get form responses
responses = forms.forms().responses().list(formId=form_id).execute()
for r in responses.get('responses', []):
for qid, ans in r.get('answers', {}).items():
print(qid, ans.get('textAnswers', {}).get('answers', []))
Admin SDK — Directory API
Endpoint: https://admin.googleapis.com
Scope: https://www.googleapis.com/auth/admin.directory.user
Requires: Service account with domain-wide delegation
from google.oauth2 import service_account
SA_FILE = 'service-account.json'
SCOPES = ['https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group']
creds = service_account.Credentials.from_service_account_file(
SA_FILE, scopes=SCOPES
).with_subject('admin@yourdomain.com')
admin = build('admin', 'directory_v1', credentials=creds)
# Create user
admin.users().insert(body={
'primaryEmail': 'newuser@yourdomain.com',
'name': {'givenName': 'New', 'familyName': 'User'},
'password': 'TemporaryPassword123!',
'changePasswordAtNextLogin': True
}).execute()
# List users
users_result = admin.users().list(domain='yourdomain.com', maxResults=100).execute()
for user in users_result.get('users', []):
print(user['primaryEmail'], user.get('suspended', False))
# Suspend user
admin.users().update(
userKey='user@yourdomain.com',
body={'suspended': True}
).execute()
# Add user to group
admin.members().insert(
groupKey='team@yourdomain.com',
body={'email': 'user@yourdomain.com', 'role': 'MEMBER'}
).execute()
# List groups
groups = admin.groups().list(domain='yourdomain.com').execute()
Apps Script API
Endpoint: https://script.googleapis.com/v1
Scope: https://www.googleapis.com/auth/script.projects
script = build('script', 'v1', credentials=creds)
# Run a deployed function
response = script.scripts().run(
scriptId='DEPLOYED_SCRIPT_ID',
body={
'function': 'myFunction',
'parameters': ['arg1', 42]
}
).execute()
result = response.get('response', {}).get('result')
Common Automation Patterns
Pattern 1: Create Document from Template
def create_doc_from_template(drive, docs, template_id, replacements, dest_folder_id=None):
"""Clone a template Google Doc and fill in placeholders."""
body = {'name': replacements.get('{{title}}', 'New Document')}
if dest_folder_id:
body['parents'] = [dest_folder_id]
copy = drive.files().copy(fileId=template_id, body=body).execute()
new_id = copy['id']
requests = [
{'replaceAllText': {'containsText': {'text': k, 'matchCase': False}, 'replaceText': v}}
for k, v in replacements.items()
]
if requests:
docs.documents().batchUpdate(documentId=new_id, body={'requests': requests}).execute()
return new_id
Pattern 2: Bulk Append to Spreadsheet
def bulk_append_rows(sheets, spreadsheet_id, sheet_name, rows):
"""Append multiple rows to a sheet in one API call."""
sheets.spreadsheets().values().append(
spreadsheetId=spreadsheet_id,
range=f'{sheet_name}!A1',
valueInputOption='USER_ENTERED',
insertDataOption='INSERT_ROWS',
body={'values': rows}
).execute()
Pattern 3: Create Meeting Notes Document
def create_meeting_notes(calendar, drive, docs, event_id):
"""Create a Google Doc for meeting notes and share with attendees."""
event = calendar.events().get(calendarId='primary', eventId=event_id).execute()
attendees = [a['email'] for a in event.get('attendees', [])]
title = f"Meeting Notes: {event['summary']} — {event['start'].get('dateTime', event['start'].get('date'))}"
doc = docs.documents().create(body={'title': title}).execute()
doc_id = doc['documentId']
for email in attendees:
drive.permissions().create(
fileId=doc_id,
body={'type': 'user', 'role': 'writer', 'emailAddress': email},
sendNotificationEmail=True
).execute()
return doc_id
Pattern 4: Form Response to Sheet
def sync_form_to_sheet(forms, sheets, form_id, spreadsheet_id):
"""Sync all form responses to a Google Sheet."""
responses = forms.forms().responses().list(formId=form_id).execute()
form_data = forms.forms().get(formId=form_id).execute()
questions = {
item['itemId']: item.get('title', '')
for item in form_data.get('items', [])
if 'questionItem' in item
}
headers = ['Timestamp'] + list(questions.values())
rows = [headers]
for resp in responses.get('responses', []):
row = [resp.get('createTime', '')]
for qid in questions:
ans = resp.get('answers', {}).get(qid, {})
text_ans = ans.get('textAnswers', {}).get('answers', [{}])
row.append(text_ans[0].get('value', '') if text_ans else '')
rows.append(row)
sheets.spreadsheets().values().update(
spreadsheetId=spreadsheet_id,
range='Sheet1!A1',
valueInputOption='USER_ENTERED',
body={'values': rows}
).execute()
Rate Limits & Best Practices
| API | Quota | Retry Strategy |
|---|---|---|
| Docs API | 300 req/min/user | Exponential backoff on 429 |
| Sheets API | 300 req/min | Batch operations reduce quota usage |
| Drive API | 1,000 req/100 sec | Use fields param to reduce payload |
| Gmail API | 250 quota units/user/sec | batchModify for bulk operations |
| Calendar API | 1,000,000 req/day | Use timeMin/timeMax to limit list results |
| Admin SDK | 10 user creates/domain/sec | Add time.sleep(0.15) between creates |
import time
from googleapiclient.errors import HttpError
def api_call_with_retry(func, *args, max_retries=5, **kwargs):
"""Wrapper that retries on 429/503 with exponential backoff."""
for attempt in range(max_retries):
try:
return func(*args, **kwargs).execute()
except HttpError as e:
if e.resp.status in (429, 503) and attempt < max_retries - 1:
wait = (2 ** attempt) + 0.1
print(f"Rate limit hit, waiting {wait:.1f}s...")
time.sleep(wait)
else:
raise
Scopes Reference
| Product | Read Scope | Write Scope |
|---|---|---|
| Docs | auth/documents.readonly |
auth/documents |
| Sheets | auth/spreadsheets.readonly |
auth/spreadsheets |
| Slides | auth/presentations.readonly |
auth/presentations |
| Drive | auth/drive.readonly |
auth/drive |
| Gmail | auth/gmail.readonly |
auth/gmail.modify |
| Calendar | auth/calendar.readonly |
auth/calendar |
| Chat | auth/chat.messages.readonly |
auth/chat.messages |
| Forms | auth/forms.body.readonly |
auth/forms.body |
| Admin SDK | auth/admin.directory.user.readonly |
auth/admin.directory.user |
References
- Google Workspace Developer Hub
- Google Docs API Reference
- Google Sheets API Reference
- Google Slides API Reference
- Google Drive API v3 Reference
- Gmail API Reference
- Google Calendar API Reference
- Google Chat API Reference
- Google Forms API Reference
- Admin SDK Directory API Reference
- Apps Script REST API
- Auth Overview & Credentials Setup
- Enable APIs Guide