Skip to main content

Google Docs

Proxy for the Google Docs v1 REST API — which is the entire API surface: create_document, get_document, and batch_update. Dual auth by design, like drive and calendar:

  • idp_passthrough mode for human users who want an agent to read or edit documents they own. Each request carries the caller's own OAuth grant.
  • google_sa mode 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's sub when 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. A client_credentials call has no user and gets 502 Upstream 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 for drive.readonly unless the resource's upstream auth carries a scopes list.

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=docs-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 doc's structured content and summarizes it (get_document).
  • A drafting workflow creates a doc and inserts/templates text into it (create_document, then batch_update with insertText / replaceAllText).

Two upstream-shape notes specific to Docs:

  • No list endpoint. The Docs API can't enumerate documents; discover document IDs via the Google Drive connector (filter on mimeType = 'application/vnd.google-apps.document'), then operate on the ID here.
  • Everything mutating is batch_update. All content edits and deletes (insertText, replaceAllText, deleteContentRange, table ops) are request objects on batch_update; deleting an entire document file is the Drive connector's job.

Default policy​

The connector ships connectors/identos.google-docs/policy/docs.rego (package pbac.connectors.identos.google_docs), 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 documents​

Blocking a specific document is operator config on the general file-access control, not a rule in this connector. An identity source matches a document by its own id: ids in blocked are denied on reads and writes, ids in write_blocked are denied on writes only (batch_update), 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​

Documents can be gated by Drive Labels and/or by folder/shared-drive location — a document is returnable via get_document 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:

  1. Define a policy per source — under pbac.operator.controls.file_access.sources, each source declares a type (label, location, or identity — the resource's own id), a required allowlist (any-of; file must match ≥1), a blocked denylist (file withheld if it matches any — always wins), and an optional write_blocked denylist that applies to mutating calls only. Approving a whole folder is a location source with the folder ID in required (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-docs": ["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.

  1. 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's required and withheld if it matches any source's blocked. 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 document 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​

ScopeUsed for
PBAC scopes (internal)docs:read, docs:writeRoute authorization
Upstream OAuth scopesopenid, userinfo.email, documents, drive.metadata.readonly, drive.labels.readonly, drive.labelsGoogle-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 docs' scopes compose with drive, gmail, and calendar on a single Google IdP.

Manifest reference​

  • ID: identos.google-docs
  • Version: 1.5.0
  • Resource type: urn:connector:identos:google-docs

Supported auth modes​

TypeDetails
idp_passthroughrequires IdP google
google_sasetup fields: sa_key_env, domain

Setup fields​

IDLabelDefaultSecret?Notes
base_urlAPI base URLhttps://docs.googleapis.comno—
upstream_auth.typeAuthenticationidp_passthroughnoidp_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_envService account key—yesPick a secret containing the service-account JSON. Required only for the google_sa auth mode. / shown when upstream_auth.type == 'google_sa'
domainWorkspace domain—noplaceholder: example.com / Required only for the google_sa auth mode. / shown when upstream_auth.type == 'google_sa'

Scopes​

Scope
docs:read
docs:write

Routes​

MethodPatternScopeResource template
POST/v1/documentsdocs:write—
GET/v1/documents/{document_id}docs:readdocs://{{document_id}}
POST/v1/documents/{document_id}:batchUpdatedocs:writedocs://{{document_id}}

MCP tools​

NameScopeDescription
create_documentdocs:writeCreate a new, empty document. Pass {"title": "..."}. Add content afterward with batch_update.
get_documentdocs:readGet a document's full structured content, ALL tabs included. Content is under tabs[]: each entry has tabProperties (tabId, title, index) and documentTab.body, and child tabs nest under childTabs. The legacy top-level body field is not returned. A ?tab=t.<n> in a Docs URL names a tabId, so match it against tabProperties.tabId.
batch_updatedocs:writeApply one or more content edits (insertText, replaceAllText, deleteContentRange, table ops, styling, …) via Docs Request objects. This is also how you DELETE content.