# Public Google Sheets to JSON Data Scraper (`zinin/public-google-sheets-to-json`) Actor

Convert public Google Sheets CSV tabs into JSON with preserved text, headers, row numbers, optional types and content hashes. Empty and partial tabs are free.

- **URL**: https://apify.com/zinin/public-google-sheets-to-json.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 tabs

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Public Google Sheets to JSON Data Scraper

Convert a public Google Sheets tab into a structured JSON snapshot while preserving text, column names and source row numbers. Each complete nonempty tab is one result, with its rows nested inside; empty tabs, limits and access errors have separate free status rows.

**Evidence status (2026-09-06).** The actor was built on the Apify platform (build ndhHaXiZGFqlgN85X), its pay-per-event tariff was read back from the platform, and three private acceptance runs on Apify infrastructure produced the rows shown in R6: a complete three-row tab, a header-only tab and a tab capped by maxRows. The source tabs are the project's own synthetic control sheets, not customer data.

### R1. What you get

- One JSON object per public tab, with ordered headers, keyed data rows and CSV record numbers.
- Text-preserving imports by default, including SKUs with leading zeros, commas and multiline cells.
- Optional conservative conversion of simple decimal numbers and lowercase booleans.
- A hash of the downloaded CSV bytes and the final source URL for snapshot provenance.
- Explicit clean absence, access failures and incomplete snapshots without selling them as complete tabs.
- Apify dataset exports and an OUTPUT summary for scheduled imports, API clients and downstream workflows.

The result is a tab snapshot rather than one paid result per cell or data row. A tab containing three records and one containing several thousand records use the same result unit when both fit the documented bounds. The rowCount inside a snapshot counts data records; resultCount on its outer envelope is one for a complete nonempty tab.

### R2. Who uses it

Data operations teams can import an owner-maintained lookup table into an application without making every consumer implement Google CSV redirects and parsing. A catalog operator can retain codes as strings while checking a tab's content hash between imports. An automation builder can route a header-only sheet to a review step instead of replacing a populated destination with an unexplained empty object.

The person operating the run needs a public CSV export or publish URL and permission to process the sheet's content. No Google account, browser session, cookie or OAuth token is accepted by this Actor. Public link sharing is a source-owner decision: review the actual tab contents before publishing them. A spreadsheet that contains confidential customer or employee data should not be made public just to use this converter.

A spreadsheet editor and an ingestion tool solve different tasks. Prepare formulas, filtering and formatting in the source sheet, then export a public tab for this Actor to read. This Actor does not edit cells, calculate spreadsheet formulas, inspect revision history or select tabs by human-readable tab name. Each supplied CSV URL identifies the export route and optional numeric gid to use.

### R3. How to run

1. Open the supplied prefill in the Input tab for a small synthetic smoke test, or provide your own permitted public CSV URLs.
2. Set headerRow to the CSV record that contains your column names; keep 1 when the first row is the header.
3. Choose a maximum number of data rows that covers the expected tab and keep inferTypes false when identifiers must remain text.
4. Start the run. Inspect the outer status and partial fields before opening the nested rows array.
5. Download JSON for a faithful snapshot, then flatten the data rows only if your destination requires a flat table.

For an ordinary public share, use `https://docs.google.com/spreadsheets/d/<id>/export?format=csv` and optionally append `&gid=N`. Without gid, Google selects the first sheet. For a sheet published to the web, use `https://docs.google.com/spreadsheets/d/e/<pubid>/pub?output=csv` with optional `&gid=N&single=true`. A normal browser editing link is not an input URL. Neither is a gviz query link: the actor derives the direct gviz CSV route from the export link itself (see R9), so you keep using the documented export form.

Test a public URL without an authenticated browser session. Seeing a sheet while signed into its owner account does not establish that the CSV is public. If Google returns a login page, change sharing or publication through Google's own interface and verify the public route again. The Actor reports not\_public and does not sign in or attempt an alternate extraction route.

### R4. Pricing

The tariff is **$0 per start + $0.005 per complete nonempty tab**, read back from the platform on 2026-09-06: the only charged event is `result-found` ($0.005, model PAY\_PER\_EVENT); there is no start event, so a run start costs nothing. One tab snapshot contains all the rows that fit the contract. One, three and five complete tabs therefore cost $0.005, $0.015 and $0.025 in result events at that tariff. A separate run can bill the same tab again even if the source has not changed.

A valid header-only or entirely empty CSV is a free not\_found result. Login HTML, wrong MIME, malformed CSV, a source failure and a tab that exceeds a row, column, byte or output limit produce no result-found event. A partially extracted tab may retain a prefix for inspection, but the entire tab unit is free. Increasing maxRows does not create a charge per nested row.

Automatic dataset-item events must be absent or priced at zero. The Actor fails closed when platform pricing cannot establish this, so an explanatory write cannot silently become a paid dataset item. A zero maximum total charge is preserved even though the SDK normally treats some zero values as an unlimited cap. With that cap, complete paid snapshots are withheld while legitimate free status rows can still be delivered when pricing is safe.

Platform evidence for the tariff above (charged event counts of the accepted runs): complete tab gJcBO8p9yS31j2Hmn `result-found: 1`; header-only tab 7YERQrAIMs0BKxdwM `0`; tab capped by maxRows TXvuK3NCIgDQ6KdlH `0`; no `apify-actor-start` charge in any run. The public Console price governs the released product; account credits, plan subscriptions and destination automation charges are outside this per-tab calculation.

