Google Sheets
Preview
Read ranges and add rows in Google Sheets. Share a sheet with the service account and apps can use it.
- Status: Preview
- Shelf: Data and docs
- How you connect: Service account key file
- Regions: global and Kazakhstan
- The service’s own API reference: https://developers.google.com/workspace/sheets/api/reference/rest
Apps read ranges and add rows in Google Sheets through a service account: a robot with an
address of its own (name@project.iam.gserviceaccount.com) that can open only the spreadsheets
someone shares with it. Nobody signs in, so there is no OAuth app to register and the connection
does not depend on one person's Google login.
How it works#
- An admin uploads the service account's JSON key file on Connectors. The API reads it,
keeps only
{ type, client_email, private_key }(Google'stoken_uri, project and client ids are dropped) and seals that to the gate (HPKE, write-only, bound to the workspace and thesheetsconnector). The address is kept in plain text, because it is not secret and the console must show it so a sheet can be shared with it. - For a call, the gate opens the key in memory, signs a short-lived assertion (RS256) and trades
it at
oauth2.googleapis.comfor an access token. The token is kept in gate memory until two minutes before Google would refuse it, keyed by workspace, sealed blob and scope, so a rotated key is a new entry and one workspace can never be handed another's token. - The gate builds the whole Sheets request from the app's small input. Apps never see the key, the token or the token endpoint.
The key file's own token_uri is ignored: the token host and the Sheets host are constants in the
gate, so a key file cannot aim it at another server. A token Google refuses is dropped and the
call is made once more with a fresh one.
Connect it#
- In Google Cloud, create or pick a project and turn on the Google Sheets API.
- IAM → Service accounts (console.cloud.google.com/iam-admin/serviceaccounts): create a service account. It needs no project role.
- Open it → Keys → Add key → Create new key → JSON. Google downloads the file once.
- Qaldy → Connectors → Google Sheets → Connect Google Sheets → Choose the key file (or paste its contents) → Seal and connect. The console reads the file in your browser first and says what is wrong with a wrong one (an OAuth client file, a truncated copy) before anything is sent. Only the three fields above leave the page.
- Share each spreadsheet with the account's address (the console shows it, with a Copy
button): in Google Sheets press Share and add it as Editor, or Viewer if apps only
read. Without this Google answers
403or404, and the SDK adds "Share the spreadsheet with the service account’s address" to the error.
Test connection signs in at Google and checks that Google accepts the key. It does not open a spreadsheet (the gate doesn't know one), so a good test does not prove a sheet is shared.
What the gate allows#
| Hosts | sheets.googleapis.com for calls, oauth2.googleapis.com to sign in (both are listed in the app's contract, "the only hosts its gate opens to") |
| Actions | readRange and appendRow; any other action is 403 action_not_supported |
readRange | GET /v4/spreadsheets/{id}/values/{range}, with a read-only token (spreadsheets.readonly), so an app that may only read can't write even when the account is an editor |
appendRow | POST …/values/{range}:append, valueInputOption=RAW, insertDataOption=INSERT_ROWS, with the spreadsheets scope |
| Inputs | spreadsheetId 10–128 of A-Za-z0-9_-; range up to 200 characters, no control characters, percent-encoded into one path segment; one row of up to 200 cells (text up to 50 000 characters, numbers, true/false or empty) |
| Values | stored as typed (RAW): a cell that starts with = stays text and never runs as a formula |
The fence on which spreadsheets is Google's own: the account sees only what is shared with it. A manifest-level scope per spreadsheet is not built; the templates take the sheet id from a setting an editor can change.
Use it from an app#
{ "id": "sheets", "actions": ["readRange", "appendRow"] }const { values } = await ctx.connectors.sheets.readRange({
spreadsheetId,
range: 'Orders!A2:F',
})
await ctx.connectors.sheets.appendRow({
spreadsheetId,
range: 'Orders!A:F',
values: ['Aigerim', 42, true],
})readRange returns values: string[][] as Sheets shows them (formatted text). The SDK also
accepts a pasted sheet address in place of the id.
When it breaks#
| You see | Why | Do |
|---|---|---|
| Connect says "That is an OAuth client file" | The downloaded JSON has installed or web | Create the key on a service account, not an OAuth client |
| Test: "refused the key (401)" | The key was deleted or the account disabled | Create a new key and Replace the key |
403 / 404 on a call | The spreadsheet isn't shared with the account | Share it with the address shown on the connector |
403 on appendRow only | The account is a Viewer | Make it an Editor |
429 | Google's per-minute quota | Slow the app down, or read a wider range in one call |
Gaps#
- Never run against a live Google account (hence Preview).
- No spreadsheet-level scope in the manifest, no "row added" trigger (the catalog lists it as planned), no formatting, no sheet creation.
- The access token lives in gate memory, so a gate restart signs in again (one extra call).