# Businesses Ignoring Bad Reviews - Google Maps Reputation Leads (`that_red_bird/unanswered-reviews-leads`) Actor

From a Google Maps reviews dataset, find businesses with unanswered 1-2 star reviews, their owner reply rate, and how many 5-star reviews they need to reach the next star. Leads for reputation agencies.

- **URL**: https://apify.com/that_red_bird/unanswered-reviews-leads.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Lead generation, Marketing, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 business scoreds

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

## Businesses Ignoring Bad Reviews - Google Maps Reputation Leads

Give it the dataset of a Google Maps reviews scraper and it lists the businesses that leave their 1-star and 2-star reviews unanswered, with the owner reply rate, how many days the reviews have sat there, and how many 5-star reviews the business needs to reach its next half-star.
Made for reputation-management agencies and review-growth freelancers who want a ready list of local businesses to pitch.

Each lead comes with a one-line pitch you can paste into a cold email.

### Quick start (3 copy-paste examples)

**1. See it work (empty input):** run with `{}`. You get a labelled demo of 5 synthetic businesses (free).

**2. Leads from a Google Maps reviews run:** run `compass/google-maps-reviews-scraper` for a list of businesses (for example "dentists in Austin"), then pick its dataset:

```json
{ "datasetId": "YOUR_DATASET_ID", "maxStars": 2, "hotLeadThreshold": 3 }
```

**3. Only fresh problems, give owners 30 days:**

```json
{ "datasetId": "YOUR_DATASET_ID", "minDaysUnanswered": 30, "lookbackDays": 365, "minUnanswered": 2 }
```

### Use it with (pipelines)

- `compass/crawler-google-places` (with reviews switched on) -> this Actor. Place rows with `title`, `placeId`, `totalScore`, `reviewsCount`, `reviewsDistribution` and nested `reviews[]` are read directly.
- `compass/google-maps-reviews-scraper` -> this Actor. Review rows with `stars`, `text`, `publishedAtDate`, `responseFromOwnerText`, `title`, `placeId`, `totalScore`, `reviewsCount` are grouped by business.
- Then add contact details with `google-maps-email-enricher` and send the pitch line.

A places dataset **without** reviews only gives the half-star calculation (rating plus review count); the unanswered counts need review rows.

### Input guide

| Field | What it does |
|---|---|
| Google Maps dataset | Dataset of the scraper run (picker). |
| Or paste rows | JSON, JSON lines or CSV, one row per review. |
| Count reviews up to this many stars as bad | Default 2 (1 and 2 stars). |
| Only return businesses with unanswered bad reviews | On by default, so you pay only for leads. |
| Minimum unanswered bad reviews | Default 1. |
| Hot lead from this many unanswered bad reviews | Default 3. |
| Ignore bad reviews younger than (days) | Do not count reviews the owner may not have seen yet. |
| Only look at reviews from the last (days) | Restrict to recent reviews. Needs a readable date. |
| Maximum businesses | Safety cap. |

### Output

| Field | Meaning |
|---|---|
| `business`, `placeId`, `address`, `category`, `phone`, `website`, `url` | The business, when the dataset has these. |
| `totalScore`, `reviewsCount`, `ratingSource` | Rating and count (exact average from `reviewsDistribution` when present, otherwise the rounded `totalScore`). |
| `reviewsAnalyzed` | Reviews found in the dataset for this business. |
| `negativeReviews`, `negativeUnanswered` | Bad reviews and how many have no owner reply. |
| `negativeResponseRatePct`, `overallResponseRatePct` | Owner reply rates among bad reviews and among all reviews in the data. |
| `oldestUnansweredDays`, `avgDaysUnanswered` | How long they have waited. |
| `worstUnanswered` | The lowest-rated oldest unanswered review (stars, text, age). |
| `nextHalfStarTarget`, `reviewsNeededNextHalfStar` | The next half-star and how many extra 5-star reviews show it on Google. |
| `hotLead` | True at or above the hot-lead threshold. |
| `pitch` | A ready sentence for your outreach. |

The half-star maths: Google shows the average rounded to one decimal, so a business shows 4.5 once its true average reaches 4.45. With average `A` over `N` reviews and target `T`, the number of 5-star reviews needed is `ceil(((T - 0.05) x N - A x N) / (5 - (T - 0.05)))`.

Sample row (shortened):