### R5. Input contract

| Field | Default | Contract |
|---|---|---|
| publishedCsvUrls | \["https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"] | 1–5 public Google Sheets export?format=csv or published /d/e/id/pub?output=csv URLs, optional gid. No gviz, credentials or private sheets. |
| headerRow | 1 | 1-based CSV record containing column names; records before it are skipped. |
| maxRows | 10000 | 1–10000 nonempty data records. A tab exceeding the cap returns a free partial result. |
| inferTypes | false | Opt in to lowercase true/false and conservative decimal numbers. Leading zeros, dates and unsafe integers stay strings. |

```json
{
  "publishedCsvUrls": [
    "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"
  ],
  "headerRow": 1,
  "maxRows": 10000,
  "inferTypes": false
}
```

The runtime accepts only these four input properties. It validates array size before deduplication, so six repeated URLs still exceed the five-entry input limit. Identical URL strings are processed once per run. Semantically equivalent URLs written in a different query order are not guaranteed to deduplicate: keep a canonical URL list in your own integration when that matters.

All URLs are at most 2,048 characters and use HTTPS on docs.google.com. Credentials, fragments, explicit nonstandard ports and unknown or repeated query parameters are rejected. gid must contain only decimal digits. The optional single parameter on the publish route must be true. Export URLs use format=csv and publish URLs use output=csv; those parameter names are not interchangeable.

The gviz route is deliberately excluded because its inferred column types can discard text mixed into otherwise numeric columns. inferTypes operates only after receiving the CSV bytes; it cannot repair values already lost by another Google endpoint. Do not convert an accepted export URL to gviz in an upstream workflow.

headerRow is a one-based CSV record index from 1 through 20. Quoted line breaks belong to the same record and do not increase this index. Records before the header are ignored as data, but still pass safety limits. maxRows counts nonempty records after the header and is between 1 and 10,000. Five tabs times 10,000 rows is the largest input product; each tab must also fit the independent byte, column and output limits.

A missing property uses the documented default in local runtime calls. The platform form marks publishedCsvUrls required and supplies a prefill. Explicit null, a numeric string where an integer is expected, or the string "false" in place of a boolean is invalid input. Correct the input once instead of relying on coercion that could change data interpretation.

### R6. Accepted platform run examples (2026-09-06)

Input:

```json
{
  "publishedCsvUrls": [
    "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"
  ]
}
```

Accepted platform row (run gJcBO8p9yS31j2Hmn), complete tab, paid once:

```json
{
  "input": "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv",
  "sourceUrl": "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/gviz/tq?tqx=out:csv&headers=1",
  "found": true,
  "status": "ok",
  "resultCount": 1,
  "partial": false,
  "error": "",
  "warnings": [],
  "checkedAt": "2026-09-05T21:03:16.837Z",
  "schemaVersion": "1.0",
  "publishedUrl": "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv",
  "tabGid": null,
  "headers": [
    "sku",
    "name",
    "price_usd",
    "qty",
    "notes"
  ],
  "rows": [
    {
      "sku": "00042",
      "name": "Widget, blue",
      "price_usd": "12.5",
      "qty": "3",
      "notes": "leading zero sku\nmulti-line note"
    },
    {
      "sku": "00043",
      "name": "Gadget red",
      "price_usd": "7",
      "qty": "0",
      "notes": "plain"
    },
    {
      "sku": "A-100",
      "name": "Quoted \"name\"",
      "price_usd": "99.99",
      "qty": "12",
      "notes": ""
    }
  ],
  "rowNumbers": [
    2,
    3,
    4
  ],
  "rowCount": 3,
  "contentHash": "6ca86237fd7537898ed58eba821b1cbf2c2ada81b4c92f04ddb6e4951b109e7d",
  "types": {
    "sku": "string",
    "name": "string",
    "price_usd": "string",
    "qty": "string",
    "notes": "string"
  },
  "syntheticFixture": true,
  "redirectChain": []
}
```

Input:

```json
{
  "publishedCsvUrls": [
    "https://docs.google.com/spreadsheets/d/1lhqNwMysBBBwjks00uuM9ns486IkQlYnUY-ObT2zhwc/export?format=csv"
  ]
}
```

Accepted platform row (run 7YERQrAIMs0BKxdwM), header-only tab, free:

```json
{
  "input": "https://docs.google.com/spreadsheets/d/1lhqNwMysBBBwjks00uuM9ns486IkQlYnUY-ObT2zhwc/export?format=csv",
  "sourceUrl": "https://docs.google.com/spreadsheets/d/1lhqNwMysBBBwjks00uuM9ns486IkQlYnUY-ObT2zhwc/gviz/tq?tqx=out:csv&headers=1",
  "found": false,
  "status": "not_found",
  "resultCount": 0,
  "partial": false,
  "error": "",
  "warnings": [],
  "checkedAt": "2026-09-05T21:03:22.506Z",
  "schemaVersion": "1.0",
  "publishedUrl": "https://docs.google.com/spreadsheets/d/1lhqNwMysBBBwjks00uuM9ns486IkQlYnUY-ObT2zhwc/export?format=csv",
  "tabGid": null,
  "headers": [
    "sku",
    "name",
    "price_usd",
    "qty",
    "notes"
  ],
  "rows": [],
  "rowNumbers": [],
  "rowCount": 0,
  "contentHash": "4f9fb8cad1f9763c83fa7b70236e4ab3e97e63a929de540aef6e5f87640e68ce",
  "types": {
    "sku": "string",
    "name": "string",
    "price_usd": "string",
    "qty": "string",
    "notes": "string"
  },
  "syntheticFixture": true,
  "redirectChain": []
}
```

