# Serper Google Places Scraper (`umamidata/serper-google-places-scraper`) Actor

Run one or many Google Places searches through the Serper.dev API with automatic pagination and CID-based dedupe. Outputs a clean, flat, import-ready dataset - built for Clay, Google Sheets, and CRM lead-gen workflows. Pay only per page request.

- **URL**: https://apify.com/umamidata/serper-google-places-scraper.md
- **Developed by:** [Umami Data](https://apify.com/umamidata) (community)
- **Categories:** Lead generation, Developer tools, Automation
- **Stats:** 13 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

$10.00 / 1,000 serper places page requests

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

## Serper Google Places Scraper

Run one or many **Google Places** searches through the [Serper.dev](https://serper.dev) Places API and get a clean, deduplicated, import-ready dataset — without writing any pagination code. Tuned for lead-gen workflows (Clay, Google Sheets, CRM imports).

You bring your own Serper API key; the actor handles pagination, deduplication, and flattening for you.

### Features

- **Familiar input** — the standard `apiKey` / `q` / `gl` / `location` fields, so it slots straight into existing Serper Places workflows.
- **Multi-query runs** — paste many search queries (one per line) in a single run.
- **Automatic pagination** — the actor walks every result page until Serper returns no more places. No manual page counting.
- **Global deduplication** — places are deduplicated by Google CID across *all* queries (first-seen wins), so the same business never appears twice.
- **Spend cap** — `maxResults` caps the number of unique places per query so you stay in control of your Serper credits. An internal 50-page safety brake also guards against runaway pagination — but a normal query stops on its own long before that (Google returns at most ~120 results per query), so you'll almost never reach it.
- **Clean, flat output** — every row is the same fixed set of snake_case columns, null-filled when Serper omits a field. Ready to import straight into a spreadsheet or CRM.
- **Secret-safe** — your Serper key is stored encrypted, never logged, and never written to a dataset row.

### Data / Output

Every dataset row has exactly these columns, in this order. Fields that Serper does not return for a given place are filled with `null`.

| Column | Description |
|--------|-------------|
| `title` | Business / place name |
| `address` | Formatted street address |
| `latitude` | Latitude of the place |
| `longitude` | Longitude of the place |
| `rating` | Average Google rating (e.g. `4.6`) |
| `rating_count` | Number of ratings |
| `price_level` | Google price level (e.g. `$$`), or `null` |
| `category` | Primary place category (e.g. `Coffee shop`) |
| `phone_number` | Phone number, when the business has one listed; otherwise `null` |
| `website` | Website, when the business has one listed; otherwise `null` |
| `cid` | Google Customer ID — the stable place identifier used for deduplication |
| `maps_url` | Google Maps link, derived as `https://maps.google.com/?cid=<cid>` |
| `query` | The search query that produced this row |
| `location` | The location used for the search |
| `gl` | The country code used for the search |
| `hl` | The language code used (if supplied) |
| `page` | The Serper result page this row came from |
| `position` | The place's position within its page |

#### Sample output row

```json
{
  "title": "Blue Bottle Coffee",
  "address": "1 Ferry Building, San Francisco, CA 94111",
  "latitude": 37.7956,
  "longitude": -122.3933,
  "rating": 4.5,
  "rating_count": 1280,
  "price_level": "$$",
  "category": "Coffee shop",
  "phone_number": "+1 510-653-3394",
  "website": "https://bluebottlecoffee.com",
  "cid": "1234567890123456789",
  "maps_url": "https://maps.google.com/?cid=1234567890123456789",
  "query": "coffee shops",
  "location": "San Francisco, United States",
  "gl": "us",
  "hl": "en",
  "page": 1,
  "position": 1
}
```

(The `cid` above is synthetic — a placeholder for documentation only.)

### Usage / Tutorial

1. **Get a Serper API key.** Sign up at [serper.dev](https://serper.dev) and copy your API key from the dashboard. Serper offers a **free tier** so you can try the actor before paying for credits.
2. **Paste your key** into the **Serper API key** field. It is a secret field — stored encrypted, never logged, never written to output.
3. **Enter a search.** Set a single query in `q` (e.g. `coffee shops`), or paste many queries (one per line) into **Search queries**.
4. **Set the location.** Provide a 2-letter country code in `gl` (e.g. `us`) and a named `location` (e.g. `New York, United States`). Optionally set `hl` for the language and `maxResults` to cap results per query.
5. **Run the actor.** It paginates through every page automatically and pushes clean, deduplicated rows to the dataset. Export to JSON, CSV, or Excel, or pull it into Clay / Sheets / your CRM.

### Run it from code, n8n, or the API

You don't have to use the Console — the actor runs the same way programmatically. There are **two separate credentials**, don't mix them up:

- **Apify API token** — authenticates *you* to Apify so you can start a run (from your Apify account → Settings → API & Integrations).
- **Serper API key** — passed as the `apiKey` **input field**, exactly like in the Console. It is a secret field (encrypted, never logged), but you still include it in the input payload — that's expected and safe.

#### n8n

Add your **Apify API token** as Apify credentials once, then use the Apify node's **"Run Actor and get dataset items"** operation, select this actor, and provide the input JSON (keep your Serper key in an n8n credential/variable and reference it via an expression rather than hardcoding):

```json
{ "apiKey": "YOUR_SERPER_API_KEY", "queries": ["coffee shops in soho"], "gl": "gb", "location": "London, United Kingdom", "maxResults": 50 }
```

The dataset rows flow straight into the next node. (No dedicated node? An HTTP Request node hitting the REST endpoint below works identically.)

#### REST API (one call, returns the rows)

```bash
curl -X POST "https://api.apify.com/v2/acts/USERNAME~serper-google-places-scraper/run-sync-get-dataset-items?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "apiKey": "YOUR_SERPER_API_KEY", "queries": ["coffee shops in soho"], "gl": "gb", "location": "London, United Kingdom", "maxResults": 50 }'
```

#### JavaScript / Python (Apify client)

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('USERNAME/serper-google-places-scraper').call({
  apiKey: process.env.SERPER_KEY,
  queries: ['coffee shops in soho'],
  gl: 'gb',
  location: 'London, United Kingdom',
  maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(APIFY_TOKEN)
run = client.actor("USERNAME/serper-google-places-scraper").call(run_input={
    "apiKey": SERPER_KEY,
    "queries": ["coffee shops in soho"],
    "gl": "gb",
    "location": "London, United Kingdom",
    "maxResults": 50,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

> **Bound your spend programmatically:** set a per-run charge ceiling (`maxTotalChargeUsd` in the run options / API). The actor stops cleanly the moment the limit is reached, so an automated loop can never run away. Replace `USERNAME` with your Apify username (the actor lives at `apify.com/USERNAME/serper-google-places-scraper`).

### Pricing

This actor uses **pay-per-event** charging, plus your own Serper credits.

- **Actor fee:** **$0.01 per Serper Places page request** (~10 places per page). That works out to **$10 per 1,000 page requests**. One charged event = one Serper page fetched, so a single multi-page query costs multiple events (e.g. a query that spans 3 pages = 3 charged events). Charging is **per page request, not per result** — your cost scales with pages fetched, not with how many places land on each page.
- **Bring your own Serper credits:** you supply your own Serper API key and pay Serper directly for the API calls the actor makes. The actor never resells Serper credits.
- **Serper free tier:** Serper provides a free tier of credits, so you can evaluate the actor at no Serper cost before topping up.

### Input / Output Examples

#### Input

A minimal, runnable input. Replace the placeholder with your real key.

```json
{
  "apiKey": "YOUR_SERPER_API_KEY",
  "q": "coffee shops",
  "gl": "us",
  "location": "New York, United States",
  "maxResults": 100
}
```

To run several searches at once, supply `queries` (one per line) instead of, or in addition to, `q`:

```json
{
  "apiKey": "YOUR_SERPER_API_KEY",
  "queries": ["coffee shops", "bakeries", "bookstores"],
  "gl": "us",
  "location": "New York, United States",
  "hl": "en",
  "maxResults": 50
}
```

#### Output

Each run produces dataset rows shaped exactly like the [sample output row](#sample-output-row) above, with the columns documented in [Data / Output](#data--output).

### FAQ

**Why is pricing per page request and not per result?**
Serper bills per page request, and a page can return a variable number of places (up to ~10). Charging per page keeps your cost predictable and tied to the actual API calls made, instead of mischarging based on how many places happen to appear on a page.

**What happens when I hit the Serper charge limit?**
The actor respects the run's max-charge limit. It stops issuing new page requests before exceeding the limit, so you never get charged beyond what you set — partial results collected up to that point are still saved.

**How many results can a single query return?**
`maxResults` caps the number of **unique** places per query (counted after deduplication — duplicates never consume the cap). Omit it for unlimited results. An internal 50-page safety brake also applies as a runaway-pagination guard; whichever limit is reached first ends the query. In practice a single query exhausts on its own well before 50 pages — Google returns at most ~120 results per query, so the natural last-page stop almost always fires first. The brake exists only to bound spend if the upstream API ever fails to terminate.

**Do duplicate places across multiple queries get returned twice?**
No. Places are deduplicated globally by Google CID across all queries in a run (first-seen wins), so the same business never appears in more than one row.

# Actor input Schema

## `apiKey` (type: `string`):

Your serper.dev API key. Stored encrypted; never logged or written to dataset rows.

## `q` (type: `string`):

Google Places search query (e.g. 'coffee shops'). Optional when 'queries' is supplied; runs first when both are set.

## `queries` (type: `array`):

One query per line. Runs after 'q'. Blanks and exact duplicates are dropped automatically.

## `gl` (type: `string`):

2-letter country code (e.g. 'us').

## `location` (type: `string`):

Named location (e.g. 'New York, United States').

## `hl` (type: `string`):

Optional 2-letter language code (e.g. 'en') passed through to Serper.

## `maxResults` (type: `integer`):

Caps the number of UNIQUE places per query, counted after cross-query deduplication — duplicates never consume the cap. Omit it (API/JSON callers) for unlimited results. An internal 50-page safety brake applies independently as a runaway guard, but a normal query stops on its own well before that (Google returns at most ~120 results per query); whichever limit is reached first ends the query.

## Actor input object example

```json
{
  "q": "coffee shops",
  "gl": "us",
  "location": "New York, United States",
  "maxResults": 100
}
```

# 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 = {
    "q": "coffee shops",
    "gl": "us",
    "location": "New York, United States",
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("umamidata/serper-google-places-scraper").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 = {
    "q": "coffee shops",
    "gl": "us",
    "location": "New York, United States",
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("umamidata/serper-google-places-scraper").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 '{
  "q": "coffee shops",
  "gl": "us",
  "location": "New York, United States",
  "maxResults": 100
}' |
apify call umamidata/serper-google-places-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,umamidata/serper-google-places-scraper"
        }
    }
}
```

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/kvtswEa9CanjhbCZp/builds/YNuSAT6PjSrgmTCrn/openapi.json
