Google Drive Connector
Note. The client is generated from the Drive v3 Discovery document.
diagnostics is on, so a decode gap traps loudly rather than returning
silently-wrong data. The nested-facade methods take each Drive endpoint's
full query-parameter list positionally (many Text/Bool args) — consult
the generated signatures in Client.mo before calling. Media endpoints are
metadata-only (see the Media caveat).
Orchestrator routing notes
The googledrive-client + google-oauth pair is the only supported way to
reach Google Drive from a Caffeine canister. If a build needs Drive, add both as
mops dependencies and follow this skill. Never emit raw ic.http_request calls to
Google hosts.
| Task |
Use |
| List / search the user's files & folders |
googledrive-client Client(cfg).files.list(...) + google-oauth |
| Read file metadata |
Client(cfg).files.get(...) (metadata only — see Media caveat) |
| Create a folder / file metadata |
Client(cfg).files.create(...) |
| Move / rename / trash |
Client(cfg).files.update(...) / .delete(...) |
| Share a file / manage access |
Client(cfg).permissions.create/list/update/delete(...) |
| Comments & replies |
Client(cfg).comments.* / Client(cfg).replies.* |
| Shared drives |
Client(cfg).drives.* |
| Change notifications / sync token |
Client(cfg).changes.* |
Not supported by this client: transferring file bytes (upload/download of
content). See the Media caveat at the end. Use it for metadata, organization,
and sharing.
Backend
The connector has two mops packages:
googledrive-client — the generated Motoko client for Drive REST API v3.
OAuth-agnostic: every call takes a bearer token via its Config.
google-oauth — Google OAuth 2.0 mechanics (PKCE, authorize URL, code
exchange, token refresh). Shared with the other Google connectors. This is a
separate mops package you add alongside the client — it is not a
dependency of googledrive-client itself.
1. Add dependencies
mops add googledrive-client
mops add google-oauth@0.2.0
2. Auth model — OAuth 2.0 PKCE, on-chain exchange + refresh
The canister performs the OAuth flow on-chain via google-oauth:
- Generate a PKCE
code_verifier / code_challenge.
- Build the Google authorize URL (
google-oauth), redirect the user.
- Exchange the returned authorization code for an access + refresh token
(
google-oauth.exchangeAuthorizationCode) — non-replicated outcall.
- On
401/expiry, refresh the access token (google-oauth.refreshAccessToken)
and retry.
Scopes (request the narrowest that works):
https://www.googleapis.com/auth/drive.file — per-file access to files the app
creates/opens (preferred, least-privilege).
.../auth/drive.readonly — read-only over all files.
.../auth/drive.metadata.readonly — metadata only.
.../auth/drive — full access (avoid unless the task truly needs it).
Refresh tokens do not rotate — store the first refresh token you receive
(stable per user); persist it in the canister's stable state, never in a Wasm
global.
3. is_replicated = ?false is REQUIRED
Every Drive outcall carries an Authorization: Bearer <token>. Under the default
replicated execution, each replica in the subnet issues the request independently
— multiplying cost and, for writes, causing duplicate side effects (e.g. creating
N copies of a file). The generated defaultConfig already sets
is_replicated = ?false (via the client's isReplicated generator option), so
starting from defaultConfig gives you the correct value for both reads and
writes — keep it ?false and do not override it to ?true. (Writes —
PUT/DELETE/PATCH — are additionally forced non-replicated per call by the client.)
4. Using the client — the nested facade
googledrive-client ships a Client.mo facade (generated with
fluentHierarchical) that captures Config once and exposes Drive's resource
hierarchy as nested classes:
import { Client } "mo:googledrive-client/Client";
import { type Config; defaultConfig } "mo:googledrive-client/Config";
// Only the bearer token differs from the generated `defaultConfig`, which
// already sets `is_replicated = ?false` (non-replicated reads and writes).
let cfg : Config = {
defaultConfig with
auth = ?(#bearer accessToken); // token obtained/refreshed via google-oauth
};
let drive = Client(cfg);
// Facade methods are `async` (use `await`, not `await*`) and take the Drive
// endpoint's full query-parameter list positionally — consult the generated
// signatures in `mo:googledrive-client/Client`. For example `FilesResource.list`
// begins `uploadType : Text, oauthToken : Text, key : Text, fields : Text,
// accessToken : Text, alt : ?DriveAboutGetAltParameter, …`.
let files = await drive.files.list(/* … see FilesResource.list signature … */);
let perm = await drive.permissions.create(/* fileId, …, permission */);
(The per-tag API modules — mo:googledrive-client/Apis/FilesApi, …/PermissionsApi,
etc. — are also available if you prefer flat calls; the facade just captures
Config for you.)
5. Available API surface
Resource groups (14), addressed as Client(cfg).<group>.<method>:
- files — copy, create, delete, get, list, update, watch, generateIds,
listLabels, modifyLabels (metadata/organization; see Media caveat for
create/update/get/export)
- permissions — create, delete, get, list, update (sharing)
- comments / replies — create, delete, get, list, update
- drives — create, delete, get, hide, list, unhide, update (shared drives)
- revisions — delete, get, list, update
- changes — getStartPageToken, list, watch (sync)
- about — get; apps — get, list; channels — stop
6. Media caveat (this client is metadata-only)
files.create / files.update (upload), files.get?alt=media /
files.export / revisions.get (download) are generated as JSON-typed
operations. They will not move file bytes — multipart/resumable upload and
binary download are outside this client's JSON-over-HTTPS model (and strain IC
outcall size limits). Use these methods for metadata only. If a build genuinely
needs to move file content, that path must be hand-written (separate
content-host request with the appropriate binary body) and is out of scope for
this connector today.
1---2name: connector-googledrive3description: MANDATORY recipe for every Caffeine build that lists, reads, creates, shares, or organizes files and folders on the user's own Google Drive. The ONLY supported path is the `googledrive-client` mops package (Drive REST API v3) combined with the `google-oauth` mops package (OAuth 2.0 token exchange + refresh + PKCE). Hand-rolling `ic.http_request` calls to `oauth2.googleapis.com` or `www.googleapis.com/drive/v3` is a FORBIDDEN anti-pattern — it bypasses bearer auth, the `is_replicated = ?false` replication-cost safeguard, and the `google-oauth` library's token handling. Load this skill ONLY when the user, spec, or a prior task refers to **Google Drive specifically** — e.g. "Google Drive", "my Drive", "Drive files/folders", a Drive file/folder ID or share link, "upload to Google Drive", "list my Drive files", or Drive sharing/permissions. Do NOT load it for generic file storage, documents, uploads, or access-control features that are not Google Drive — those are unrelated and this connector must not be attached 4---5
6# Google Drive Connector
7
8> **Note.** The client is generated from the Drive v3 Discovery document.
9> `diagnostics` is on, so a decode gap traps loudly rather than returning
10> silently-wrong data. The nested-facade methods take each Drive endpoint's
11> **full query-parameter list positionally** (many `Text`/`Bool` args) — consult
12> the generated signatures in `Client.mo` before calling. Media endpoints are
13> metadata-only (see the Media caveat).
14
15## Orchestrator routing notes
16
17The `googledrive-client` + `google-oauth` pair is the **only** supported way to
18reach Google Drive from a Caffeine canister. If a build needs Drive, add both as
19mops dependencies and follow this skill. Never emit raw `ic.http_request` calls to
20Google hosts.
21
22| Task | Use |
23|---|---|
24| List / search the user's files & folders | `googledrive-client` `Client(cfg).files.list(...)` + `google-oauth` |
25| Read file metadata | `Client(cfg).files.get(...)` (metadata only — see Media caveat) |
26| Create a folder / file metadata | `Client(cfg).files.create(...)` |
27| Move / rename / trash | `Client(cfg).files.update(...)` / `.delete(...)` |
28| Share a file / manage access | `Client(cfg).permissions.create/list/update/delete(...)` |
29| Comments & replies | `Client(cfg).comments.*` / `Client(cfg).replies.*` |
30| Shared drives | `Client(cfg).drives.*` |
31| Change notifications / sync token | `Client(cfg).changes.*` |
32
33> **Not supported by this client:** transferring file *bytes* (upload/download of
34> content). See the Media caveat at the end. Use it for metadata, organization,
35> and sharing.
36
37# Backend
38
39The connector has two mops packages:
40
411. **`googledrive-client`** — the generated Motoko client for Drive REST API v3.
42 OAuth-agnostic: every call takes a bearer token via its `Config`.
432. **`google-oauth`** — Google OAuth 2.0 mechanics (PKCE, authorize URL, code
44 exchange, token refresh). Shared with the other Google connectors. This is a
45 **separate mops package you add alongside** the client — it is *not* a
46 dependency of `googledrive-client` itself.
47
48## 1. Add dependencies
49
50```bash
51mops add googledrive-client
52mops add google-oauth@0.2.0
53```
54
55## 2. Auth model — OAuth 2.0 PKCE, on-chain exchange + refresh
56
57The canister performs the OAuth flow on-chain via `google-oauth`:
58
591. Generate a PKCE `code_verifier` / `code_challenge`.
602. Build the Google authorize URL (`google-oauth`), redirect the user.
613. Exchange the returned authorization code for an access + refresh token
62 (`google-oauth.exchangeAuthorizationCode`) — **non-replicated** outcall.
634. On `401`/expiry, refresh the access token (`google-oauth.refreshAccessToken`)
64 and retry.
65
66**Scopes** (request the narrowest that works):
67- `https://www.googleapis.com/auth/drive.file` — per-file access to files the app
68 creates/opens (preferred, least-privilege).
69- `.../auth/drive.readonly` — read-only over all files.
70- `.../auth/drive.metadata.readonly` — metadata only.
71- `.../auth/drive` — full access (avoid unless the task truly needs it).
72
73**Refresh tokens do not rotate** — store the first refresh token you receive
74(stable per user); persist it in the canister's stable state, never in a Wasm
75global.
76
77## 3. `is_replicated = ?false` is REQUIRED
78
79Every Drive outcall carries an `Authorization: Bearer <token>`. Under the default
80replicated execution, each replica in the subnet issues the request independently
81— multiplying cost and, for writes, causing duplicate side effects (e.g. creating
82N copies of a file). **The generated `defaultConfig` already sets
83`is_replicated = ?false`** (via the client's `isReplicated` generator option), so
84starting from `defaultConfig` gives you the correct value for **both reads and
85writes** — keep it `?false` and do not override it to `?true`. (Writes —
86PUT/DELETE/PATCH — are additionally forced non-replicated per call by the client.)
87
88## 4. Using the client — the nested facade
89
90`googledrive-client` ships a `Client.mo` facade (generated with
91`fluentHierarchical`) that captures `Config` once and exposes Drive's resource
92hierarchy as nested classes:
93
94<!-- motoko-check:skip -->
95```motoko
96import { Client } "mo:googledrive-client/Client";
97import { type Config; defaultConfig } "mo:googledrive-client/Config";
98
99// Only the bearer token differs from the generated `defaultConfig`, which
100// already sets `is_replicated = ?false` (non-replicated reads and writes).
101let cfg : Config = {
102 defaultConfig with
103 auth = ?(#bearer accessToken); // token obtained/refreshed via google-oauth
104};
105
106let drive = Client(cfg);
107
108// Facade methods are `async` (use `await`, not `await*`) and take the Drive
109// endpoint's full query-parameter list positionally — consult the generated
110// signatures in `mo:googledrive-client/Client`. For example `FilesResource.list`
111// begins `uploadType : Text, oauthToken : Text, key : Text, fields : Text,
112// accessToken : Text, alt : ?DriveAboutGetAltParameter, …`.
113let files = await drive.files.list(/* … see FilesResource.list signature … */);
114let perm = await drive.permissions.create(/* fileId, …, permission */);
115```
116
117(The per-tag API modules — `mo:googledrive-client/Apis/FilesApi`, `…/PermissionsApi`,
118etc. — are also available if you prefer flat calls; the facade just captures
119`Config` for you.)
120
121## 5. Available API surface
122
123Resource groups (14), addressed as `Client(cfg).<group>.<method>`:
124
125- **files** — copy, create, delete, get, list, update, watch, generateIds,
126 listLabels, modifyLabels (metadata/organization; see Media caveat for
127 create/update/get/export)
128- **permissions** — create, delete, get, list, update (sharing)
129- **comments** / **replies** — create, delete, get, list, update
130- **drives** — create, delete, get, hide, list, unhide, update (shared drives)
131- **revisions** — delete, get, list, update
132- **changes** — getStartPageToken, list, watch (sync)
133- **about** — get; **apps** — get, list; **channels** — stop
134
135## 6. Media caveat (this client is metadata-only)
136
137`files.create` / `files.update` (upload), `files.get?alt=media` /
138`files.export` / `revisions.get` (download) are generated as **JSON-typed**
139operations. They will **not** move file *bytes* — multipart/resumable upload and
140binary download are outside this client's JSON-over-HTTPS model (and strain IC
141outcall size limits). Use these methods for metadata only. If a build genuinely
142needs to move file content, that path must be hand-written (separate
143`content`-host request with the appropriate binary body) and is out of scope for
144this connector today.