Input:

```json
{
  "publishedCsvUrls": [
    "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"
  ],
  "maxRows": 1
}
```

Accepted platform row (run TXvuK3NCIgDQ6KdlH), maxRows cap, free partial notice:

```json
{
  "input": "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv",
  "sourceUrl": "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/gviz/tq?tqx=out:csv&headers=1",
  "found": false,
  "status": "limit_exceeded",
  "resultCount": 0,
  "partial": true,
  "error": "limit: maxRows=1 reached",
  "warnings": [],
  "checkedAt": "2026-09-05T21:03:27.162Z",
  "schemaVersion": "1.0",
  "publishedUrl": "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv",
  "tabGid": null,
  "headers": [
    "sku",
    "name",
    "price_usd",
    "qty",
    "notes"
  ],
  "rows": [
    {
      "sku": "00042",
      "name": "Widget, blue",
      "price_usd": "12.5",
      "qty": "3",
      "notes": "leading zero sku\nmulti-line note"
    }
  ],
  "rowNumbers": [
    2
  ],
  "rowCount": 1,
  "contentHash": "6ca86237fd7537898ed58eba821b1cbf2c2ada81b4c92f04ddb6e4951b109e7d",
  "types": {
    "sku": "string",
    "name": "string",
    "price_usd": "string",
    "qty": "string",
    "notes": "string"
  },
  "syntheticFixture": true,
  "redirectChain": []
}
```

The rows above are unedited dataset items of the accepted platform runs executed on Apify infrastructure on 2026-09-06 with the deployed build and the live tariff. For an export link, `sourceUrl` is the derived docs.google.com gviz CSV route that was actually fetched and `redirectChain` is empty because that route answers directly; keep the stable input URL as the source identifier.

The happy fixture contains three data records. Its first SKU remains "00042", its first product name includes a comma, and its note contains a line break. The header-only fixture has the same five headers and zero data records. The capped fixture retains one data row but returns found=false, resultCount=0 and partial=true because it did not complete the tab.

The fixture's syntheticFixture flag identifies controlled data owned by the project. This flag is not an estimate of whether arbitrary buyer data is synthetic; only the two known fixture URLs receive it. An ordinary public sheet does not become a real commercial canary merely because that flag is false. Release acceptance separately verifies a permitted source with meaningful production use.

A not\_public response is an error with a nonempty explanation. It must not be interpreted like the clean empty fixture, whose error is exactly an empty string. A 429 response remains rate\_limited even if Google's quota page happens to be HTML. The Actor retries transient requests within its deadline, then reports the remaining failure without charging a tab result.

### R7. Field dictionary and CSV semantics

| Field | Meaning and integration rule |
|---|---|
| input | The supplied stable public CSV URL; use it to match an outcome to your requested work. |
| publishedUrl | Original export or publish URL on parsed snapshots, regardless of the delivery host. |
| tabGid | Numeric gid as a string when present, otherwise null. Null means the default sheet was requested, not that its actual gid is zero. |
| sourceUrl | Final validated response URL for a parsed CSV; access failures can retain the original input URL. |
| headers | Ordered collision-free output keys derived from the selected header record. |
| rows | Array of objects keyed by headers. Present on parsed snapshots, including a retained prefix on some limits. |
| rowNumbers | One-based CSV record indexes corresponding to rows. These are not physical newline counts or permanent business identifiers. |
| rowCount | Number of retained data records, including a partial prefix; inspect status before treating it as the full tab size. |
| contentHash | SHA-256 of the downloaded bytes when available, before header processing or type inference. |
| types | Observed JavaScript type per output column: string, number, boolean or mixed. Header-only columns default to string. |
| found | True only for a complete, nonempty tab unit. False does not by itself mean the source contains no records. |
| resultCount | One for a complete tab; zero for clean absence, a limit or an error. |
| partial | Indicates incomplete extraction or scope. A retained prefix is still incomplete when its rowCount is positive. |
| error | Empty for a normal complete result and clean absence; nonempty for errors and documented limits. |
| warnings | Array reserved for nonfatal explanatory notices; consumers must tolerate an empty array. |
| checkedAt | UTC extraction observation timestamp, not Google's cell modification time. |
| schemaVersion | Version of this output contract, currently 1.0. |
| redirectChain | Validated or refused redirect evidence (empty for the direct gviz route of an export link), useful when Google changes a delivery host or returns a login route. |

Header values are trimmed; cell values are not. An empty header becomes column\_1, column\_2 and so on using its original position. Duplicate keys receive suffixes such as \_2 until every key is unique. Collisions are checked against keys already produced, so a source header that already contains a suffix does not overwrite another column. The headers array is the authoritative mapping for the resulting objects.