```json
{ "type": "business", "business": "Demo Dental Clinic", "totalScore": 4.2, "reviewsCount": 312, "reviewsAnalyzed": 6,
  "negativeReviews": 4, "negativeUnanswered": 4, "negativeResponseRatePct": 0, "overallResponseRatePct": 33.3,
  "oldestUnansweredDays": 410, "nextHalfStarTarget": 4.5, "reviewsNeededNextHalfStar": 142, "hotLead": true,
  "pitch": "Demo Dental Clinic (4.2 stars from 312 reviews) has left 4 of its 4 reviews of 2 stars or less unanswered, the oldest 410 days old; it needs about 142 more 5-star reviews to show 4.5 stars." }
```

### Price

Pay per event: **$0.003 per business returned** and **$0.01 more for each hot lead**. Businesses filtered out and the free demo are never charged.
Example: 200 leads, 40 of them hot = 200 x $0.003 + 40 x $0.01 = **$1.00**.

### FAQ

**Does it scrape Google Maps?** No. It reads a dataset you already have, so there is nothing to block.

**Why is the response rate based on few reviews?** Reviews scrapers usually return a sample (often the newest N). Rates are computed on the reviews in your dataset; each row says how many.

**Can a business have replied and it still shows as unanswered?** If the scraper did not capture the owner response field, replies look missing. Check one business on Google Maps before a big outreach.

### Honest limits

- Owner-reply detection uses the fields `responseFromOwnerText` and common alternatives. Verified against the documented field names of the popular scrapers, not against a fresh live export.
- Review age needs `publishedAtDate` or an English relative date such as "2 months ago"; other languages give no age (the review is still counted).
- If only `totalScore` (one decimal) is available, the half-star number can be off by a few reviews; with `reviewsDistribution` it is exact.
- It finds leads, it does not contact anyone.

# Actor input Schema

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

Pick the dataset of a Google Maps reviews scraper run (for example compass/google-maps-reviews-scraper) or of a Google Maps places scraper that includes reviews. Leave empty to use pasted rows below, or to see the built-in demo.

## `rows` (type: `string`):

Paste a JSON array, JSON lines or CSV with one row per review. Useful columns: title (business), placeId, stars, text, publishedAtDate, responseFromOwnerText, totalScore, reviewsCount. Ignored when a dataset is picked above.

## `maxStars` (type: `integer`):

Reviews with this many stars or fewer count as negative. 2 means 1-star and 2-star reviews.

## `onlyLeads` (type: `boolean`):

When on, businesses that answered all their bad reviews (or have none) are left out, so you pay only for real leads. Turn off to score every business in the data.

## `minUnanswered` (type: `integer`):

A business is returned as a lead only with at least this many unanswered bad reviews. Used when 'Only return businesses with unanswered bad reviews' is on.

## `hotLeadThreshold` (type: `integer`):

Businesses with at least this many unanswered bad reviews are marked as hot leads (a separately priced event).

## `minDaysUnanswered` (type: `integer`):

Only count a bad review as unanswered when it is at least this many days old, so recent reviews the owner has not seen yet are not counted. 0 counts every bad review.

## `lookbackDays` (type: `integer`):

Restrict the analysis to reviews published within this many days. 0 uses every review in the data. Reviews without a readable date are left out when this is set.

## `maxBusinesses` (type: `integer`):

Safety cap on how many businesses are returned in one run.

## Actor input object example

```json
{
  "rows": "",
  "maxStars": 2,
  "onlyLeads": true,
  "minUnanswered": 1,
  "hotLeadThreshold": 3,
  "minDaysUnanswered": 0,
  "lookbackDays": 0,
  "maxBusinesses": 2000
}
```

# Actor output Schema

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

No description

## `downloadCsv` (type: `string`):

No description

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

No description

## `count` (type: `string`):

No description

# 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 = {
    "rows": "",
    "maxStars": 2,
    "onlyLeads": true,
    "minUnanswered": 1,
    "hotLeadThreshold": 3,
    "minDaysUnanswered": 0,
    "lookbackDays": 0,
    "maxBusinesses": 2000
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/unanswered-reviews-leads").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 = {
    "rows": "",
    "maxStars": 2,
    "onlyLeads": True,
    "minUnanswered": 1,
    "hotLeadThreshold": 3,
    "minDaysUnanswered": 0,
    "lookbackDays": 0,
    "maxBusinesses": 2000,
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/unanswered-reviews-leads").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 '{
  "rows": "",
  "maxStars": 2,
  "onlyLeads": true,
  "minUnanswered": 1,
  "hotLeadThreshold": 3,
  "minDaysUnanswered": 0,
  "lookbackDays": 0,
  "maxBusinesses": 2000
}' |
apify call that_red_bird/unanswered-reviews-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/unanswered-reviews-leads"
        }
    }
}
```

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/PYNMnqIO3oBP51MfF/builds/kqWnC4GXeFwDmVeel/openapi.json
