Dataset to Google Sheets — No OAuth avatar

Dataset to Google Sheets — No OAuth

Pricing

from $5.00 / 1,000 sheet updateds

Go to Apify Store
Dataset to Google Sheets — No OAuth

Dataset to Google Sheets — No OAuth

Export any Apify dataset to Google Sheets: append or replace rows after every run, or straight from an AI agent over MCP. Share the sheet once with the Actor's e-mail; no OAuth, no tokens that expire.

Pricing

from $5.00 / 1,000 sheet updateds

Rating

0.0

(0)

Developer

Duffy Radmilović

Duffy Radmilović

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

2

Monthly active users

8 days ago

Last modified

Share

Send any Apify dataset into your Google Sheet: append new rows, skip duplicates, or replace the tab. There's no Google login and no OAuth token. Share your sheet once with the Actor's e-mail and it keeps working. No token to expire, no invalid_grant, no reconnecting accounts.

That also makes it one of the few ways an AI agent can write to Google Sheets: agents can't click through an OAuth consent screen, but they can call this Actor over Apify's MCP server.

Why this exists

The usual way to get scraped data into Google Sheets is to connect your Google account with OAuth. Those tokens expire, get revoked, or get tied to the wrong account, and scheduled runs start failing.

This Actor uses a Google service account instead. You give it access the same way you'd give a colleague access: Share → add an e-mail → Editor. It can only touch spreadsheets you shared with it.

Setup (one minute, once per spreadsheet)

  1. Create or open a Google Sheet.

  2. Click Share and add this e-mail as Editor:

    sheets-bot@helena-sheets.iam.gserviceaccount.com

    If Google says the address has no Google account, share anyway. That's normal for service accounts.

  3. Copy the sheet link into the Google Sheet link field and pick a dataset.

If you forget step 2, the run tells you exactly that, with the e-mail to add. It charges nothing beyond Apify's minimal start fee.

Use it after another Actor (the main use case)

Run a scraper on a schedule and have every run land in the same sheet automatically:

  1. Open the scraper (or its task) in Apify Console → Integrations → connect an Actor → choose Dataset to Google Sheets — No OAuth.

  2. In the integration input set:

    {
    "spreadsheetUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
    "sheetName": "Results",
    "mode": "append",
    "dedupeBy": "url",
    "datasetId": "{{resource.defaultDatasetId}}"
    }
  3. Done. Each successful scraper run appends only rows whose url isn't in the tab yet.

Important: the dataset has to arrive through the datasetId field. Apify only grants this Actor read access to datasets passed that way.

Tested end to end: a scraper run followed by this Actor wrote 57 rows. The next identical run wrote 0 and skipped 57 duplicates.

Example: competitor Shopify prices in your sheet every morning

Shopify Price & Stock Change Monitor + this Actor = a daily log of competitors' price drops, restocks and new products in Google Sheets, for about $1–1.50 a month per store. Connect it as above and add a readable column order:

{
"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
"sheetName": "Price changes",
"mode": "append",
"datasetId": "{{resource.defaultDatasetId}}",
"columns": ["detectedAt", "shopName", "changeType", "title", "variantTitle",
"priceBefore", "priceAfter", "priceChangePct", "availableAfter", "productUrl", "message"]
}

Leave dedupeBy empty here: the same product can change again tomorrow, and that is a new row you want.

Use it from an AI agent (MCP)

Claude, ChatGPT, Cursor and other MCP clients can call this Actor as a tool through Apify's MCP server. The agent passes the rows directly, no dataset needed:

  1. Share your sheet with sheets-bot@helena-sheets.iam.gserviceaccount.com as Editor (once, by a human).
  2. Add the Actor to your MCP client: https://mcp.apify.com/?tools=duffy1990/dataset-to-google-sheets
  3. Ask the agent something like "put these results into my sheet, skip ones already there by url".

The agent calls the tool with:

{
"spreadsheetUrl": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit",
"sheetName": "Results",
"rows": [
{ "url": "https://example.com/a", "price": 10 },
{ "url": "https://example.com/b", "price": 12 }
],
"mode": "append",
"dedupeBy": "url"
}

and gets back a short status: Wrote 2 rows to "Results". If the sheet isn't shared, the answer says so and names the e-mail to add, so the agent can tell you what to do instead of guessing.

Tested through the Apify MCP server: the call above wrote 2 rows in about 6 seconds.

Input

fielddefaultmeaning
spreadsheetUrl(required)Link of the Google Sheet, or its ID.
sheetNameSheet1Tab to write into. Created if missing.
datasetId—Dataset to copy. In integrations use {{resource.defaultDatasetId}}.
rows—Alternative to a dataset: a JSON array of objects.
modeappendappend adds rows below existing ones. replace clears the tab first.
dedupeBy—Field name. In append mode, rows whose value is already in that column are skipped.
columnsallWhich fields to write, in this order. Nested fields use dots: price.amount.
maxRows0Safety brake. 0 = no limit.

How data is laid out

  • Nested objects are flattened: {"price": {"amount": 7}} → column price.amount.
  • Lists of simple values become one cell: ["a","b"] → a, b. Lists of objects become a JSON string.
  • The header row only ever grows. Existing columns keep their position; new fields get new columns on the right. Your formulas and formatting to the left stay where they are.
  • Values are written as they are (RAW). Numbers stay numbers and text stays text. A product code like 00123 isn't turned into 123, and a date-like string isn't reinterpreted.

Output

One row per run in the default dataset:

{
"status": "OK",
"spreadsheetId": "17gw...nxU",
"sheetName": "Shopify",
"mode": "append",
"rowsIn": 57,
"rowsWritten": 0,
"rowsSkippedDuplicate": 57,
"columns": ["shopDomain", "productId", "variantId", "title", "price"],
"sheetUrl": "https://docs.google.com/spreadsheets/d/17gw...nxU/edit#gid=...",
"message": "Wrote 0 rows to \"Shopify\", skipped 57 duplicates.",
"finishedAt": "2026-09-24T19:16:10.000Z"
}

Problems come back as a row with a clear status, not just a log line:

statusmeaning
NOT_SHAREDThe sheet isn't shared with the Actor's e-mail. The message says which e-mail to add.
SPREADSHEET_NOT_FOUNDThe link doesn't point to an existing sheet.
DATASET_NOT_FOUNDThe dataset doesn't exist.
DATASET_NOT_ACCESSIBLEThe dataset wasn't passed through the datasetId field (see above).
EMPTY_DATASETNothing to write. Nothing charged.
BAD_INPUTMissing or invalid input. The message says which field.
TOO_LARGEWriting would pass Google's 10 million cells per spreadsheet. Checked before writing.
RATE_LIMITEDGoogle kept refusing after several retries with back-off.
CHARGE_LIMIT_REACHEDYour maximum cost per run is too low to write anything.

Pricing

Pay per event:

eventprice
Sheet updated (once per successful run)$0.005
Rows written, per started 1,000 rows$0.02
  • 500 new rows a day → $0.025 a day, about $0.75 a month.
  • One-off export of 12,000 rows → 12 × $0.02 + $0.005 = $0.245.
  • A scheduled run with no new rows (all duplicates) still costs $0.005, because the sheet was checked.
  • Failed runs (not shared, bad input, empty dataset) are not charged.

If you set a maximum cost per run, the Actor writes only as many rows as that limit covers, then says so in the result.

Limitations — please read

  • It can't create spreadsheets. Service accounts have no Drive storage of their own, so the Actor only writes into a sheet you created and shared.
  • Google's limit is 10 million cells per spreadsheet, counting every tab. For very large exports use a new spreadsheet or pick fewer columns.
  • One cell holds at most 50,000 characters. Longer values are cut off with ….
  • Google allows about 60 write requests per minute for this service account. The Actor writes up to 5,000 rows per request and retries with back-off, so normal use never gets close. Very heavy parallel use may be slowed down.
  • dedupeBy checks one column. It compares values as text.
  • replace clears values, not formatting.

Privacy

  • The Actor can open only spreadsheets you explicitly shared with its e-mail. Share a sheet made for this purpose, not your whole Drive.
  • It reads the spreadsheet's tab names and sizes, plus the header row and the dedupeBy column of the tab it writes to. It reads no other cell contents.
  • Remove access at any time: Share → remove the e-mail.
  • The Actor stores no copy of your data outside your Apify run.

FAQ

Why not just use Apify's Google Drive integration? That uploads a file (CSV/JSON) into a Drive folder. This Actor writes rows into a live Google Sheet, appends, and skips duplicates.

Can I write several scrapers into one spreadsheet? Yes, use a different sheetName for each.

Does it work with shared drives / Google Workspace? Yes, if your organisation allows sharing with outside e-mails. Some companies block it. Then the run returns NOT_SHARED.

What if Google is down or slow? Requests are retried with exponential back-off. Rows already written stay written and are reported in the result.