google-workspace

>

INSTALLATION
npx skills add https://github.com/supercent-io/skills-template --skill google-workspace
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card