# GBP Audit – Google Business Profile Auditor (GMB) (`acebuilds/gbp-auditor`) Actor

Local SEO audit for any Google Business Profile (Google My Business / GMB) in seconds. Get a 0–90 score, letter grade, severity-ranked issues, prioritized fixes, and a nearby-competitor comparison. Built for local SEO agencies, freelancers, and small business owners.

- **URL**: https://apify.com/acebuilds/gbp-auditor.md
- **Developed by:** [Ace](https://apify.com/acebuilds) (community)
- **Categories:** AI, Lead generation, Open source
- **Features:** Limited permissions
- **Stats:** 127 total users, 7 monthly users, 100.0% runs succeeded, 3 bookmarks
- **User rating**: No ratings yet

## Pricing

from $350.00 / 1,000 audit completeds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Google Business Profile Auditor

Audit any Google Business Profile in seconds — one business, or a list of up to 500 in a
single run. Input a business name and city, get back a scored report with severity-ranked
issues, specific recommendations, and a competitor comparison, all in structured JSON.
You are billed per completed audit; audits that fail are free.

> **Price: $1.00 per business audited** ($0.35 on an Apify Business plan). The Store shows
> this as "$1,000 / 1,000" because it quotes prices per 1,000 events — here one event is
> one complete audit report, not one data row. Audits that fail are not charged.

Built for marketing agencies, freelancers, and anyone who needs to audit local business
listings at scale without doing it manually.

***

### What It Does

1. Searches Google Maps for the target business
2. Extracts profile data using Apify's maintained `compass/crawler-google-places` actor
3. Scores 8 auto-detectable profile factors (max 90 points)
4. Flags manual-check items that cannot be auto-detected
5. Compares the business against nearby competitors
6. Returns a complete audit JSON to your dataset

***

### Input

Audit one business with `businessName` + `city`, or many with `businesses`.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `businessName` | string | For a single audit | Business name (exact or approximate) |
| `city` | string | For a single audit | City where the business is located |
| `state` | string | No | US state abbreviation — narrows results |
| `businesses` | array | For a batch | List of `{ businessName, city, state? }` objects, up to 500 per run. When present, the single fields above are ignored |
| `maxCompetitors` | integer | No | Competitors to include per audit (1–5, default: 3) |

**Single audit:**

```json
{
  "businessName": "Joe's Pizza",
  "city": "Austin",
  "state": "TX",
  "maxCompetitors": 3
}
```

**Batch audit** — one run, one dataset row per business, billed only for the ones that
complete:

```json
{
  "businesses": [
    { "businessName": "Joe's Pizza", "city": "Austin", "state": "TX" },
    { "businessName": "Home Slice Pizza", "city": "Austin", "state": "TX" },
    { "businessName": "Via 313", "city": "Austin", "state": "TX" }
  ],
  "maxCompetitors": 3
}
```

Batch rules:

- Businesses are audited three at a time, in list order. Each audit takes roughly 20–90 s,
  so a 100-business batch finishes in about 15–50 min.
- Duplicate entries (same name, city and state) are audited once.
- An entry that is malformed, cannot be found on Google Maps, or fails for any other reason
  gets its own dataset row with an `error` field and is **not charged**. The rest of the
  batch continues.
- If you set a **maximum total charge** on the run and the batch reaches it, the remaining
  businesses are listed in the dataset as not audited, so nothing is silently dropped.

***

### Output

All results are saved to the default dataset. One item per business — a single audit
writes one row, a batch writes one row per entry, in list order.

#### Top-level fields

| Field | Type | Description |
|-------|------|-------------|
| `businessName` | string | Matched business name from Google Maps |
| `searchedAs` | string | The name you passed as input |
| `city` | string | City from the listing |
| `state` | string | State from the listing |
| `auditDate` | string | ISO timestamp of the audit |
| `mapsUrl` | string | Direct Google Maps URL for the business |
| `placeId` | string | Google Place ID |
| `score` | number | Auto-detected score (0–90) |
| `maxAutoScore` | number | Always 90 — max from auto-detectable fields |
| `grade` | string | A / B / C / D |
| `summary` | string | One-line human-readable summary |
| `profile` | object | Raw profile data (see below) |
| `issues` | array | Ranked issues found |
| `manualChecks` | array | Fields requiring manual verification |
| `recommendations` | array | Specific, actionable fixes — one per issue |
| `competitiveInsights` | array | Head-to-head comparisons against the competitors found |
| `competitors` | array | Nearby competitor summaries, ordered by search rank |
| `poweredBy` | string | Always `GBP Auditor on Apify Store` |
| `dataSource` | string | Always `compass/crawler-google-places` |

#### `profile` object

| Field | Type | Notes |
|-------|------|-------|
| `rating` | number | null | Average star rating (e.g. 4.3) |
| `reviewCount` | number | Total number of reviews |
| `reviewsDistribution` | null | Always `null` — the run requests `maxReviews: 0`, so Google Maps returns no star breakdown. Kept for schema stability. |
| `hasWebsite` | boolean | Whether a website is linked |
| `website` | string | null | Website URL if present |
| `phone` | string | null | Phone number if present |
| `address` | string | null | Full address string |
| `imageCount` | number | Total photos on the listing |
| `categories` | array | Business category tags |
| `permanentlyClosed` | boolean | True if marked permanently closed |
| `temporarilyClosed` | boolean | True if marked temporarily closed |

#### `issues` array items

| Field | Type | Values |
|-------|------|--------|
| `severity` | string | `critical` / `high` / `medium` / `low` |
| `field` | string | Which profile field has the issue |
| `message` | string | Human-readable description of the problem |

**Every point you lose is explained.** If the score is below 90, `issues` names each factor
that cost points and `recommendations` carries a matching fix. A profile that scores full
marks on a factor produces no issue for it, so a 90/90 profile returns `issues: []` —
that empty array means "nothing to fix", not "nothing detected".

Severity is about ranking impact, not about how many points were lost:

| Severity | Meaning |
|----------|---------|
| `critical` | Actively suppresses the listing — e.g. marked permanently closed, or a rating below 3.0 |
| `high` | A missing fundamental — no phone, no website, too few reviews to enter the Local Pack |
| `medium` | Present but underbuilt — partial address, thin photo set, no categories |
| `low` | Already competitive, short of best practice — e.g. a 4.1 rating or 50-99 reviews |

The `summary` field counts `critical` and `high` together as "high-priority".

#### `manualChecks` array items

Fields that cannot be auto-detected (business description, recent posts, services listed,
Q\&A section). Each item has:

| Field | Type | Description |
|-------|------|-------------|
| `field` | string | Field name |
| `message` | string | What to check manually |
| `why` | string | Why it matters for ranking |

#### `competitiveInsights` array items

Always present when at least one competitor was found (empty array if none were). These
compare the target against the competitor set rather than against a fixed benchmark, so
they fire even on a high-scoring profile that has no issues left.

| Field | Type | Description |
|-------|------|-------------|
| `type` | string | `review_gap` / `review_lead` / `photo_gap` / `photo_lead` / `rating_gap` / `rating_lead` / `website_advantage` |
| `message` | string | The comparison, with the competitor named and the gap quantified |
| `action` | string | What to do about it |

#### `competitors` array items

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Competitor business name |
| `rating` | number | null | Their average rating |
| `reviewCount` | number | Their total reviews |
| `hasWebsite` | boolean | Whether they have a website linked |
| `imageCount` | number | Their photo count |
| `rank` | number | null | Their position in the search results |
| `mapsUrl` | string | null | Their Google Maps URL |

Competitors are returned in ascending `rank` order (any unranked result sorts last), and
other locations of the same business are excluded — a shared domain, a matching name, or a
subsidiary that carries the parent brand in its title will not be returned as a rival.
Google re-ranks local results per search, so the specific competitors returned for the same
business can differ between runs.

***

### Scoring Breakdown

| Factor | Max Points | Notes |
|--------|-----------|-------|
| Phone number | 10 | Present or not |
| Website linked | 15 | Present or not |
| Star rating | 15 | Scaled: 4.5+ = full, 4.0+ = 10, 3.5+ = 5, <3.5 = 0 |
| Review count | 20 | Scaled: 100+ = full, 50+ = 15, 20+ = 10, 5+ = 5, <5 = 0 |
| Photo count | 15 | Scaled: 20+ = full, 10+ = 10, 3+ = 5, <3 = 0 |
| Category set | 5 | Present or not |
| Address complete | 10 | Full address = 10, partial = 5, missing = 0 |
| **Total** | **90** | |

**Grade thresholds:** A ≥ 75, B ≥ 55, C ≥ 35, D < 35.

Partial credit on a scaled factor still produces an issue and a recommendation — a listing
with 50 reviews scores 15/20 and gets a `low` severity entry naming the 100-review
threshold it fell short of.

**Fields not auto-scored** (require manual verification):

- Business description text
- Recent Google Business posts
- Services / menu items listed
- Q\&A section populated

***

### Example Output

Generated by running the scorer on a sample profile — the field set, ordering, and summary
wording are exactly what a run writes to the dataset.

```json
{
  "businessName": "Joe's Pizza",
  "searchedAs": "Joe's Pizza",
  "city": "Austin",
  "state": "TX",
  "auditDate": "2026-08-26T14:23:00.000Z",
  "mapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJexampleJoesPizza",
  "placeId": "ChIJexampleJoesPizza",
  "score": 45,
  "maxAutoScore": 90,
  "grade": "C",
  "summary": "\"Joe's Pizza\" scored 45/90 (C). 4 issues found (2 high-priority). 3 competitive insights generated. 4 fields require manual verification.",
  "profile": {
    "rating": 4.1,
    "reviewCount": 18,
    "reviewsDistribution": null,
    "hasWebsite": false,
    "website": null,
    "phone": "(512) 555-0100",
    "address": "1234 S Congress Ave, Austin, TX 78704",
    "imageCount": 7,
    "categories": [
      "Pizza restaurant",
      "Italian restaurant"
    ],
    "permanentlyClosed": false,
    "temporarilyClosed": false
  },
  "issues": [
    {
      "severity": "high",
      "field": "website",
      "message": "No website linked to this listing."
    },
    {
      "severity": "low",
      "field": "rating",
      "message": "Rating is 4.1 — solid, but profiles at 4.5+ earn full marks and win more clicks from the Local Pack."
    },
    {
      "severity": "high",
      "field": "reviewCount",
      "message": "Only 18 reviews. Businesses in the Google Local Pack typically have 20+."
    },
    {
      "severity": "medium",
      "field": "images",
      "message": "Only 7 photos. Listings with 10+ photos get significantly more views."
    }
  ],
  "manualChecks": [
    {
      "field": "businessDescription",
      "message": "Manually verify: does your profile have a 400–750 character business description?",
      "why": "Description is not returned by the Google Maps scraper. Log in to GBP to check."
    },
    {
      "field": "recentPosts",
      "message": "Manually verify: have you posted a Google Business update in the last 7 days?",
      "why": "Recent posts signal activity to Google and appear directly in your listing."
    },
    {
      "field": "servicesOrMenu",
      "message": "Manually verify: are your services, products, or menu items listed in GBP?",
      "why": "Listed services help Google match your business to more relevant queries."
    },
    {
      "field": "questionsAndAnswers",
      "message": "Manually verify: have you pre-populated the Q&A section with common questions?",
      "why": "Pre-answered Q&As appear in your listing and reduce friction for new customers."
    }
  ],
  "recommendations": [
    "Link your website in Google Business Profile → Info. A missing website significantly hurts local search ranking.",
    "You are close to the 4.5 threshold. Ask recent happy customers for a review — at this rating a handful of 5-star reviews moves the average quickly.",
    "Set up an automated review request: send a follow-up text or email 24 hours after every transaction with a direct link to your Google review page.",
    "Add photos of: exterior (for navigation), interior, staff, and your top products/dishes. Use real photos — stock images are flagged by Google."
  ],
  "competitiveInsights": [
    {
      "type": "review_gap",
      "message": "Your top competitor (Tony's Pizzeria) has 312 reviews vs your 18 — a gap of 294.",
      "action": "Set up an automated post-visit review request (SMS or email) to close this gap systematically."
    },
    {
      "type": "photo_gap",
      "message": "Tony's Pizzeria has 47 photos vs your 7 — a gap of 40.",
      "action": "Upload photos of your exterior, interior, staff, and top products. Aim to exceed the competitor count."
    },
    {
      "type": "rating_gap",
      "message": "Tony's Pizzeria has a higher rating (4.6) than you (4.1).",
      "action": "Review your 1-star and 2-star feedback for recurring themes — one fixable issue often drives most negative reviews."
    }
  ],
  "competitors": [
    {
      "name": "Tony's Pizzeria",
      "rating": 4.6,
      "reviewCount": 312,
      "hasWebsite": true,
      "imageCount": 47,
      "rank": 1,
      "mapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJexampleTonys"
    }
  ],
  "poweredBy": "GBP Auditor on Apify Store",
  "dataSource": "compass/crawler-google-places"
}
```

***

### Pricing

**$1.00 per completed audit — or $0.35 if you are on an Apify Business plan.** Audit one
business or five hundred in a run; the price is the same per business. No subscription to
this Actor.

The Apify Store quotes this price per 1,000 events, so it appears there as
**$1,000 / 1,000**. Each event is one finished audit (score, issues, recommendations and
competitor comparison for one business), so that is $1.00 per business — not $1.00 per
1,000 data rows, which is how many scrapers price.

#### Your price depends on your Apify plan

This Actor offers the Apify Store pricing discount. It is applied automatically at run
time based on the plan your account is already on — there is nothing to claim or enter.

| Your Apify plan | Per audit | 100 audits | 500 audits |
|-----------------|-----------|-----------|-----------|
| Free | $1.00 | $100.00 | $500.00 |
| Starter | $0.70 | $70.00 | $350.00 |
| Scale | $0.50 | $50.00 | $250.00 |
| Business or above | **$0.35** | **$35.00** | **$175.00** |

If you audit profiles in bulk — agency prospecting, client reporting, a monthly re-audit
across a book of business — put the list in `businesses` and run it once. The Business-plan
rate is where this Actor is designed to be used.

#### How the charge is calculated

This Actor is billed **pay per event**. There is exactly one event, `audit-completed`,
charged **once per business that receives a report**, after the report is written to the
dataset. Nothing is charged when a run starts, and nothing is charged for a business whose
audit fails — see below.

#### Costs billed separately by Apify

Two things are **not** included in the price above:

1. **Apify platform usage (compute).** This Actor is a lightweight orchestrator — it peaks
   at roughly 55 MB of real memory — so platform usage is typically a fraction of a cent
   per audit, charged at your plan's rates.
2. **The sub-actor call.** This Actor calls `compass/crawler-google-places` to fetch Google
   Maps data, one or two searches per business. That Actor bills its own pay-per-event
   charges (a `place-scraped` event) to your account. See its listing for current rates.

#### Reliability, and what happens if an audit fails

See the run statistics on this listing, which Apify publishes automatically and the
developer cannot edit. (Runs a user cancels mid-flight are counted separately by Apify as
aborted.)

**A failed audit is not billed.** The charge fires only after a report is written, so a
scraper outage, a business that cannot be found on Google Maps, or a malformed batch entry
produces an `error` row in the dataset and no charge. If something else goes wrong, open an
issue on this Actor and I will make it right.

***

### Notes

- **Batch limit is 500 businesses per run.** Larger lists: split them across runs, or
  start runs from the Apify API or a scheduled task
- Results reflect publicly visible Google Maps data at the time of the run
- Business matching uses name + city — add `state` if results are ambiguous
- The `compass/crawler-google-places` sub-actor is maintained by Apify and handles
  proxy rotation and anti-blocking automatically
- Google Maps data structure may change; the sub-actor is updated by Apify when this happens

***

### Support

Questions or issues? Open a GitHub issue or contact via the Apify Store listing page.

# Actor input Schema

## `businessName` (type: `string`):

The exact or approximate name of the business to audit. For more than one business, use the Batch field below instead.

## `city` (type: `string`):

City where the business is located

## `state` (type: `string`):

US state abbreviation to narrow results

## `businesses` (type: `array`):

Audit many profiles in one run. A JSON list of objects, each with `businessName` and `city` (and optional `state`). Up to 500 per run. When this list is provided, the single Business Name / City fields above are ignored. Each business is billed only when its audit succeeds.

## `maxCompetitors` (type: `integer`):

How many competitors to include in the comparison (1–5)

## Actor input object example

```json
{
  "businessName": "Joe's Pizza",
  "city": "Austin",
  "state": "TX",
  "businesses": [
    {
      "businessName": "Joe's Pizza",
      "city": "Austin",
      "state": "TX"
    },
    {
      "businessName": "Home Slice Pizza",
      "city": "Austin",
      "state": "TX"
    }
  ],
  "maxCompetitors": 3
}
```

# Actor output Schema

## `auditResults` (type: `string`):

The audit for the requested business, as a dataset item. Opens on the formatted Audit Results table.

# 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 = {
    "businessName": "Joe's Pizza",
    "city": "Austin",
    "state": "TX"
};

// Run the Actor and wait for it to finish
const run = await client.actor("acebuilds/gbp-auditor").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 = {
    "businessName": "Joe's Pizza",
    "city": "Austin",
    "state": "TX",
}

# Run the Actor and wait for it to finish
run = client.actor("acebuilds/gbp-auditor").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 '{
  "businessName": "Joe'\''s Pizza",
  "city": "Austin",
  "state": "TX"
}' |
apify call acebuilds/gbp-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,acebuilds/gbp-auditor"
        }
    }
}
```

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/QIdxEfhwbtceaECLX/builds/1HHB9CIu9JEAmX18d/openapi.json
