# Google Service Account

Authenticate with Google APIs using a service account key from your workflow.

Service account authentication lets your workflow call Google APIs (Sheets, Drive, Gmail, Calendar, etc.) without an interactive OAuth flow. You generate a key from Google Cloud, store the credentials as workflow secrets, and use `api.google.getServiceAccountToken()` to get a short-lived access token.

Treat these tokens as short-lived. In multi-step or fan-out workflows, fetch the token again in each step that needs it rather than passing the token through the payload.

## api.google.getServiceAccountToken()

```js
const { token, error } = await api.google.getServiceAccountToken(
  privateKey,
  clientEmail,
  scopes,
  impersonateUser  // optional
);
```

| Parameter | Type | Description |
| --- | --- | --- |
| `privateKey` | `string` | The `private_key` field from your service account JSON key file |
| `clientEmail` | `string` | The `client_email` field from your service account JSON key file |
| `scopes` | `string[]` | Array of scope keys (see table below) |
| `impersonateUser` | `string` | Optional. Email of a Google Workspace user to impersonate. Requires domain-wide delegation to be enabled on the service account. |

Returns `{ token, error }`. Always check `error` before using `token`.

### Scope keys

Pass scope keys as strings, not URLs. The platform resolves them to the correct Google OAuth scope URLs.

| Key | Google API |
| --- | --- |
| `'SHEETS'` | Spreadsheets (read/write) |
| `'SHEETS_READONLY'` | Spreadsheets (read only) |
| `'DRIVE'` | Drive (full access) |
| `'DRIVE_READONLY'` | Drive (read only) |
| `'DRIVE_FILE'` | Drive files created by the app |
| `'GMAIL'` | Gmail (read/modify) |
| `'GMAIL_SEND'` | Gmail (send only) |
| `'GMAIL_READONLY'` | Gmail (read only) |
| `'CALENDAR'` | Calendar (read/write) |
| `'CALENDAR_EVENTS'` | Calendar events (read/write) |
| `'CALENDAR_READONLY'` | Calendar (read only) |
| `'DOCS'` | Google Docs (read/write) |
| `'DOCS_READONLY'` | Google Docs (read only) |
| `'PEOPLE'` | Contacts (read/write) |
| `'PEOPLE_READONLY'` | Contacts (read only) |
| `'ADMIN_DIRECTORY'` | Admin SDK directory |
| `'ADMIN_REPORTS'` | Admin SDK usage reports |

## api.google.sheet

`api.google.sheet` provides helpers for the Google Sheets API. Each method takes an access token obtained from `api.google.getServiceAccountToken()` as its first argument.

These helpers are only for Google Sheets. For Drive, Gmail, Calendar, Docs, or other Google APIs, get a token with `api.google.getServiceAccountToken()` and then call the API directly with `fetch()`.

### appendRows()

Appends one or more rows to a sheet. Rows are added after the last row with data in the specified range.

```js
await api.google.sheet.appendRows(token, spreadsheetId, range, values);
```

| Parameter | Type | Description |
| --- | --- | --- |
| `token` | `string` | Access token from `getServiceAccountToken()` |
| `spreadsheetId` | `string` | The spreadsheet ID from the Google Sheets URL |
| `range` | `string` | A1 notation of the range, e.g. `'Sheet1!A1'` |
| `values` | `any[][]` | Array of rows, each row an array of cell values |

### readRows()

Reads values from a range.

```js
const rows = await api.google.sheet.readRows(token, spreadsheetId, range);
```

Returns a 2D array of cell values, e.g. `[['Name', 'Email'], ['Alice', 'alice@example.com']]`.

### updateRows()

Overwrites a range with new values.

```js
await api.google.sheet.updateRows(token, spreadsheetId, range, values);
```

### clearRange()

Clears all values in a range without deleting the cells.

```js
await api.google.sheet.clearRange(token, spreadsheetId, range);
```

### createSheet()

Creates a new spreadsheet and returns the API response (including the new spreadsheet ID).

```js
const result = await api.google.sheet.createSheet(token, title, properties);
```

| Parameter | Type | Description |
| --- | --- | --- |
| `title` | `string` | The name of the new spreadsheet |
| `properties` | `object` | Optional. Additional spreadsheet properties |

### getSheetMetadata()

Returns the spreadsheet metadata (title, sheet names, IDs, etc.).

```js
const meta = await api.google.sheet.getSheetMetadata(token, spreadsheetId);
```

### shareSheet()

Shares a spreadsheet with a Google account.

```js
await api.google.sheet.shareSheet(token, spreadsheetId, emailAddress, role, type);
```

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `emailAddress` | `string` | required | The email to share with |
| `role` | `string` | `'writer'` | `'reader'`, `'commenter'`, or `'writer'` |
| `type` | `string` | `'user'` | `'user'`, `'group'`, `'domain'`, or `'anyone'` |

### createAndShareSheet()

Creates a spreadsheet and immediately shares it. This is a convenience wrapper around `createSheet()` plus `shareSheet()`.

```js
const result = await api.google.sheet.createAndShareSheet(token, title, shareWithEmail, role, properties);
```

## Setup

### 1. Create a service account in Google Cloud

1. Go to **Google Cloud Console → IAM & Admin → Service Accounts**
2. Click **Create Service Account**, give it a name, and click **Done**
3. Enable the Google APIs your workflow needs, such as Sheets API or Drive API
4. Click the service account → **Keys → Add Key → Create new key → JSON**
5. Download the JSON key file. You need `private_key` and `client_email` from it

### 2. Grant access to your spreadsheet

Share the spreadsheet with the service account's `client_email` address (the same way you would share with a person), giving it the role your workflow needs (`Viewer` or `Editor`).

For shared Sheets or Drive files, impersonation is usually not needed. For Gmail, Calendar, or other user-owned Google Workspace data, domain-wide delegation and `impersonateUser` are often required.

### 3. Store credentials as workflow secrets

In the workflow code editor, open **More actions → Manage variables** and add:

- `GOOGLE_PRIVATE_KEY` - the `private_key` value from the JSON key file
- `GOOGLE_CLIENT_EMAIL` - the `client_email` value from the JSON key file
- `SPREADSHEET_ID` - if you want to use the example below without hardcoding a spreadsheet ID

### 4. Use in your workflow

```js
export class Workflow {
  async start(data, headers, api) {
    await api.scheduleNextStep({
      delay: 10,
      action: 'writeToSheet',
      payload: { orderId: data.id, total: data.total_price },
    });
  }

  async writeToSheet({ orderId, total }, headers, api) {
    const { token, error } = await api.google.getServiceAccountToken(
      env.GOOGLE_PRIVATE_KEY,
      env.GOOGLE_CLIENT_EMAIL,
      ['SHEETS']
    );

    if (error) {
      console.log('Failed to get Google token:', error);
      return;
    }

    await api.google.sheet.appendRows(
      token,
      env.SPREADSHEET_ID,
      'Orders!A1',
      [[orderId, total, new Date().toISOString()]]
    );
  }
}
```

If you need Gmail, Calendar, Drive, or Docs with a service account token, use the token in a normal `fetch()` call to the Google API you need.