# Google Form Bulk Submitter — Inspect & Submit (`subimpact/google-form-bulk-submit`) Actor

Scrape any public Google Form's fields, then bulk-submit JSON or CSV responses. No login needed. Pay per response.

- **URL**: https://apify.com/subimpact/google-form-bulk-submit.md
- **Developed by:** [subimpact](https://apify.com/subimpact) (community)
- **Categories:** Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 response submitteds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Google Form Bulk Submitter — Inspect & Submit

Paste a public Google Form link and either **read its field schema** or **bulk-submit responses** from JSON or CSV.

No login. No browser. No CAPTCHA solving. The actor talks plain HTTP to the same `formResponse` endpoint your browser uses, so runs are fast and cheap.

***

### What it does

#### 1. `inspect` mode

Fetches the form page and returns the full question schema:

| Field | Meaning |
| --- | --- |
| `entryId` | The ID you submit as `entry.{entryId}` |
| `label` | The question text |
| `typeName` | `short_text`, `paragraph`, `dropdown`, `multiple_choice`, `checkboxes`, or `other` |
| `typeCode` | Google's raw type code (5 = linear scale, 7 = date, 9 = time, 10/11 = grids, 13 = file upload) |
| `required` | Best-effort required flag read from the form definition |
| `options` | Choice list for dropdown / multiple choice / checkbox questions |
| `hasOther` / `otherEntryId` | Whether the question has an "Other" box, and the entry key used for it |

Use this first — the labels it returns are exactly the keys to use in submit mode.

#### 2. `submit` mode

Give it the same form URL plus your responses. For every row the actor:

1. matches each column to a question (exact label → case-insensitive → partial match → or an `entry.{id}` key),
2. checks required questions and validates choice values against the real option list,
3. POSTs `entry.{id}=value` (+ the form's `fbzx` anti-CSRF token) to the form's `formResponse` endpoint,
4. pushes a per-row result to the dataset, and a summary row at the end.

Bad rows fail individually with a readable error — they never abort the run.

***

### Supported form URLs

- `https://docs.google.com/forms/d/e/1FAIpQLS.../viewform` (current format)
- `https://docs.google.com/forms/d/{longId}/viewform` (older format)
- `https://forms.gle/XXXXXX` (short link — redirects are followed automatically)

***

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `mode` | `inspect` | `submit` | `inspect` | |
| `formUrl` | string | — | **Required.** Public Google Form link. |
| `responses` | array of objects | — | Submit mode. One object per response. |
| `responsesCsv` | string | — | Submit mode alternative. Header row = question labels. Ignored when `responses` is set. |
| `email` | string | — | Only used when the form has Google's built-in email-collection field. A per-row `email` column overrides it. |
| `maxSubmissions` | integer | `100` | Capped at 1000. Extra rows are ignored. |
| `delayMs` | integer | `3000` | Pause between submissions and retries. Minimum 500. |
| `proxyCountry` | `US` | `DE` | `SG` | `MY` | `GB` | `US` | Country for Apify residential proxy sessions. |
| `proxyUrls` | array of strings | — | Your own proxies. When set, used instead of Apify Proxy. |

#### Inspect input

```json
{
  "mode": "inspect",
  "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLSeNmLLIzBuJBDgwJYe4591u6pIYu1N1bJU9raVTQJpVVMQZsw/viewform"
}
```

#### Submit input — JSON

Keys are question labels. Use an array for checkbox questions. A value that is not in the option list is sent through the question's "Other" box when it has one, otherwise the row fails with a clear message.

```json
{
  "mode": "submit",
  "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLS.../viewform",
  "delayMs": 3000,
  "responses": [
    {
      "Name of person completing form": "Ada Lovelace",
      "Email address of person completing form": "ada@example.com",
      "Technical Committee/Group": "Animal Bioacoustics",
      "If this is a cosponsored initiative, enter all the committees that will cosponsor": ["Noise", "Musical Acoustics"],
      "Amount in US dollars of funding requested.": "5000",
      "Description and Purpose of the Initiative (enter full details)": "A study of analytical engines."
    },
    {
      "Name of person completing form": "Alan Turing",
      "Email address of person completing form": "alan@example.com",
      "entry.844093924": "Computational Acoustics",
      "Amount in US dollars of funding requested.": "7500",
      "Description and Purpose of the Initiative (enter full details)": "On computable numbers."
    }
  ]
}
```

#### Submit input — CSV

The first row holds the question labels. Quoted fields may contain commas and newlines. For a checkbox question, separate the choices with commas inside one quoted cell.

```json
{
  "mode": "submit",
  "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLS.../viewform",
  "responsesCsv": "Name of person completing form,Technical Committee/Group,Amount in US dollars of funding requested.,Description and Purpose of the Initiative (enter full details)\nAda Lovelace,Animal Bioacoustics,5000,\"Analytical engines, in detail\"\nAlan Turing,Computational Acoustics,7500,\"Line one\nLine two\""
}
```

***

### Output

Dataset rows carry a `kind` field: `inspect`, `submission` or `summary`.

**Inspect row**

```json
{
  "kind": "inspect",
  "mode": "inspect",
  "formId": "1FAIpQLSeNmLLIzBuJBDgwJYe4591u6pIYu1N1bJU9raVTQJpVVMQZsw",
  "formTitle": "ASA 2026 Technical Initiative Proposal Form",
  "action": "https://docs.google.com/forms/d/e/1FAIpQLS.../formResponse",
  "fbzxPresent": true,
  "emailAddressPresent": false,
  "fileUpload": false,
  "fieldCount": 9,
  "fields": [
    {
      "entryId": "844093924",
      "label": "Technical Committee/Group",
      "typeCode": 2,
      "typeName": "dropdown",
      "required": true,
      "options": ["Acoustical Oceanography", "Animal Bioacoustics", "Architectural Acoustics"],
      "hasOther": false,
      "otherEntryId": null,
      "fileUpload": false
    }
  ],
  "timestamp": "2026-09-07T10:00:00.000Z"
}
```

**Submission rows + summary**

```json
{
  "kind": "submission",
  "formId": "1FAIpQLS...",
  "formTitle": "ASA 2026 Technical Initiative Proposal Form",
  "index": 0,
  "status": "submitted",
  "fields": { "Name of person completing form": "Ada Lovelace", "Technical Committee/Group": "Animal Bioacoustics" },
  "error": null,
  "attempts": 1,
  "warnings": [],
  "timestamp": "2026-09-07T10:00:03.000Z"
}
{
  "kind": "submission",
  "index": 1,
  "status": "failed",
  "fields": { "Name of person completing form": "Alan Turing" },
  "error": "Value \"Underwater Basket Weaving\" is not a valid option for \"Technical Committee/Group\". Valid options: Acoustical Oceanography | Animal Bioacoustics | ...",
  "timestamp": "2026-09-07T10:00:06.000Z"
}
{
  "kind": "summary",
  "mode": "summary",
  "formTitle": "ASA 2026 Technical Initiative Proposal Form",
  "total": 2,
  "submitted": 1,
  "failed": 1,
  "skipped": 0,
  "timestamp": "2026-09-07T10:00:06.000Z"
}
```

`status` is `submitted`, `failed` (validation or submission error) or `skipped` (nothing in the row matched the form).

***

### Pricing

Pay per event:

- **$0.01 per form inspected**
- **$0.02 per response submitted** — only successful submissions are charged. Validation failures and rejected rows cost nothing.

***

### Reliability notes

- Requests go through Apify Proxy: residential first, with an automatic fallback to datacenter groups. Supply `proxyUrls` to use your own proxies instead.
- Each row gets up to 3 attempts. Rate limits (HTTP 429), blocks (403) and Google server errors trigger a fresh proxy session and a `delayMs` pause; a rejection (HTTP 400) fails immediately, because retrying identical bad data will not help.
- Raise `delayMs` if you see 429s. Submitting hundreds of responses in seconds looks like abuse to Google and to the form owner.

### Limitations

- **Public forms only.** Forms that require a Google sign-in cannot be read or submitted, by design.
- **File-upload questions are skipped.** Google requires a signed-in account for uploads; the actor reports `fileUpload: true` and submits the rest of the row.
- Questions Google encodes with less common type codes (linear scale, date, time, grids) are reported as `typeName: "other"` with their entry ID, so you can still submit values for them — but their accepted formats are not validated.
- Multi-page forms with conditional (section-branching) logic are not walked page by page; responses are posted in a single request.

### Ethics & acceptable use

Only submit to forms you own or have explicit permission to automate. This actor does not solve CAPTCHAs or bypass access controls.

It will not log in, will not touch forms that require authentication, and will not attempt to defeat any protection a form owner has put in place. Use it for your own data collection, migrations from other tools, load-testing your own forms, and seeding test data — not for spamming, ballot stuffing, or overwhelming someone else's form. Respect the form owner's terms and Google's Terms of Service.

# Actor input Schema

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

inspect = read the form and return its field schema (labels, types, entry IDs, options). submit = validate and POST your bulk responses to the form.

## `formUrl` (type: `string`):

Public Google Form link. Supported: https://docs.google.com/forms/d/e/1FAIpQLS.../viewform, https://docs.google.com/forms/d/{id}/viewform, or a https://forms.gle/XXXX short link.

## `responses` (type: `array`):

Submit mode. Array of objects, one per response. Each key is a question label from the form (exact match preferred, case-insensitive and partial matches are accepted) or an entry ID such as "entry.123456789". Values are strings; use an array of strings for checkbox questions. Example: \[{"Name of person completing form": "Ada Lovelace", "Technical Committee/Group": "Animal Bioacoustics"}]

## `responsesCsv` (type: `string`):

Submit mode alternative to the JSON array. First row = question labels, one response per following row. Quoted fields may contain commas and newlines. Ignored when "responses" is set.

## `email` (type: `string`):

Only used when the form collects an email address in its built-in emailAddress field. A per-row "email" column overrides this value.

## `maxSubmissions` (type: `integer`):

Maximum number of response rows to submit in one run. Extra rows are ignored.

## `delayMs` (type: `integer`):

Pause between submissions and between retry attempts. Lower values risk rate limiting by Google.

## `proxyCountry` (type: `string`):

Country for Apify residential proxy sessions.

## `proxyUrls` (type: `array`):

Optional. Your own proxy URLs (e.g. http://user:pass@host:port). When set, these are used instead of Apify Proxy.

## Actor input object example

```json
{
  "mode": "inspect",
  "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLS.../viewform",
  "maxSubmissions": 100,
  "delayMs": 3000,
  "proxyCountry": "US"
}
```

# Actor output Schema

## `kind` (type: `string`):

inspect / submission / summary

## `formId` (type: `string`):

Google Form ID taken from the form URL

## `formTitle` (type: `string`):

Title of the Google Form

## `index` (type: `string`):

Zero-based index of the response row (submission rows)

## `status` (type: `string`):

submitted / failed / skipped (submission rows)

## `fields` (type: `string`):

Discovered field schema (inspect) or the label/value pairs that were submitted (submission)

## `error` (type: `string`):

Validation or submission error for this row, null when successful

## `attempts` (type: `string`):

Number of HTTP attempts made for this row

## `fieldCount` (type: `string`):

Number of discovered questions (inspect rows)

## `fileUpload` (type: `string`):

True when the form contains a file-upload question (cannot be filled by this actor)

## `total` (type: `string`):

Number of response rows processed (summary row)

## `submitted` (type: `string`):

Number of responses successfully submitted (summary row)

## `failed` (type: `string`):

Number of responses that failed (summary row)

## `skipped` (type: `string`):

Number of responses skipped (summary row)

## `timestamp` (type: `string`):

ISO timestamp of the row

# 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 = {
    "mode": "inspect",
    "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLS.../viewform",
    "maxSubmissions": 100,
    "delayMs": 3000,
    "proxyCountry": "US"
};

// Run the Actor and wait for it to finish
const run = await client.actor("subimpact/google-form-bulk-submit").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 = {
    "mode": "inspect",
    "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLS.../viewform",
    "maxSubmissions": 100,
    "delayMs": 3000,
    "proxyCountry": "US",
}

# Run the Actor and wait for it to finish
run = client.actor("subimpact/google-form-bulk-submit").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 '{
  "mode": "inspect",
  "formUrl": "https://docs.google.com/forms/d/e/1FAIpQLS.../viewform",
  "maxSubmissions": 100,
  "delayMs": 3000,
  "proxyCountry": "US"
}' |
apify call subimpact/google-form-bulk-submit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,subimpact/google-form-bulk-submit"
        }
    }
}

```

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/FFT7Mq6nMn2DSG2aL/builds/PAfNPRkNjNda1rjXw/openapi.json
