# Google Sheets Export: Append, Upsert & Sync (`brasspage/google-sheets-sync`) Actor

Google Sheets export and sync for any Apify dataset, Actor run output or JSON rows. Append, replace or upsert by key column, with a summary of rows appended, updated and unchanged. Share the sheet with our bot email: no Google Cloud setup, no expiring tokens. $0.10 per 1,000 rows.

- **URL**: https://apify.com/brasspage/google-sheets-sync.md
- **Developed by:** [Brasspage](https://apify.com/brasspage) (community)
- **Categories:** Integrations, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 93.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 row writtens

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### Google Sheets Export: Append, Upsert & Sync

Give it a Google Sheet URL and an Apify dataset ID (or JSON rows). It writes the rows to the tab you name, with a header row made from your data, and returns a summary of what changed. **$0.10 per 1,000 rows written.** Unchanged rows in upsert mode are free.

| You give it                                             | You get back                                                        |
| ------------------------------------------------------- | ------------------------------------------------------------------- |
| Sheet URL and tab name                                  | Your rows in that tab, header row added, new fields as new columns  |
| Dataset ID, for example `{{resource.defaultDatasetId}}` | Nested fields flattened: `price.value` becomes column `price/value` |
| Mode: `append`, `replace` or `upsert` by a key column   | A summary row: `rowsAppended`, `rowsUpdated`, `rowsUnchanged`       |
| Share the sheet with our bot email (2 minutes)          | Errors that say what to fix, and retries on Google quota errors     |

Example: a scraper dataset row `{"url": "https://shop.example/a", "title": "Mug", "price": {"value": 12, "currency": "USD"}}` becomes this sheet row:

| url                    | title | price/value | price/currency |
| ---------------------- | ----- | ----------- | -------------- |
| https://shop.example/a | Mug   | 12          | USD            |

**Before you click Start:** share your sheet with `sheets-sync-test@brasspage-tools.iam.gserviceaccount.com` as **Editor**, put your Apify user ID in a cell named `brasspage_owner`, and paste the sheet URL. No Google Cloud project, no key. With no input a run only returns this setup note and costs the $0.00005 start. [Setup takes about 2 minutes](#setup-in-2-minutes). 11 of 11 runs succeeded in the last 30 days (Apify run stats, Oct 25, 2026).

Use it after a scraper run, on a schedule, or as an **integration** that runs by itself each time another Actor finishes.

- **Share and go:** share the sheet with our bot email. No OAuth pop-up, no tokens that expire after 7 days, and it works from the API, schedules, and AI agents.
- **Reliable writes:** large exports go in chunks, and Google quota errors (429) and server errors retry with exponential backoff.
- **Your column order stays the same.** New fields become new columns at the end.

### What you get

- **Your rows in the Google Sheet tab you name**, with a header row made from your data. If the tab does not exist, the Actor adds it.
- **One summary row per run:** the sheet URL, rows read, appended, updated, unchanged and skipped, and the columns written.
- **Errors that say what to fix** for login and sharing problems. Google quota errors retry by themselves.
- **$0.10 per 1,000 rows written.** Unchanged rows in upsert mode are free.

A run needs your sheet, so a one-click run with no input only returns a setup note (it costs only the $0.00005 start fee).

### How to use

1. In Google Sheets click **Share**, add `sheets-sync-test@brasspage-tools.iam.gserviceaccount.com` as **Editor**, and untick "Notify people".
2. Type your Apify user ID in one cell and name that cell `brasspage_owner` (**Data > Named ranges**). This proves the sheet is yours.
3. Open the Actor in the Apify Console, paste the sheet URL and a dataset ID, then click **Start**.
4. To export every run of a scraper, add this Actor on the scraper's **Integrations** tab with Dataset ID `{{resource.defaultDatasetId}}`.

### What makes it reliable

Exports to Google Sheets usually fail for the same few reasons. This Actor handles each one:

| Problem                                                               | What this Actor does                                                                  |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| The scraper returned 0 items                                          | The run **succeeds** and the sheet is not touched. Your integration does not break.   |
| Google quota error `429` or `500/503`, `ECONNRESET`                   | Retries up to 8 times with exponential backoff (up to about 4 minutes).               |
| OAuth token expired (`invalid_grant`)                                 | The bot login and service-account keys do not expire.                                 |
| The sheet is too small for the new rows or columns                    | The tab grows first, then the data is written.                                        |
| Large datasets time out                                               | Writes go in chunks of about 2 MB. Each chunk has an exact range, so a retry is safe. |
| Text longer than 50,000 characters (the Google cell limit)            | The text is cut to 50,000 characters and the run tells you.                           |
| The 10,000,000-cell limit                                             | Checked **before** writing, with a clear message.                                     |
| `"00123"` loses its zeros, `"1,5"` becomes a date, `=` runs a formula | Default `RAW` mode writes values exactly as they are.                                 |
| Append mode rewrites the whole sheet                                  | Append writes only the new rows. Upsert writes only the cells that changed.           |
| Tab name `Sheet1` matches `Sheet10`                                   | Exact tab-name match. If the tab does not exist, it is created.                       |

### Setup in 2 minutes

1. Open your Google Sheet and click **Share**.
2. Add `sheets-sync-test@brasspage-tools.iam.gserviceaccount.com`, select **Editor**, untick "Notify people", and click **Share**.
3. Prove the sheet is yours: type your Apify user ID into one empty cell (find it in Apify Console > **Settings** > **API & Integrations**, "User ID"; the first failed run also prints it). Select that cell, click **Data > Named ranges**, and name it `brasspage_owner`.
4. Paste the sheet URL into **Spreadsheet ID or URL**, give a data source (a dataset ID or JSON rows), choose a mode, and click **Start**.

Each run through the bot reads `brasspage_owner` before it writes. If the range is missing or holds anything other than your user ID, the run stops and writes nothing. Only an editor of your sheet can set that cell, so another Apify account that knows the sheet URL cannot write to it. To stop access at any time, remove the bot email from **Share**.

#### Your own Google key (optional)

If you would rather not share the sheet with a shared bot, use a service account of your own:

1. Create a Google Cloud project ([console.cloud.google.com/projectcreate](https://console.cloud.google.com/projectcreate)). You do not need billing.
2. Enable the [Google Sheets API](https://console.cloud.google.com/apis/library/sheets.googleapis.com) in that project.
3. Under **IAM & Admin > Service Accounts**, create a service account (no roles), open **Keys**, and click **Add key > Create new key > JSON**. Keep the file safe: it is a password.
4. Share your sheet with the `client_email` from the file as **Editor**.
5. Paste the **whole** file into **Your own service-account key (JSON, optional)**. Apify stores it encrypted.

If your Google Workspace blocks key creation (the policy `iam.disableServiceAccountKeyCreation`), use the bot login above or a personal Google account.

### Run it automatically after another Actor (integration)

1. Open the Actor that makes your data (for example, a scraper) and go to its **Integrations** tab.
2. Click **Add integration**, select **Apify Actor**, and choose **Google Sheets Sync**.
3. Fill in the spreadsheet and the mode (and your own key, if you use one).
4. In **Dataset ID**, type `{{resource.defaultDatasetId}}`. Apify replaces it with the dataset of the run that just finished.
5. Save. Each time the scraper run succeeds, this Actor writes that run's data to your sheet.

To export the data of one run by hand, open the run, go to its **Storage** tab, and copy the dataset ID into **Dataset ID**.

### Input examples

Each example was run once on the Apify platform on Oct 27, 2026, against our own test sheet. All three SUCCEEDED. To try one, do the 2-minute setup above, put your sheet URL in `spreadsheetId`, and paste it into the **JSON** input tab.

**1. Append JSON rows** (3.5 seconds, run `DcDRAnfjhbm84GvCM`). The tab `readme-books` did not exist, so the Actor made it and wrote the header `url, title, author/name, author/born, shelf`. Summary: 2 appended. Cost: 2 rows = $0.0002.

```json
{
  "spreadsheetId": "<your sheet URL>",
  "sheetName": "readme-books",
  "mode": "append",
  "rows": [
    {
      "url": "https://www.gutenberg.org/ebooks/1342",
      "title": "Pride and Prejudice",
      "author": { "name": "Jane Austen", "born": 1775 },
      "shelf": "to-read"
    },
    {
      "url": "https://www.gutenberg.org/ebooks/84",
      "title": "Frankenstein",
      "author": { "name": "Mary Shelley", "born": 1797 },
      "shelf": "to-read"
    }
  ]
}
```

**2. Upsert by a key column** (2.3 seconds, run `VD4Ja8Hnod3BBwOBV`). Same tab, one book moved to `read`, one new book. Summary: 1 appended, 1 updated, 1 unchanged. You pay for the 2 rows written; the unchanged row is free.

```json
{
  "spreadsheetId": "<your sheet URL>",
  "sheetName": "readme-books",
  "mode": "upsert",
  "keyFields": ["url"],
  "rows": [
    {
      "url": "https://www.gutenberg.org/ebooks/1342",
      "title": "Pride and Prejudice",
      "author": { "name": "Jane Austen", "born": 1775 },
      "shelf": "read"
    },
    {
      "url": "https://www.gutenberg.org/ebooks/84",
      "title": "Frankenstein",
      "author": { "name": "Mary Shelley", "born": 1797 },
      "shelf": "to-read"
    },
    {
      "url": "https://www.gutenberg.org/ebooks/11",
      "title": "Alice's Adventures in Wonderland",
      "author": { "name": "Lewis Carroll", "born": 1832 },
      "shelf": "to-read"
    }
  ]
}
```

**3. Export another Actor's dataset, chosen columns only** (3.1 seconds, run `Vt4za9nSTD1v0337N`). The dataset came from a [media-transcriber](https://apify.com/brasspage/media-transcriber) run on a public NASA video. Replace mode wrote the 6 named columns to `readme-transcripts`. Use the dataset ID from your own run's **Storage** tab, or `{{resource.defaultDatasetId}}` in an integration.

```json
{
  "spreadsheetId": "<your sheet URL>",
  "sheetName": "readme-transcripts",
  "mode": "replace",
  "datasetId": "<dataset ID>",
  "columns": [
    "mediaUrl",
    "durationSec",
    "language",
    "wordCount",
    "text",
    "srtUrl"
  ]
}
```

Run it from the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/brasspage~google-sheets-sync/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"spreadsheetId":"1AbCdEf...","mode":"append","rows":[{"name":"A","price":10}]}'
```

### Input fields

| Field                                                     | What it does                                                                                              |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `spreadsheetId`                                           | The spreadsheet URL or ID.                                                                                |
| `sheetName`                                               | The tab name. Empty = the first tab. A missing tab is created.                                            |
| `mode`                                                    | `append` (default), `replace`, or `upsert`.                                                               |
| `keyFields`                                               | Field names that identify a row, for example `["url"]` or `["store", "sku"]`. Upsert needs them.          |
| `serviceAccountJson`                                      | Optional: your own service-account key file (secret). Empty = the bot login.                              |
| `datasetId`                                               | The dataset to export.                                                                                    |
| `rows`                                                    | Raw JSON: an array of objects.                                                                            |
| `columns`                                                 | Pick and order the fields to write. Empty = all fields.                                                   |
| `flattenNested`                                           | `true` (default): nested fields become `a/b` columns. `false`: nested values are written as JSON text.    |
| `valueInputOption`                                        | `RAW` (default) writes values as they are. `USER_ENTERED` lets Sheets parse dates, numbers, and formulas. |
| `oauthClientId`, `oauthClientSecret`, `oauthRefreshToken` | Optional OAuth login instead of a service account (see the FAQ).                                          |

If you give both, `rows` wins over `datasetId`.

### Modes

**append** adds the rows after the last row of the tab. The first run writes the header row. If you also set `keyFields`, rows whose key is already in the sheet are skipped (dedupe).

**replace** writes the header and all rows from the top of the tab, then clears the old rows and columns below and to the right. The tab is never empty during the run. If the source is empty, the tab is **not** cleared.

**upsert** finds rows by `keyFields`. If a key is already in the sheet, only the cells that changed are written. If a key is new, the row is added at the end. Columns you added by hand (for example "Notes" or "Status") and formulas in them stay as they are. If the same key appears more than once in the input, the items are merged and the last value wins.

In all modes, new fields become new columns at the end of the header. Existing columns keep their order.

### Output example

The Actor writes one summary item to its dataset (and to the `OUTPUT` record):

```json
{
  "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/1AbC.../edit#gid=0",
  "spreadsheetTitle": "Price tracker",
  "sheetName": "Products",
  "mode": "upsert",
  "source": "dataset aBcD1234eFgH5678i",
  "rowsRead": 1200,
  "rowsAppended": 35,
  "rowsUpdated": 112,
  "rowsUnchanged": 1053,
  "rowsSkippedDuplicate": 0,
  "rowsSkippedNoKey": 0,
  "rowsWritten": 147,
  "columns": ["url", "title", "price/value", "price/currency", "inStock"],
  "durationMs": 4210,
  "errors": []
}
```

`errors` lists warnings, for example long text that was cut. A real error fails the run, and the run's status message says what to fix.

### Pricing

Pay per event. You pay only for what is written:

| Event       | Price            |
| ----------- | ---------------- |
| Actor start | $0.00005 per run |
| Row written | $0.0001 per row  |

Examples: 100 new rows = $0.01. A daily upsert of 2,000 products where 150 changed = $0.015. Rows that did not change, duplicates that were skipped, and the header row are free. A run with the empty default input writes no rows and costs only the $0.00005 start. Set a maximum cost per run in the run options to cap the cost: the Actor writes only as many rows as that limit allows and tells you.

### Limits

- Google Sheets allows 10,000,000 cells per spreadsheet, 18,278 columns per tab, and 50,000 characters per cell.
- Google allows about 60 write requests per minute per service account. The Actor writes up to about 2 MB per request, so this is rarely reached. When it is, the Actor waits and retries.
- The Actor reads the dataset and the current tab into memory. The default is 512 MB. If a run on a very large sheet runs out of memory, set the run memory to 1 to 2 GB.
- In `append` and `upsert` modes, row 1 of the tab is the header.

### FAQ

**Why a bot email instead of "Sign in with Google"?**
The bot is a Google service account: a robot Google user that can open only the sheets you share with it. Its key does not expire, so schedules and integrations keep working. OAuth tokens for apps in "Testing" status expire after 7 days, which is the most common reason Google Sheets exports stop working.

**Is the bot login safe?**
The bot can open only the sheets you share with it, and you can remove it from **Share** at any time. Its key is kept in Apify secret storage and never shown in logs. Before each write, the bot checks that the sheet's `brasspage_owner` cell holds the Apify user ID of the account that runs it, and refuses any other account. If you use your own key instead, Apify stores it encrypted and does not show it in logs.

**Can I use OAuth instead?**
Yes. Create an OAuth client (type "Desktop app") in your own Google Cloud project, get a refresh token with the scope `https://www.googleapis.com/auth/spreadsheets` (for example with the [OAuth 2.0 Playground](https://developers.google.com/oauthplayground) and "Use your own OAuth credentials"), and fill in `oauthClientId`, `oauthClientSecret`, and `oauthRefreshToken`. Publish the OAuth app to "In production", or the token expires after 7 days.

**Can I write to an Excel file in Google Drive?**
No. Open the file and use **File > Save as Google Sheets**, then use the new spreadsheet.

**How do I write nested data?**
Leave `flattenNested` on. Use `columns` to pick only the fields you need, for example `["title", "price/value", "images/0"]`.

**Can an AI agent use it?**
Yes. To give an AI agent this one tool, connect it to the Apify MCP server at `https://mcp.apify.com?tools=brasspage/google-sheets-sync`. The agent passes `rows` and the sheet, and gets the sync summary back.

**Does it read data from Google Sheets?**
No. This Actor only writes to Google Sheets.

### Troubleshooting

| Message starts with                                   | Fix                                                                                               |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `AUTH: No access to the spreadsheet`                  | Share the sheet with the email in the message (the bot, or your key's `client_email`) as Editor.  |
| `AUTH: The shared Google login writes only to sheets` | Add the `brasspage_owner` named range with your Apify user ID (see Setup).                        |
| `AUTH: The named range brasspage_owner`               | Make that cell hold only your Apify user ID, nothing else.                                        |
| `AUTH: The Google Sheets API is turned off`           | Open the link in the message and click **Enable**. Wait 1 to 2 minutes.                           |
| `AUTH: "serviceAccountJson" is not valid JSON`        | Paste the whole key file, from the first `{` to the last `}`.                                     |
| `AUTH: This JSON is an OAuth client file`             | You pasted the wrong file. Create a key under **Service Accounts > Keys**.                        |
| `AUTH: Google rejected the service-account key`       | The key was deleted. Create a new key and paste it.                                               |
| `AUTH: The OAuth refresh token is expired or revoked` | Make a new refresh token, and publish your OAuth app to "In production".                          |
| `Spreadsheet "..." was not found`                     | Check the ID in the URL, and share the sheet with the bot email (or your service account).        |
| `This file is an Excel file`                          | Use **File > Save as Google Sheets**.                                                             |
| `Key field "x" is not in the data`                    | Check the spelling in `keyFields`. Nested fields use a slash: `product/id`.                       |
| `This run cannot read dataset`                        | Put the dataset ID into **Dataset ID**. In an integration, use `{{resource.defaultDatasetId}}`.   |
| `No data source`                                      | Set `datasetId` or `rows`. In an integration, set `datasetId` to `{{resource.defaultDatasetId}}`. |

### Issues and support

Found a bug or need a feature? Open an issue in the Actor's **Issues** tab, with the run URL if you have one. We check it every day and reply there.

### Related Actors

Other tools from the same developer, with the same pay-per-result pricing:

- [ATS Jobs API](https://apify.com/brasspage/careers-site-jobs-api): every open job from Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee and Personio boards, with new-job alerts.
- [Audio & Podcast Transcription](https://apify.com/brasspage/media-transcriber): podcast episodes and audio or video files to text, SRT and VTT for $0.004 per minute.
- [Website Contacts & Tech Stack](https://apify.com/brasspage/website-contact-tech-audit): public emails, phones, social links, CMS and pixels for a list of websites.
- [PDF to Markdown OCR](https://apify.com/brasspage/pdf-ocr-to-markdown): scanned PDFs and images to clean Markdown with tables, for $0.006 per page.

# Changelog

This Actor's version history is a separate document: https://apify.com/brasspage/google-sheets-sync/changelog.md

# Actor input Schema

## `spreadsheetId` (type: `string`):

The Google Sheet to write to. Paste the full URL or only the ID (the part between /d/ and /edit). In Google Sheets click Share and add sheets-sync-test@brasspage-tools.iam.gserviceaccount.com as Editor (or the email of your own key, if you paste one below). With the shared login, the sheet also needs your Apify user ID in a cell named brasspage_owner (Data > Named ranges).

## `sheetName` (type: `string`):

The tab to write to. If the tab does not exist, the Actor creates it. Empty = the first tab.

## `mode` (type: `string`):

append adds new rows. replace overwrites the tab. upsert updates the rows whose key columns match and adds the others (needs Key columns).

## `keyFields` (type: `array`):

One or more field names that identify a row, for example "url" or "sku". Upsert needs them. In append mode they skip rows whose key is already in the sheet. Nested fields use a slash, for example "product/id".

## `serviceAccountJson` (type: `string`):

Optional. Paste the whole JSON key file of your own Google Cloud service account, then share the spreadsheet with its "client_email" as Editor. The value is stored encrypted.

## `oauthClientId` (type: `string`):

Only if you do not use a service account. Client ID of your own Google OAuth client.

## `oauthClientSecret` (type: `string`):

Client secret of the same OAuth client.

## `oauthRefreshToken` (type: `string`):

Refresh token with the scope https://www.googleapis.com/auth/spreadsheets. Tokens of OAuth apps in "Testing" status expire after 7 days.

## `datasetId` (type: `string`):

The Apify dataset to export, for example the default dataset of a scraper run (Storage > Datasets, or the run's Storage tab). In an integration, set it to {{resource.defaultDatasetId}}.

## `rows` (type: `array`):

An array of objects to write, for example \[{"name": "A", "price": 10}]. Use this when the data does not come from a dataset.

## `columns` (type: `array`):

Pick and order the fields to write. Empty = all fields. Nested fields use a slash, for example "price/value" or "images/0".

## `flattenNested` (type: `boolean`):

On: {"price": {"value": 5}} becomes a column "price/value". Off: nested objects are written as JSON text in one cell.

## `valueInputOption` (type: `string`):

RAW keeps text as text, so "00123" keeps its zeros and a value that starts with "=" is not run as a formula. USER_ENTERED makes Sheets parse values as if you typed them.

## Actor input object example

```json
{
  "spreadsheetId": "https://docs.google.com/spreadsheets/d/1AbCdEfGhIjKlMnOpQrStUvWxYz/edit",
  "sheetName": "Products",
  "mode": "append",
  "keyFields": [
    "url"
  ],
  "oauthClientId": "123456789012-abc123def456.apps.googleusercontent.com",
  "datasetId": "aBcD1234eFgH5678i",
  "rows": [
    {
      "name": "A",
      "price": 10
    }
  ],
  "columns": [
    "url",
    "title",
    "price/value",
    "price/currency"
  ],
  "flattenNested": true,
  "valueInputOption": "RAW"
}
```

# Actor output Schema

## `summary` (type: `string`):

One item per run: spreadsheet URL, sheet name, mode, rows read, appended, updated, unchanged, duplicates skipped, rows written (billed), duration and warnings.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("brasspage/google-sheets-sync").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("brasspage/google-sheets-sync").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call brasspage/google-sheets-sync --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,brasspage/google-sheets-sync"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/l1AJCaPr8nLnAsHIM/builds/tAbjaFLcO1U16kosd/openapi.json