A dangerous-looking header such as **proto** remains an inert data key. The converter uses objects without a prototype while constructing rows, and no source value is evaluated as JavaScript. If your downstream application merges arbitrary keys into ordinary objects, retain its own key-safety controls. Safe extraction does not make unsafe destination code safe.

Short records are padded with empty strings through the header width. A data record wider than its header is invalid\_csv because silently dropping extra fields would corrupt column alignment. An all-empty record after the header is skipped. A cell containing spaces is not an empty string and therefore keeps its record. This preserves source text even when a visually blank sheet has meaningful whitespace.

UTF-8 is required and invalid byte sequences are refused. A UTF-8 BOM is supported. Commas separate columns, double quotes protect commas and newlines, and doubled double quotes represent a literal quote inside a quoted field. A semicolon-delimited export is not autodetected as another dialect: Google's CSV route is the documented source. Unterminated quotes and invalid record structure produce a free invalid\_csv outcome.

With inferTypes=false, values such as 0, false and 2026-09-05 remain strings. With inferTypes=true, only lowercase true and false become booleans, and conservative decimal syntax can become a finite safe number. Leading zeros, date-like strings, scientific notation, unsafe integers and negative zero remain text. Empty cells remain empty strings rather than null. JavaScript numeric conversion is unsuitable for exact decimal money arithmetic; keep inference off when exact decimal text is important.

The types object describes values retained in this snapshot. It is not a database schema for future runs and does not inspect missing records beyond a cap. A column can be mixed when one cell is convertible and another remains text. If a destination requires a strict numeric column, apply an explicit validation policy and quarantine exceptions rather than replacing them with zero.

A hash change means the exported bytes changed. It does not reveal which row changed or prove a business change: line endings, quoting, source reordering and other byte-level differences can alter the hash. A stable hash under different inference settings does not imply identical JSON types, because the hash precedes conversion. For semantic comparisons, store the input settings and compare selected normalized data in your application.

### R8. Evidence and boundaries

Only the two documented Google CSV URL forms are accepted. An export link is fetched through `https://docs.google.com/spreadsheets/d/<id>/gviz/tq?tqx=out:csv&headers=1[&gid=N]`, which docs.google.com serves directly under its robots allowance for `/spreadsheet` paths with the first row as the first CSV line; the `/export` route itself redirects to googleusercontent delivery shards whose robots.txt differs by shard. Published links (`/d/e/<pubid>/pub?output=csv`) are fetched as given; redirects to googleusercontent.com subdomains remain permitted delivery hops. Every hop still passes URL policy, DNS address checks and connection pinning; a permitted hostname with a private DNS answer is blocked. The actor does not accept a googleusercontent URL as a starting CSV source.

robots.txt is fetched using the same safe transport for each contacted origin. A missing robots file is distinct from an unavailable or invalid robots response; the latter stops access. A disallowed path stops access. The approved Google route has a one-second crawl delay, matching the Actor's minimum spacing. An accounts.google.com redirect is recognized as an access failure and is not fetched as an authenticated login flow.

Limits are 10 MB CSV per tab, 50 columns per record, 1 MB per CSV record, approximately 5 MB serialized output per tab and a 240-second working deadline. The output accounting leaves space below the hard 5 MB envelope. Source requests have bounded timeouts and retries. Large data beyond these bounds should be split by the source owner into separate permitted tabs or another appropriate export workflow.

A document limit is a result, not a source outage. One limit-only input ends with an empty fatal summary. A mixed run containing a limit and a bad source also remains nonfatal at the run level while retaining the individual source error. When every requested tab actually fails, fatal is All tabs failed and the run is marked failed. A caller should inspect both row statuses and the summary instead of inferring completeness from the terminal state alone.

### R9. Decision routing

| Observation | Suggested next step | Evidence to retain |
|---|---|---|
| ok, found true, partial false | Validate headers and import the nested records. | Stable input URL, hash, checkedAt and run ID. |
| not\_found with empty error | Confirm the empty tab is expected before changing a destination. | Header list, headerRow setting and source URL. |
| not\_public | Ask the sheet owner to verify sharing and the public CSV route. | Status and a redacted redirect chain. |
| invalid\_mime or invalid\_csv | Inspect the permitted export response and correct the input/source format. | Response status, error and original URL. |
| limit\_exceeded | Split the source tab or adjust an allowed cap, then rerun only that scope. | Retained row count and the exact limit explanation. |
| rate\_limited or source\_error | Retry later after checking source availability; do not erase existing data. | Failure timestamp, summary and run ID. |
| budgetExhausted | Plan remaining inputs within a new spending cap. | Already delivered inputs and unprocessed count. |

Do not treat a successful partial snapshot as a replacement table. If your importer requires a complete source, quarantine the partial row and leave the previous destination version in place. If your use case permits a prefix, label that prefix in the destination and preserve the limit reason. This is an application decision; the free status itself does not grant permission to present the prefix as complete.

### R10. Commercial playbooks

#### Import a public product lookup

Maintain SKU, label and reference-price columns in a permitted public sheet. Keep inference off so zero-prefixed identifiers and decimal text remain exact. Validate that the returned headers match the destination mapping before loading any row. A renamed source column should trigger a mapping review, not a silent empty column in the application. Use the SKU as your business key and retain rowNumbers only as source-location evidence.

