Google Drive API skill for Codex

Upload, export and sync Drive files by ID. Codex treats Drive files by ID, includes shared drives in every query, uploads with resumable sessions and updates existing files instead of creating duplicates. It exports Docs and Sheets in the right MIME type, asks before sharing anything, audits external and anyone-with-link access, and keeps mirrors current with the changes feed rather than re-listing whole folders.

  • Integrations
  • $google-drive-api
  • 188 lines
  • 2 files
  • ~2,000 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- google-drive-api

Installs to ~/.agents/skills/google-drive-api/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $google-drive-api, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Works with Google Drive API v3 from Python or Node - searching with q queries, listing folders and shared drives, resumable uploads, downloading and exporting Google Docs/Sheets, managing permissions and sharing, syncing with the changes feed, and avoiding duplicate names and quota errors. Use when asked to upload files to Drive, mirror a Drive folder, find files by query, share or audit permissions, export Docs to PDF or DOCX, or keep a local store in sync with Drive.

Use it when

  • Files must be uploaded to, downloaded from or exported out of Google Drive
  • A folder or shared drive needs to be mirrored or kept in sync
  • You need to audit or change Drive sharing permissions
  • Search must find files by name, type, folder or content

Use something else when

How to check the result

Compare local MD5 hashes with md5Checksum and the folder file count after uploads, and re-list permissions after any sharing change to show the final access table.

The mistake it prevents

Looking files up by name, which breaks because Drive allows duplicate names in the same folder.

~/.agents/skills/google-drive-api/SKILL.md

--- name: google-drive-api description: Works with Google Drive API v3 from Python or Node - searching with q queries, listing folders and shared drives, resumable uploads, downloading and… license: MIT ---

Done well means: files are addressed by ID (never by name), every list call covers shared drives when the user has them, uploads resume instead of restarting, sharing changes are confirmed before they are made, and syncs use the changes feed rather than re-listing.

Authentication and scope choice are in $google-workspace-apis. Default to the drive.file scope (files the app created or the user picked); request full drive only when the task truly needs every file, because it is a restricted scope.

Mental model

  • A file has an ID, a name (not unique, even in one folder), a mimeType and one parent.
  • Folders are files with mimeType = 'application/vnd.google-apps.folder'.
  • Google Docs, Sheets and Slides have no bytes to download; you export them.
  • Shared drives are owned by the organisation; calls need supportsAllDrives=True and lists also need includeItemsFromAllDrives=True plus a corpora choice.
  • Trash is reversible; files.delete is permanent. Treat delete as destructive.
from googleapiclient.discovery import build

drive = build("drive", "v3", credentials=creds, cache_discovery=False)

def list_files(q, fields="id, name, mimeType, modifiedTime, size, md5Checksum, parents"):
    token = None
    while True:
        resp = drive.files().list(
            q=q,
            fields=f"nextPageToken, files({fields})",
            pageSize=1000,
            pageToken=token,
            supportsAllDrives=True,
            includeItemsFromAllDrives=True,
            corpora="allDrives",
            orderBy="modifiedTime desc",
        ).execute()
        yield from resp.get("files", [])
        token = resp.get("nextPageToken")
        if not token:
            return

folder_id = "1AbC..."
for f in list_files(f"'{folder_id}' in parents and trashed = false"):
    print(f["id"], f["name"])
