# Business Search & AI Visibility Audit: NAP, Schema, AI Crawlers (`cygnix_labs/business-search-ai-visibility-audit`) Actor

Local SEO + AI search audit per business site: NAP and opening-hours consistency (site, schema, Google Business Profile, directories), schema vs visible prices, AI crawler access (GPTBot, OAI-SearchBot, PerplexityBot, ClaudeBot) and indexing. P0-P3 findings with fixes. $0.04/site.

- **URL**: https://apify.com/cygnix_labs/business-search-ai-visibility-audit.md
- **Developed by:** [Umair Saleem Mohammad Saleem](https://apify.com/cygnix_labs) (community)
- **Categories:** SEO tools, AI, Agents
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$40.00 / 1,000 site auditeds

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

## Business Search & AI Visibility Audit - NAP, Schema, AI Crawlers

**Answer first:** give this actor a business website (clinic, salon, restaurant, shop, agency client) and it checks whether **Google, maps apps and AI assistants (ChatGPT, Perplexity, Gemini, Copilot) get the right facts**: name, phone, address and opening hours, prices in structured data, AI crawler access and indexing basics. Every problem becomes one row with a **P0-P3 severity, the URL, what was observed, what was expected and a plain-English fix**. **$0.04 per audited site.** Unreachable or bot-blocked sites are free.

| Quick facts | |
|---|---|
| Best for | Local SEO audits, AI search visibility (AEO / GEO) checks, agency pitch audits, client onboarding baselines, multi-location QA |
| Input | Website URLs, plus optional Google Business Profile URL, directory listing URLs and the owner-confirmed facts |
| Output | One row per finding (P0-P3, evidence, fix) + one summary row per site, a findings CSV and a client-ready Markdown report |
| Price | $0.04 per audited site (homepage + up to 25 key pages + sitemap sample). Findings rows are free. No start fee |
| Needs | Nothing: no API keys, no proxies, no logins |
| Does not do | Change anything on the site, invent a score, or claim a fix is approved. It collects evidence and flags |

### What it checks

| Area | Checks | Example finding (illustrative) |
|---|---|---|
| **NAP & opening hours** | Name, phone, address and day-by-day opening hours across the site's pages, its LocalBusiness / Organization schema, the Google Business Profile, directory listings and the facts you confirm | "Schema says Thu 09:00-21:00, contact page says Thu 09:00-15:00" (P1) |
| **Structured data vs the page** | Invalid JSON-LD, missing LocalBusiness schema, schema phone/address not shown on the page, Product/Offer price not equal to the visible price, offers without currency, FAQ markup for questions nobody can see | "Schema price 43.00 USD, page shows 39.00" (P1) |
| **AI crawler access** | robots.txt rules for Googlebot, Bingbot, OAI-SearchBot, ChatGPT-User, PerplexityBot, GPTBot, ClaudeBot, Google-Extended, CCBot; meta robots / X-Robots-Tag noindex, nosnippet, max-snippet:0; firewall test with AI bot user agents; JavaScript-only content; /llms.txt | "robots.txt blocks OAI-SearchBot: no ChatGPT search citations" (P1) |
| **Indexing basics** | Status codes, http→https, redirect chains, canonical (missing, conflicting, cross-domain, broken target), noindex on money pages, XML sitemap (exists, valid, in robots.txt, sampled URLs return 200 and are indexable), hreflang (codes, absolute URLs, return links, live targets) | "Homepage canonical points to a 404" (P1) |

#### Severity model (no vanity scores)

| Level | Meaning | Examples |
|---|---|---|
| **P0** act today | A wrong live fact or a block that removes the business from search | Schema phone differs from the confirmed phone, homepage noindex, robots.txt blocks Googlebot, robots.txt 5xx |
| **P1** this week | Conflicting facts or AI search access problems | Hours in schema differ from the page, AI search bot blocked, price mismatch, invalid JSON-LD, no LocalBusiness schema on a local business |
| **P2** this cycle | Hygiene that weakens trust or crawling | Sitemap missing or with broken URLs, hreflang errors, hours not in schema |
| **P3** backlog | Nice to have, or a policy choice to confirm | No llms.txt, training bots blocked (GPTBot, CCBot), missing canonical |

### Example output (from a real test run, business name removed)

| Severity | Finding | Observed | Expected |
|---|---|---|---|
| P0 | Phone in structured data differs from the confirmed business phone | schema: +965 2296 xxxx | confirmed: +965 2228 xxxx |
| P1 | Opening hours in structured data differ from hours shown on the site | schema: Th 09:00-21:00, Sa 09:00-21:00 | contact page: Th 09:00-15:00, Sa 11:00-19:00 |
| P1 | Address in structured data does not match the address shown on the site | schema street words not on any page: (old building name) | the address on the contact page |
| P2 | Opening hours shown on the site but missing from structured data | Mo-Fr 09:30-19:00, Sa 09:00-18:00, Su closed | openingHoursSpecification |
| P2 | 1 of 10 sampled sitemap URLs return errors | /2020/05/31/reopening/ (500) | every sitemap URL returns 200 |

Each row also has `fix` (what to change, in plain English), `evidence`, `url`, `code`, `category`, `source` (website, Google Business Profile, a directory, or your confirmed facts) and `status: FLAGGED - needs human review`.

The **summary row** per site lists the facts found (schema phones vs visible phones, addresses, hours), the AI crawler table (allowed / blocked per bot, HTTP status with each bot's user agent), llms.txt, sitemap stats, structured data types and what each profile/directory says.

### How to use

1. Paste one website per line in **Business websites**. To compare with listings, add the Google Maps / Business Profile URL and directory URLs on the same line, separated by spaces.
2. Optional: type the owner-confirmed (or Google Business Profile) name, phone, address and hours. Any website or schema value that differs is flagged.
3. Run. Open the **Findings** tab (P0 first), or download `FINDINGS.csv` / `REPORT.md` from the key-value store.

```json
{
  "sites": ["https://example-dental.com https://www.yelp.com/biz/example-dental"],
  "knownPhone": "+1 555 010 0000",
  "knownHours": "Mon-Fri 9am-6pm, Sat 10am-2pm, Sun closed",
  "maxPagesPerSite": 8
}
```

Bulk with different profiles per business: use `businesses`, e.g. `[{"url": "https://clinic-a.com", "gbpUrl": "...", "directoryUrls": ["..."], "phone": "...", "hours": "..."}]`.

### Google Business Profile: honest note

Google Maps pages load their details with JavaScript, and this actor does not log in or scrape Google. When a profile cannot be read you get a P3 row saying so (never a guess). Type the profile's phone, address and hours into the **Confirmed** fields and they are compared with the website and schema. Directory pages (Yelp, Fresha, Yell, TripAdvisor, Zomato and others) are read from their structured data or page text when the site allows it.

### Pricing

**$0.04 per audited site**, charged once when the site's summary row is written. A site includes the homepage, up to 25 key pages (contact, about, locations, prices, services, products), robots.txt, llms.txt, the sitemap with up to 100 sampled URLs, hreflang alternates, canonical targets and the profile/directory URLs you give.

- Free: sites that do not resolve, time out or are blocked by bot protection, invalid input lines and all finding rows.
- 100 sites = $4. Set "Max sites per run" or Apify's maximum cost per run to cap spend.

### Who uses it

- **SEO and marketing agencies:** a fact-based audit for a sales call or client onboarding in minutes, with evidence a client can verify.
- **Local businesses (clinics, salons, restaurants, cleaners, shops):** find out why Google or ChatGPT shows the wrong hours or phone.
- **AEO / GEO specialists:** check that AI search bots can read the site and that the facts they will quote are consistent.
- **Multi-location brands:** catch branch pages with old hours, phones or addresses.

### FAQ

**Is blocking GPTBot a problem?** Not necessarily. GPTBot, ClaudeBot, CCBot and Google-Extended are training crawlers; blocking them is a business choice, so it is a P3 "confirm this is intended". Blocking OAI-SearchBot, ChatGPT-User or PerplexityBot removes you from those AI search answers (P1). Blocking Googlebot or Bingbot is P0.

**Does llms.txt matter?** It is a proposed standard and no major AI search engine has confirmed using it, so a missing file is only P3.

**Why no overall score?** Scores hide what to do. A P0 wrong phone matters more than ten P3 items, so you get a prioritised list with evidence instead.

**Does it fix anything?** No. It reads public pages and flags. A person decides what to change.

**Is it polite to sites?** About 30-60 requests per site, at most 3 at a time, with an identifying user agent. Private and internal addresses are never fetched.

### Done-for-you audit

Want this turned into a prioritised fix plan for your client or your own business, checked by a person, with Google Business Profile and AI answer checks included? **Cygnix Labs runs a $99 agency audit.** Email **info@cygnixlabs.com** with the website.

### More Cygnix Labs actors

- [AEO & AI Visibility Audit](https://apify.com/cygnix_labs/aeo-readiness-checker): page-level AI answer readiness
- [Redirect Checker](https://apify.com/cygnix_labs/redirect-checker): redirect chains, loops and status codes in bulk
- [Broken Backlink Finder](https://apify.com/cygnix_labs/backlink-reclamation-finder): reclaim links pointing at 404 pages
- [Traffic Drop Checker](https://apify.com/cygnix_labs/traffic-drop-emergency-check): Google update or technical break?

### Changelog

- **0.1** (2026-11-01): First release. NAP and day-by-day hours consistency (site, schema, profile, directories, confirmed facts), schema vs visible price/FAQ, AI crawler robots + firewall test, llms.txt, indexing basics, P0-P3 findings with fixes.

# Actor input Schema

## `sites` (type: `array`):

One business per line: the website URL. Optionally add its Google Business Profile / Google Maps URL and directory listing URLs (Yelp, Fresha, Yell, Foursquare, Zomato, Talabat…) on the same line, separated by spaces, to compare name, phone, address and opening hours. Domains without https:// are fine.

## `gbpUrl` (type: `string`):

Optional. Applies to the first site above. Google Maps pages load details with JavaScript, so if they cannot be read the audit tells you and you can type the profile facts in the fields below.

## `directoryUrls` (type: `array`):

Optional. Listing pages for the first site (Yelp, Fresha, Treatwell, Yell, Foursquare, TripAdvisor, Zomato…). Name, phone, address and hours are read from their structured data or page text.

## `knownName` (type: `string`):

Optional. The name as it appears on Google Business Profile or as confirmed by the owner. Mismatches are flagged.

## `knownPhone` (type: `string`):

Optional, e.g. +965 2228 0611. If the website or its schema shows another number, that is flagged as P0.

## `knownAddress` (type: `string`):

Optional. Street address as on Google Business Profile.

## `knownHours` (type: `string`):

Optional, plain English, e.g. "Sun-Wed 9am-9pm, Thu 9am-3pm, Sat 11am-7pm, Fri closed".

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

Advanced: a list of objects for bulk audits, e.g. \[{"url": "https://clinic.com", "gbpUrl": "https://maps.google.com/?cid=…", "directoryUrls": \["https://www.yelp.com/biz/…"], "name": "…", "phone": "…", "address": "…", "hours": "Mon-Fri 9am-5pm"}]. Every field except url is optional.

## `maxPagesPerSite` (type: `integer`):

Homepage plus the most important linked pages (contact, about, locations, prices, services, products). Same price whatever you choose.

## `sitemapSampleSize` (type: `integer`):

URLs sampled evenly from the XML sitemap and checked for errors, redirects and noindex.

## `testBotUserAgents` (type: `boolean`):

Requests the homepage once each as OAI-SearchBot, PerplexityBot, GPTBot and ClaudeBot to spot CDN/WAF rules that block them. Requests come from Apify IPs, not the bots' official IPs, so results are flagged as observations to confirm in your logs.

## `maxSites` (type: `integer`):

Safety cap on billed sites per run.

## `maxConcurrency` (type: `integer`):

How many sites are audited at the same time (each site is read politely, 3 requests at a time).

## `requestTimeoutSecs` (type: `integer`):

Per request.

## Actor input object example

```json
{
  "sites": [
    "https://www.jibladental.com/en",
    "https://www.beilondon.com/ https://www.fresha.com/a/bei-london-london-703-fulham-road-lg07d2ad"
  ],
  "maxPagesPerSite": 8,
  "sitemapSampleSize": 15,
  "testBotUserAgents": true,
  "maxSites": 100,
  "maxConcurrency": 3,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

## `findings` (type: `string`):

No description

## `summaries` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `report` (type: `string`):

No description

## `summary` (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 = {
    "sites": [
        "https://www.jibladental.com/en",
        "https://www.beilondon.com/ https://www.fresha.com/a/bei-london-london-703-fulham-road-lg07d2ad"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cygnix_labs/business-search-ai-visibility-audit").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 = { "sites": [
        "https://www.jibladental.com/en",
        "https://www.beilondon.com/ https://www.fresha.com/a/bei-london-london-703-fulham-road-lg07d2ad",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cygnix_labs/business-search-ai-visibility-audit").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 '{
  "sites": [
    "https://www.jibladental.com/en",
    "https://www.beilondon.com/ https://www.fresha.com/a/bei-london-london-703-fulham-road-lg07d2ad"
  ]
}' |
apify call cygnix_labs/business-search-ai-visibility-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cygnix_labs/business-search-ai-visibility-audit"
        }
    }
}
```

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/SaaR2HrTx68MLfQ6G/builds/NOI6Ad7Xdgn1dyot5/openapi.json