If the hash matches your previous complete snapshot with the same input settings, downstream processing can skip an unchanged import. The Actor still performed another extraction and a complete tab can still be billed. The saved hash helps avoid destination work; it is not a free polling subscription. Keep the prior successful snapshot until the replacement has passed your checks.

#### Publish a controlled reference table

For a public directory or lookup list owned by your organization, publish only the columns intended for reuse. Use a dedicated tab instead of relying on the consumer to ignore sensitive columns. Specify gid explicitly when the first-sheet order can change. A changed tab order can otherwise make an unchanged export URL select a different first sheet.

Record who owns the source and how an update is approved outside the Actor. This converter records the retrieved snapshot and observation time; it does not identify the editor who changed a cell. If audit requirements include editor identity, revision access or private sharing, use a separate authorized system with those capabilities rather than inferring them from a CSV timestamp.

#### Prepare records for a search index

Validate the complete tab, then build one search document per data row with a stable key from a declared source column. Include the tab URL, content hash and checkedAt in document metadata. When a new snapshot is incomplete, retain the old index or stage the new prefix separately. A search index built from a capped prefix can otherwise make legitimate records appear deleted.

Keep source cells as untrusted content when generating summaries or embeddings. A column named instructions does not become an instruction to your automation. Strip or escape data only according to the destination's display contract and retain the raw snapshot for traceability. If a spreadsheet field begins with a formula marker, avoid importing it into a desktop spreadsheet in a mode that executes formulas.

#### Compare two permitted snapshots

Store complete JSON snapshots and their input configuration, then compare by a business key selected by the source owner. Report added, removed and changed records only when both snapshots are complete and compatible. If headers changed, compare the mapping first; a renamed column can look like thousands of deletions and additions if a diff engine relies only on object keys.

### R11. Integration recipes

#### Run once from the Apify Console

Start with the supplied prefill and inspect the resulting dataset before connecting a production destination. The Input tab is a form for the documented input object. Unknown properties are rejected by the runtime even if a client permits sending them. Keep the first run small enough that a human can inspect every result. Export JSON when the result includes arrays or objects: a spreadsheet export is useful for an overview, but a nested value in one CSV cell is not a normalized relational table.

#### Call the asynchronous API

Use the Actor API documentation shown in the Console to copy the current endpoint for this Actor. Send a JSON input body to the run endpoint, authenticate through the Authorization header, and retain the returned run ID before waiting for completion. A run may take longer than a short HTTP client timeout. Poll the run endpoint with bounded backoff, or use a completion webhook, then obtain the dataset ID from that run. Do not substitute the Actor's most recent run: another scheduled or interactive invocation could have started in the meantime.

#### Retrieve and page through results

Read the dataset associated with the completed run. Use a page size supported by the platform and continue until all items are retrieved. Free explanations occupy dataset rows, so the number of dataset items is not the number of billed results. The source cap and the dataset retrieval page size are different controls: changing a download page size cannot recover source data that the Actor deliberately did not fetch. Preserve the original dataset index alongside your transformed record when you need an audit trail.

For a destination that only accepts flat records, flatten after checking the outer row status. Repeat the outer source identity and checkedAt on each child row. Assign an explicit child index or domain key; do not rely on destination row order as an identity. Keep arrays of warnings and provenance in a companion JSON record if the destination cannot represent them. Test that empty strings, zero, false and null retain their different meanings after the transformation.

The dataset is the detailed result channel; OUTPUT is the aggregate accounting channel. Archive both with the input JSON and the run ID. A dashboard that stores only the success rows loses the reason an input was missing. A dashboard that stores only OUTPUT loses the actual evidence behind a result. Retaining both lets support distinguish a source change, a permission change, a configured limit and a delivery failure without rerunning the source.

#### Use n8n

Create a small workflow with a manual trigger, a node that starts this Actor, a wait or polling step, a dataset read step and an IF or Switch node. Route complete results to the importer, clean absence to a review branch, and errors or limits to a separate operational queue. Avoid a workflow that maps every returned row directly into a production table. A free explanatory row intentionally does not have every field of a complete result.

In the import branch, retain sourceUrl, checkedAt, schemaVersion and the domain identity. Use an upsert key appropriate to your destination instead of blind insertion. Put a unique constraint on your own job ID plus result identity if your webhook handler can run more than once. If the destination rejects a row, retry the destination write from the saved dataset; there is usually no reason to start another source run and pay to retrieve evidence you already have.

Set the workflow timeout above the Actor's expected run duration and use a bounded polling schedule. A stopped or failed run must leave the workflow's waiting state. Record the run ID in failure notifications, but avoid embedding entire source records when they contain information your notification channel should not receive. Test the negative and partial branches with the examples in this manual before enabling a schedule.

#### Use Make or Zapier

Build the scenario around a completed run, then retrieve its dataset. An iterator over dataset items should be followed by a filter on the documented status and completeness fields. Name branches by their operational meaning: import, no current result, retry source later, and investigate configuration. Include a final count comparison so a scenario that silently drops a row cannot report a complete import.