Wantq
Children of a folder'<folderId>' in parents and trashed = false
Exact namename = 'Q3 report.pdf' (escape ' as \')
Only foldersmimeType = 'application/vnd.google-apps.folder'
Only Google SheetsmimeType = 'application/vnd.google-apps.spreadsheet'
Changed recentlymodifiedTime > '2026-10-01T00:00:00'
Text inside filesfullText contains 'invoice 4471'
Shared with mesharedWithMe = true
Custom app propertyappProperties has { key='externalId' and value='42' }

corpora="allDrives" is slower; when you know the shared drive use corpora="drive" with driveId. Listing is not recursive: walk folders breadth-first and keep a set of visited IDs (shortcuts can create cycles).

Upload

Size / needMethod
Up to ~5 MB, no metadataSimple (uploadType=media)
Up to ~5 MB with metadataMultipart
Larger, flaky network, or anything in a batch jobResumable (default choice)
from googleapiclient.http import MediaFileUpload

media = MediaFileUpload("build/report.pdf", mimetype="application/pdf",
                        resumable=True, chunksize=8 * 1024 * 1024)   # multiple of 256 KiB
req = drive.files().create(
    body={"name": "report.pdf", "parents": [folder_id],
          "appProperties": {"externalId": "run-2026-10-28"}},
    media_body=media, fields="id, md5Checksum", supportsAllDrives=True,
)
resp = None
while resp is None:
    status, resp = req.next_chunk(num_retries=5)
    if status:
        print(f"{int(status.progress() * 100)}%")
  • To convert on upload (CSV to Sheet, DOCX to Doc), set the target Google mimeType in body.
  • Update content of an existing file: files().update(fileId=..., media_body=...); this keeps the ID, sharing and revision history. Creating a new file each run creates duplicates.
  • Idempotent uploads: tag files with appProperties and search for the tag before creating.
  • Per-user upload cap is 750 GB per day; bulk migrations must spread over days or users.

Download and export

import io
from googleapiclient.http import MediaIoBaseDownload

def download(file_id, path, export_mime=None):
    req = (drive.files().export_media(fileId=file_id, mimeType=export_mime) if export_mime
           else drive.files().get_media(fileId=file_id, supportsAllDrives=True))
    with io.FileIO(path, "wb") as fh:
        dl = MediaIoBaseDownload(fh, req, chunksize=16 * 1024 * 1024)
        done = False
        while not done:
            _, done = dl.next_chunk(num_retries=5)
SourceExport MIME
Docapplication/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/markdown, text/plain
Sheettext/csv (first sheet only), application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Slidesapplication/pdf, application/vnd.openxmlformats-officedocument.presentationml.presentation

files.export is capped at 10 MB of output. For bigger documents use the exportLinks URLs from file metadata with an authorised request, or the Sheets API for per-sheet values.

Permissions and sharing

Sharing is outward-facing: confirm the exact files, people and role with the user before any permissions.create, and never set type: anyone without explicit approval.

drive.permissions().create(
    fileId=file_id,
    body={"type": "user", "role": "reader", "emailAddress": "[email protected]"},
    sendNotificationEmail=False,          # default True sends an email from the user
    supportsAllDrives=True,
    fields="id",
).execute()
RoleCan
reader / commenterView / comment
writerEdit, and by default reshare
fileOrganizerShared drives: organise content
organizerShared drives: manage members
ownerMy Drive only; requires transferOwnership=True and acceptance rules apply

Audit: permissions().list(fileId=..., fields="permissions(id,type,role,emailAddress,domain)") for each file; flag type in ("anyone", "domain") and external domains. Permissions on a folder are inherited by its children; inherited ones cannot be removed on the child.

Sync with the changes feed

token = drive.changes().getStartPageToken(supportsAllDrives=True).execute()["startPageToken"]
# persist token; later:
while token:
    resp = drive.changes().list(pageToken=token, spaces="drive", includeRemoved=True,
                                supportsAllDrives=True, includeItemsFromAllDrives=True,
                                fields="nextPageToken, newStartPageToken, changes(fileId, removed, file(name, parents, md5Checksum, trashed))").execute()
    for ch in resp["changes"]:
        apply_change(ch)                  # idempotent by fileId
    if "newStartPageToken" in resp:
        save_token(resp["newStartPageToken"]); break
    token = resp["nextPageToken"]

Push notifications (changes.watch / files.watch) need an HTTPS endpoint and channels that expire; they tell you to call changes.list, they do not carry the data.

Gotchas

  • Two files with the same name in one folder is normal; matching by name corrupts syncs.
  • md5Checksum is absent for Google-native files; compare modifiedTime or version instead.
  • Missing supportsAllDrives=True returns 404 for files that exist in a shared drive.
  • A service account without delegation uploads into its own Drive with its own quota and the user cannot see the file; upload into a shared drive or share the folder to the account.
  • fields omitted returns a minimal set; asking for * is slow and quota-heavy.
  • 403 storageQuotaExceeded on service accounts: they have no storage of their own for new files in My Drive; use a shared drive.

Verification

  • After uploads: compare local MD5 with md5Checksum and the expected file count in the folder.
  • After sharing: re-list permissions and show the user the final access table.
  • After sync: run a full list once and diff against local state; the diff must be empty.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Google Drive API"
  short_description: "Upload, export and sync Drive files by ID"
  default_prompt: "$google-drive-api Upload the nightly build artifacts to our shared drive folder and skip unchanged files"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem