Google Sheets
Proxy for the Google Sheets v4 REST API. Dual auth by design — like drive and calendar, the typical production setup runs the connector in one of two modes per managed resource:
idp_passthroughmode for human users who want an agent to read or edit spreadsheets they already own. Each request carries the caller's own OAuth grant.google_samode uses a Workspace service account with domain-wide delegation. For each call it impersonates the calling user, so the user does not link a Google account, but the call still needs a user token. The user is the token'ssubwhen that is an email, otherwise the user's email, and its domain must match the Workspace domain setup field. No setup field names a user to impersonate. Aclient_credentialscall has no user and gets 502Upstream auth failed: No user identity in context; cannot impersonate(GoogleServiceAccountProvider.java). The connector sets no Google scopes for this mode, so the gateway asks Google fordrive.readonlyunless the resource's upstream auth carries ascopeslist.
google_sa set up from the Admin Portal fails in 0.65.0. Every call gets
502 Upstream auth failed: No Google SA key configured, because the form
stores the secret reference under sa_key_env
(ConnectorV1Adapter.java:316-320) and the gateway reads it only from
service_account_key_env (GoogleServiceAccountProvider.java:187-196).
After you create the resource, rename the key with the admin API:
NAME=sheets-sa # the managed resource's name
curl -s -H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" "$PBAC_AS_URL/admin/api/managed-resources/$NAME" \
| jq '.upstreamAuth.service_account_key_env = .upstreamAuth.sa_key_env | del(.upstreamAuth.sa_key_env)' \
| curl -s -X PUT -H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" -H "Content-Type: application/json" \
"$PBAC_AS_URL/admin/api/managed-resources/$NAME" -d @-
To ask Google for other scopes, append
| .upstreamAuth.scopes = ["<scope URL>"] to the jq filter. We ran this
on a test instance with a stub token endpoint standing in for Google. After
the rename, the gateway sent a signed JWT-bearer assertion with the calling
user as sub and drive.readonly, or the scopes we set, as scope, and
forwarded the token it got back to the upstream. A user outside the
Workspace domain got 502 Email domain '...' does not match configured domain. We have not run a google_sa call through to Google.
Overview
Typical uses:
- An agent reads a forecast tab and summarizes the numbers (
get_values,batch_get_values). - A reporting bot appends rows to a tracking sheet (
append_values) or rewrites a range (update_values). - A workflow spins up a fresh spreadsheet, adds a tab, and copies a
template sheet into it (
create_spreadsheet,batch_update,copy_sheet).
Two upstream-shape notes specific to Sheets:
- No list endpoint. The Sheets API can't enumerate spreadsheets;
discover spreadsheet IDs via the Google Drive connector (filter on
mimeType = 'application/vnd.google-apps.spreadsheet'), then operate on the ID here. - Deletes go through
batch_update. Deleting a sheet, row, column, or range is adeleteSheet/deleteDimension/deleteRangerequest object onbatch_update; deleting an entire spreadsheet file is the Drive connector's job.update_values/append_valuescarryvalueInputOptionin the query string (RAWvsUSER_ENTERED), and ranges use A1 notation (Sheet1!A1:C10).
Default policy
The connector ships connectors/identos.google-sheets/policy/sheets.rego
(package pbac.connectors.identos.google_sheets), which holds no rules.
The IdP-routing obligation that earlier releases documented here guarded on
input.connector, a field no entrypoint sets, so it never fired, and it was
removed. In idp_passthrough mode the gateway enforces the Google link
itself. Before it forwards a call it checks for a usable linked Google token, and without one it answers
401 with "error": "idp_link_required" and a link_url.
Blocked and read-only spreadsheets
Blocking a specific spreadsheet is operator config on the general
file-access control, not a rule in this connector. An identity source
matches a spreadsheet by its own id: ids in blocked are denied on reads and
writes, ids in write_blocked are denied on writes only (update_values,
append_values, clear_values, batch_clear_values, batch_update,
copy_sheet), so reads still work. See
File access control below for the shape — an
identity source pools with any label or location source governing the same
connector.
File access control
Spreadsheets can be gated by Drive Labels and/or by folder/shared-drive
location — a spreadsheet is returnable via get_spreadsheet if it matches
any governing source's approval (an approved label or an approved folder),
and denied if it matches any source's blocked (pooled OR, deny-wins). This
is configured through the shared file_access operator control, used across
all Google file connectors (Docs, Sheets, Slides, Drive). Full mechanics (the
pooled predicate, OR-combine and deny-wins, fail-closed rules) live in the
File Access Control guide;
for getting label/folder IDs and the required OAuth scopes, see the
Google file access setup guide.
Configuration uses a two-level model:
- Define a policy per source — under
pbac.operator.controls.file_access.sources, each source declares atype(label,location, oridentity— the resource's own id), arequiredallowlist (any-of; file must match ≥1), ablockeddenylist (file withheld if it matches any — always wins), and an optionalwrite_blockeddenylist that applies to mutating calls only. Approving a whole folder is alocationsource with the folder ID inrequired(Google Drive does not allow labels on folders):
curl -X PUT "http://localhost:8080/admin/api/policy-data/by-key?dataKey=pbac.operator.controls.file_access" \
-H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"dataKey": "pbac.operator.controls.file_access",
"payload": {
"sources": {
"google-drive-labels": {
"type": "label",
"required": ["<approved-label-id>"],
"blocked": ["<restricted-label-id>"]
}
},
"governed": {
"urn:connector:identos:google-sheets": ["google-drive-labels"]
}
}
}'
The call replaces the whole row, so keep the sources and governed entries
of every other connector you govern in the payload.
- Activate per connector — under
governed, list (as an array) which sources govern each connector's resource type; all listed sources pool into one OR-of-approvals, deny-wins decision — a file is returnable if it matches any source'srequiredand withheld if it matches any source'sblocked. Omitting a connector leaves it ungoverned (inert; no check). Once a source is configured and a connector is governed by it, enforcement is fail-closed: reads that cannot verify the file's values are denied (403).
The control resolves spreadsheet IDs from tool invocations to fetch labels/location; file and scope requirements are documented in the spec at docs/superpowers/specs/2026-08-11-general-file-access-control-design.md.
Scope model
| Scope | Used for | |
|---|---|---|
| PBAC scopes (internal) | sheets:read, sheets:write | Route authorization |
| Upstream OAuth scopes | openid, userinfo.email, spreadsheets, drive.metadata.readonly, drive.labels.readonly, drive.labels | Google-side grants the user consents to (idp_passthrough mode). google_sa mode sets none and gets drive.readonly |
See Google Workspace: multi-connector setup for how sheets' scopes compose with drive, gmail, and calendar on a single Google IdP.
Manifest reference
- ID:
identos.google-sheets - Version:
1.3.0 - Resource type:
urn:connector:identos:google-sheets
Supported auth modes
| Type | Details |
|---|---|
idp_passthrough | requires IdP google |
google_sa | setup fields: sa_key_env, domain |
Setup fields
| ID | Label | Default | Secret? | Notes |
|---|---|---|---|---|
base_url | API base URL | https://sheets.googleapis.com | no | — |
upstream_auth.type | Authentication | idp_passthrough | no | idp_passthrough forwards each user's own Google OAuth token (recommended). google_sa uses a Workspace service account with domain-wide delegation impersonating a fixed user. |
sa_key_env | Service account key | — | yes | Pick a secret containing the service-account JSON. Required only for the google_sa auth mode. / shown when upstream_auth.type == 'google_sa' |
domain | Workspace domain | — | no | placeholder: example.com / Required only for the google_sa auth mode. / shown when upstream_auth.type == 'google_sa' |
Scopes
| Scope |
|---|
sheets:read |
sheets:write |
Routes
| Method | Pattern | Scope | Resource template |
|---|---|---|---|
POST | /v4/spreadsheets | sheets:write | — |
GET | /v4/spreadsheets/{spreadsheet_id} | sheets:read | sheets://{{spreadsheet_id}} |
GET | /v4/spreadsheets/{spreadsheet_id}/values/{range} | sheets:read | sheets://{{spreadsheet_id}} |
GET | /v4/spreadsheets/{spreadsheet_id}/values:batchGet | sheets:read | sheets://{{spreadsheet_id}} |
PUT | /v4/spreadsheets/{spreadsheet_id}/values/{range} | sheets:write | sheets://{{spreadsheet_id}} |
POST | /v4/spreadsheets/{spreadsheet_id}/values/{range}:append | sheets:write | sheets://{{spreadsheet_id}} |
POST | /v4/spreadsheets/{spreadsheet_id}/values/{range}:clear | sheets:write | sheets://{{spreadsheet_id}} |
POST | /v4/spreadsheets/{spreadsheet_id}/values:batchClear | sheets:write | sheets://{{spreadsheet_id}} |
POST | /v4/spreadsheets/{spreadsheet_id}/sheets/{sheet_id}:copyTo | sheets:write | sheets://{{spreadsheet_id}} |
POST | /v4/spreadsheets/{spreadsheet_id}:batchUpdate | sheets:write | sheets://{{spreadsheet_id}} |
MCP tools
| Name | Scope | Description |
|---|---|---|
create_spreadsheet | sheets:write | Create a new spreadsheet. Pass the Sheets Spreadsheet resource body (e.g. {properties:{title}, sheets:[...]}). |
get_spreadsheet | sheets:read | Get a spreadsheet's metadata and (optionally) cell data. Use the includeGridData flag sparingly — it can be large. |
get_values | sheets:read | Read values from a single A1 range (e.g. "Sheet1!A1:C10"). |
batch_get_values | sheets:read | Read values from multiple A1 ranges in one call. |
update_values | sheets:write | Overwrite values in an A1 range. Pass values as a 2-D array; set valueInputOption (RAW or USER_ENTERED). |
append_values | sheets:write | Append rows after the last row of a table within the given A1 range. |
clear_values | sheets:write | Clear values (not formatting) from a single A1 range. |
batch_clear_values | sheets:write | Clear values from multiple A1 ranges in one call. |
copy_sheet | sheets:write | Copy a single sheet from this spreadsheet into another spreadsheet. |
batch_update | sheets:write | Apply one or more structural updates (add/delete sheet, insert/delete rows & columns, formatting, etc.) via Sheets batchUpdate request objects. This is also how you DELETE sheets/rows/columns/ranges. |
Send feedback about this page