If a run yields nested arrays, decide whether the destination expects one object per result or one object per child record. Expanding nested arrays can multiply automation tasks even though it does not create extra Actor result events. Estimate those destination operations separately from this Actor's tariff. The Actor's result price is not a quote for third-party workflow subscriptions, destination API usage or your own data storage.

Schedules belong to the automation or to Apify. Avoid enabling both for the same job unless you intentionally want two independent polling streams. Include the exact input configuration in your job description, because a copied scenario can keep an older cap or strategy without anyone noticing. After an input change, perform one manual run and inspect both a complete result and the new summary before resuming recurring execution.

#### Use a completion webhook

A webhook signals that a run reached a terminal state; it is not itself the complete dataset. Read the run ID from the event, resolve that run's dataset, and process it using an idempotent destination operation. Webhook systems can deliver the same event more than once. Keep a processed-event record and reject duplicate destination work without assuming that a second notification means the Actor ran twice.

Keep webhook verification and destination credentials in your receiving service. The Actor does not accept arbitrary callback URLs in its input and does not send your extracted data to a buyer-supplied endpoint. Configure platform webhooks through the platform's supported controls. This keeps source retrieval separate from your choice of destination and makes it possible to replay destination processing from a saved dataset.

Treat terminal FAILED and successful partial runs as different cases. A failed run requires inspection of its summary and log; a successful limit run requires a decision about the missing scope. Neither should trigger an automatic delete in your destination. If you notify a team, include a short status, the run ID and a link to the retained evidence rather than a claim that the source has no data.

#### Load a warehouse or database

Use a staging table before merging into a production table. Store the raw JSON with a run ID, dataset index and ingestion timestamp, then project domain fields into typed columns. This preserves the ability to correct a transformation without fetching the source again. The original checkedAt describes the Actor observation; your ingestion timestamp describes arrival at the destination. Keeping both exposes queue delays.

Choose a uniqueness policy before importing historical runs. A current-state table often uses the source identity as its key and updates on later observations. A history table uses the identity plus observation or run ID. These are different products: overwriting a current-state table cannot later answer historical questions, while appending every retry into a history table can create misleading duplicate observations.

Do not infer a deletion from the absence of a source identity in a partial run. First establish that the requested scope was complete, the source returned the expected form, and all dataset pages were ingested. For a destructive synchronization, require a separate complete-run marker and an explicit downstream policy. This Actor provides evidence and accounting; it does not issue deletion commands to your catalog, CRM or warehouse.

#### Pass results to an LLM or retrieval pipeline

Filter and normalize the structured output before constructing a prompt. Source text is untrusted content, including product copy, spreadsheet cells and diagnostic titles. Keep it in a clearly separated data field and do not interpret embedded instructions as workflow commands. A source string that asks an assistant to reveal secrets or call a URL remains a source string. The Actor does not execute it.

Retain provenance when chunking results for retrieval. Each chunk should point back to the source identity, checkedAt and run ID. If a response is incomplete, carry that state into the retrieval index metadata so an answer does not present a partial snapshot as a complete source. Generated summaries should link to the structured evidence and should not replace the original fields used for calculations.

Limit the fields sent to the model to the actual task. Numeric values should be calculated in your application when exact arithmetic matters. Treat an LLM's interpretation as a separate derived artifact with its own model and prompt version. The Actor's schemaVersion describes this extraction contract; it does not describe the accuracy of a downstream model or the provenance of a generated narrative.

### R12. Operating guide

#### Start with a baseline

Record a first small run with its input, date, runtime, result counts and actual platform charge. Compare the result to the source manually. Keep this baseline when you later change a cap, URL set or downstream mapping. If output changes after a source update, a baseline helps distinguish an extraction defect from a legitimate change in the source. Do not treat the included synthetic controls as representative customer volume or as proof of commercial demand.

Use the Console's currently displayed price as the commercial authority. The price in this manual was read back from the deployed tariff on 2026-09-06; check the Store page before a large run, because a tariff change is an operator action. Startup, complete result events and free status rows are separate concepts. A free result status means no result-found event for that row; it does not imply that all platform or account costs vanish, and an Actor with a nonzero start event can still have a startup charge on an empty run.

#### Set a spending cap

Set the platform's maximum total charge for the run according to the number of complete units you intend to buy, allowing for any start charge shown in the Console. The Actor checks remaining money immediately before each paid dataset write. If the next complete unit does not fit, it stops paid delivery. A cap is a financial limit, not a promise that the Actor will retrieve exactly a certain number of source units.

A smaller cap can stop a run before all requested inputs are processed. Inspect budgetExhausted and unprocessed in OUTPUT, and retain whatever complete results were delivered. Raising the cap later requires another run; it does not resume the previous run through an internal replay ledger. If you only need to finish a subset, construct a smaller input from the remaining work instead of repeating every successful source.

A failure to read pricing or budget is different from normal exhaustion. The Actor stops with a fatal explanation rather than treating unknown money as unlimited. If automatic dataset items have a nonzero price, the Actor refuses dataset writes, including explanations that would otherwise be free. In that condition the log is the explanation channel. Contact support with the run ID; repeatedly rerunning the same broken pricing configuration is not a remedy.

#### Understand delivery uncertainty

A paid result is sent using a linked dataset write and result event. If a remote write or charge operation fails, it can be impossible for the client to know whether the row landed. The Actor stops and records deliveryUncertain rather than retrying the same row automatically. This prevents a silent duplicate, but it also means that a failed run can require reconciliation against the platform's actual dataset and charged-event counts.

For a destination integration, a dataset row is evidence of a delivered record, while OUTPUT.paid records the SDK-confirmed count. Investigate any uncertainty instead of equating those quantities mechanically. If support needs a reproduction, provide the smallest input that demonstrates the issue and the original run ID. Avoid deleting the evidence or restarting multiple times before the original state has been inspected.

#### Plan schedules around source behavior

Choose a polling interval that reflects how often the source can usefully change and how soon your decision needs fresh evidence. A schedule does not make an upstream cache fresh. Repeated identical runs can be billed again and may not add useful information. Begin with manual or infrequent execution and increase frequency only after observing actual changes and operational value.

The implementation runs source work sequentially. This keeps request pressure and memory usage bounded and makes small runs easy to inspect. Increasing the number of URLs does not create parallel workers. The platform timeout is 300 seconds with a 240-second working deadline. The remaining time is reserved for delivery and summary work, but the platform can still interrupt a process for reasons outside the source request path.

There are at most two retries after an initial transient request failure. Only network failures, HTTP 429 and server errors qualify; a malformed successful body or ordinary client error is not repeatedly fetched. Retry-After is honored within the remaining working deadline. If the requested wait no longer fits, the Actor returns the appropriate free explanation and stops that source attempt. This is a bounded retry policy, not a guarantee that a busy upstream service becomes available.

#### Read the summary as a ledger

requested is the number of normalized source work units requested by the run. processed records units whose domain processing reached an output decision; its exact unit is explained in the product-specific contract. unprocessed records the remainder. delivered counts complete result rows confirmed by the delivery path. paid counts confirmed result events when running with monetized pricing; local development can deliver results with paid equal to zero.

free counts explanatory or clean absence rows written without result-found. failed counts free rows that describe actual errors, excluding documented limit\_exceeded results. A free row may still contain a partial snapshot for inspection. partial indicates incomplete source scope, output, budget coverage or a fatal interruption. fatal is an empty string for a normal complete run and for documented nonfatal limits; a nonempty string explains a run-level failure.

replaySafe is false because the Actor does not maintain a persistent ledger across runs. checkedAt in OUTPUT is the summary time and can be later than each row's checkedAt. Keep row timestamps for source comparisons. A summary is deliberately small and does not duplicate all the data. If writing the summary itself fails, the log and terminal run state become necessary evidence for troubleshooting.

#### Monitor useful signals

Track runs that produce complete results, clean absence, source errors, limits and budget exhaustion as separate categories. A rising number of free rows can indicate either correctly enforced limits or a failing source; the status tells you which. Alert on repeated source failures or missing configuration, and review recurring limits against your intended input size. Do not use dataset row count alone as a health metric.

Track source request duration and run duration separately if your observability stack supports them. Startup, retries, throttling and destination processing can all contribute to end-to-end delay. A fast source request does not prove that a whole automation completed quickly. Conversely, a slow upstream result does not imply a broken dataset importer. Preserve the run ID through each system so latency can be traced.

#### Change one control at a time

When investigating a failure, keep the source and other controls fixed while changing one relevant limit or option. Compare the status, extracted values and summary to the baseline. If a permission problem is suspected, have the source owner verify access through the documented public route. Do not add credentials to a URL or try a different unapproved route to make a free error disappear.

When a schema or source changes, run a small comparison before switching a scheduled destination. Verify identifiers, numeric units, missing-value behavior, nested arrays and the clean absence branch. A transformation that works for a happy result can still fail on a documented status row. Version your own mapping independently from the Actor input so you can roll back a destination change without reverting the source configuration.

#### Prepare a useful support report

Include the Actor name, run ID, input with sensitive values removed, expected behavior, observed status and the relevant row or summary fragment. State whether the problem reproduced once or repeatedly and whether the source changed recently. Include a source URL only if you can share it safely. Do not send API keys, platform tokens, cookies or private source documents to a public issue.

For numeric discrepancies, include the original structured value and its unit rather than only a screenshot of a formatted cell. For missing records, include the configured caps and completeness state. For a suspected billing discrepancy, retain the platform's actual event counts and the run memory setting. These details let support isolate the responsible layer without guessing from an incomplete screenshot.

### R13. FAQ

**Does this support private Sheets?** No. Access is limited to a public CSV route. Adding a Google API key, password, cookie or OAuth token to the input is not supported, and unknown fields are rejected. Private collaboration links should stay private.

**Why are numbers still strings?** Text preservation is the default. Spreadsheet identifiers, postal codes and decimal values can be damaged by automatic coercion. Set inferTypes=true only when its narrow conversion rules match the destination contract.

**Can I pass a tab name instead of gid?** No. The input URL selects the export. Copy its numeric gid when you need a specific tab, and have the owner verify that the intended tab is actually public. The Actor does not list other tabs.

**Why is rowCount positive but found false?** A limit can retain a useful prefix while the complete tab remains unavailable. found describes the complete billable tab unit, not whether any cell was parsed. Check partial and error before importing it.

**Are rowNumbers spreadsheet line numbers?** They are one-based CSV record indexes. A quoted multiline cell still belongs to one CSV record. Empty records that were skipped can leave gaps, and pre-header records still contribute to the record index.

**Will formulas run?** No. The Actor receives the values exported by Google and treats them as CSV text. It does not evaluate formulas or fetch external cell references. A downstream spreadsheet may interpret formula-looking text if imported carelessly, so use a literal-text import where appropriate.

**What happens if the source changes while downloading?** The result describes the response that Google returned. It is not a transaction across several tabs and cannot prove that five tabs were observed at the same logical revision. Each tab has its own response hash and observation time.

**Does the Actor remove duplicate data rows?** No. Identical input URLs are deduplicated, but identical data records may be meaningful and are retained. Choose a business key and deduplication policy in your destination if needed.

**Can I select columns or filter source rows?** Not through this input contract. Export the intended tab, inspect its complete JSON snapshot and apply a downstream transformation. Such a filter does not change which tab was extracted or the billing unit.

**Can I read arbitrary CSV files?** No. Initial sources must be the documented Google Sheets URLs. The derived gviz route and the delivery-host allowance exist for Google's own routes and do not turn this into a general file downloader.

**Does a retry create extra paid tab events?** Transport retries occur before a result is delivered and do not themselves emit result-found. A separately started run is a new purchase and can bill the same tab again. Destination retries should usually use the saved dataset.

**Can the hash prove authorship?** No. It identifies the exact retrieved bytes when available. It does not establish who wrote a cell, whether its claim is true, whether publication was authorized or when the source was last edited.

### R14. Sources, rights and support

The source-owner workflow is documented in [Google's publishing help](https://support.google.com/docs/answer/183965). This Actor accepts public export URLs as well as published CSV URLs according to the reviewed Wave A contract. Public access and permission to reuse content are separate matters: operate only on sheets whose content you are entitled to process, and respect any applicable publication notices.

The included controls are owned synthetic fixtures, not third-party customer data. The stored live evidence is dated and does not guarantee the source remains publicly readable forever. If sharing changes, not\_public is the correct outcome. Keep a permitted source-owner contact and an update process for production imports.

For support, open an issue from the Actor's Apify page and include a run ID plus the smallest nonconfidential input that reproduces the problem. Request a private support route before sharing restricted source material. Do not post credentials or Google session cookies. A feature request should describe the intended data contract and a permitted example source, rather than only asking to accept arbitrary websites.

Related-tool links below point only to published actors of the same author. The output examples above are accepted platform rows, not local probe output.

#### Related tools

Related tools for adjacent workflows in data pipelines for AI applications.

| Actor | What it does |
|---|---|
| [Structured Data Extractor](https://apify.com/zinin/structured-extract) | Pair it in the data pipelines for AI applications workflow: Extract title, meta, JSON-LD, headings, links, emails, and price observations from up to 100 authorized... |
| [RAG Dataset Builder](https://apify.com/zinin/sitemap-to-knowledge) | Pair it in the data pipelines for AI applications workflow: Turn buyer-authorized public website sitemaps into bounded, source-linked text chunks for RAG, search,... |
| [URL to Markdown Converter](https://apify.com/zinin/url-to-markdown) | Pair it in the data pipelines for AI applications workflow: Convert up to 100 authorized public HTML pages into clean Markdown for RAG, AI agents, SEO research, and... |
| [Live Price Oracle](https://apify.com/zinin/live-price-oracle) | Pair it in the data pipelines for AI applications workflow: Get timestamped DexScreener market observations for up to 100 token contracts or bounded ticker searches |

# Actor input Schema

## `publishedCsvUrls` (type: `array`):

1–5 public Google Sheets export?format=csv or published /d/e/id/pub?output=csv URLs, optional gid. No gviz, credentials or private sheets.

## `headerRow` (type: `integer`):

1-based CSV record containing column names; records before it are skipped.

## `maxRows` (type: `integer`):

1–10000 nonempty data records. A tab exceeding the cap returns a free partial result.

## `inferTypes` (type: `boolean`):

Opt in to lowercase true/false and conservative decimal numbers. Leading zeros, dates and unsafe integers stay strings.

## Actor input object example

```json
{
  "publishedCsvUrls": [
    "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"
  ],
  "headerRow": 1,
  "maxRows": 10000,
  "inferTypes": false
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset including free status rows.

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

Counts, incomplete work and fatal state.

# 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 = {
    "publishedCsvUrls": [
        "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"
    ],
    "headerRow": 1,
    "maxRows": 10000,
    "inferTypes": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/public-google-sheets-to-json").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 = {
    "publishedCsvUrls": ["https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"],
    "headerRow": 1,
    "maxRows": 10000,
    "inferTypes": False,
}

# Run the Actor and wait for it to finish
run = client.actor("zinin/public-google-sheets-to-json").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 '{
  "publishedCsvUrls": [
    "https://docs.google.com/spreadsheets/d/198Kd2vPvpEyCgYAJLmu53jW-uPaf6m6COkmv2stQzrM/export?format=csv"
  ],
  "headerRow": 1,
  "maxRows": 10000,
  "inferTypes": false
}' |
apify call zinin/public-google-sheets-to-json --silent --output-dataset

```

## MCP server setup

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

```

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/yL6MJ9kappxDCtxIq/builds/cNr8lCy6ADrkVvcnr/openapi.json
