# AI Search Visibility Tracker (`steadfeed/ai-search-visibility-tracker`) Actor

Ask ChatGPT, Perplexity, Claude and Gemini the questions your buyers ask and see how often your brand is mentioned, its rank in the answer, competitor share of voice and cited domains. Bring your own API keys. By Steadfeed.

- **URL**: https://apify.com/steadfeed/ai-search-visibility-tracker.md
- **Developed by:** [Steadfeed](https://apify.com/steadfeed) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 answer analyzeds

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

## AI Search Visibility Tracker

**Does ChatGPT or Perplexity recommend your brand?** This Actor asks ChatGPT (OpenAI API) and Perplexity the questions your buyers ask. It then measures how often your brand is named, where it ranks against your competitors, which websites the AI cites, and the tone of the mention. Schedule it weekly to track your AI search visibility (GEO / AEO) over time.

By Steadfeed.

### How it works

1. You list your brand, a few competitors and the questions buyers ask (or a category and use cases, and the Actor writes the questions).
2. The Actor sends each question to each AI you select, using **your own API keys**.
3. Every answer is analysed. You get one dataset row per answer, plus a `SUMMARY` record with the totals for each AI.

### Bring your own API keys (required)

AI usage is billed **by OpenAI or Perplexity directly to your key**. This Actor never uses keys of its own. Keys are Apify secret inputs: they are encrypted, sent only to the provider's official API, and never logged or written to the output.

| Provider | Input field | Default model | Citations | Get a key |
|---|---|---|---|---|
| `openai` (ChatGPT) | `openaiApiKey` | `gpt-6-luna` | URLs written in the answer | https://platform.openai.com/api-keys. Add a few dollars of prepaid credit under Billing. |
| `perplexity` | `perplexityApiKey` | `sonar` (Agent API, "fast" effort, web search) | yes, the sources it searched | https://console.perplexity.ai. Paid credit is required; the minimum top-up is $10. |

You only need a key for the providers you select. You can change models with `models`, for example `{"openai": "gpt-5-mini", "perplexity": "sonar-pro"}`. For Perplexity, `sonar`/`sonar-pro` = effort "fast", `sonar-reasoning-pro` = "low", `sonar-deep-research` = "high". You can also pass `fast`/`low`/`medium`/`high` or a `provider/model` name.

#### What AI usage costs you

Measured on real runs on 2026-11-04. These are provider list prices, so check the provider's pricing page.

| Provider | Typical cost per answer |
|---|---|
| OpenAI `gpt-6-luna` | about $0.0001–0.0003 |
| Perplexity `sonar` (fast) | about $0.0017–0.002 (includes the ~$0.001 web search fee) |

Each row reports `inputTokens`, `outputTokens` and `aiCostUsd`. The `SUMMARY` record has the run total. For Perplexity, the cost is the one Perplexity reports (`provider-reported`). For OpenAI, it is tokens × the published list price (`estimated-list-price`). Your provider bill is the final word.

**Number of AI calls** = prompts × providers × `runsPerPrompt`. For example, 10 prompts × 2 providers × 3 runs = 60 calls. `maxCalls` (default 50, max 500) is a hard cap: if the plan is bigger, the run stops **before making any AI call**.

### Quick start

1. Get an API key for at least one provider (table above).
2. Fill in **Brand**, **Your domain**, **Competitors** and **Prompts**, select the **AI assistants**, and paste the matching key(s).
3. Optional: run once with **Dry run** on. This uses fake answers with no keys and no cost, so you can check the setup and output format.
4. Start the run. Open the **Overview** table in the Output tab, and the `SUMMARY` record in the key-value store.
5. To track over time, save the input as a task and add a weekly schedule.

#### Example input

```json
{
  "brand": "HubSpot",
  "brandDomain": "hubspot.com",
  "competitors": ["Salesforce", "Pipedrive", {"name": "Zoho CRM", "aliases": ["Zoho"], "domain": "zoho.com"}],
  "prompts": ["What is the best CRM for a small business?", "Which CRM would you recommend for a startup? Give me your top 5."],
  "providers": ["openai", "perplexity"],
  "openaiApiKey": "<your OpenAI key>",
  "perplexityApiKey": "<your Perplexity key>",
  "runsPerPrompt": 3
}
```

### Input

| Field | Required | Description |
|---|---|---|
| `brand` | yes | Your brand name. Matching ignores case and uses whole words, so "Acme" does not match "Acmeville". |
| `brandAliases` | | Other names that count as your brand (product names, old names). |
| `brandDomain` | | Your domain, e.g. `acme.com`. Subdomains count as cited. |
| `competitors` | | Names, or objects `{"name", "aliases", "domain"}`. Up to 25. |
| `prompts` | one of | Buyer questions, up to 100. |
| `category` + `keywords` | one of | Used if `prompts` is empty. Generates questions such as "What is the best {category} {keyword}?" and "Which {category} would you recommend {keyword}? Give me your top 5." |
| `providers` | yes | `openai`, `perplexity` or both. |
| `openaiApiKey` / `perplexityApiKey` | for each selected provider | Your keys (secret). |
| `models` | | Model overrides per provider. |
| `runsPerPrompt` | | 1–10 (default 1). AI answers vary, so repeat questions to measure consistency. |
| `sentimentMode` | | `rules` (default, free), `llm` (an extra small call per answer on your OpenAI key, which is not counted in `aiCostUsd`), or `off`. |
| `maxCalls` | | Safety cap on AI calls (default 50). |
| `maxConcurrency`, `maxRetries`, `requestTimeoutSecs` | | Throttling. |
| `dryRun` | | Fake answers: no keys, no AI calls, no AI cost. |

**Strict validation:** the whole input is checked **before any AI call**. If anything is wrong, the run fails with a list of every problem and writes no rows. Problems include a missing key for a selected provider, an unknown field, an invalid domain, a number out of range, your brand listed as a competitor, or a plan above `maxCalls`. A missing key, for example, gives: `perplexityApiKey is missing: provider "perplexity" is selected, so add your own Perplexity API key from https://console.perplexity.ai (or remove "perplexity" from providers, or turn on dryRun to test without keys)`.

### Output

#### Dataset: one row per prompt × provider × run

| Field | Meaning |
|---|---|
| `brandMentioned` | Did the answer name your brand (or an alias)? |
| `brandMentionCount` | How many times it was named. |
| `brandRank` | Your position among the tracked brands (yours + competitors), ordered by first mention. 1 = named first. Empty if not named. Brands you didn't list do not count. |
| `competitorsMentioned`, `competitorMentions` | Which competitors were named, with their count and rank. |
| `citedUrls`, `citedDomains` | Sources the AI cited or linked. |
| `brandDomainCited` | Was your website among the sources? |
| `competitorDomainsCited` | Competitor websites among the sources. |
| `sentiment` | Tone about your brand: `positive` / `neutral` / `negative`. Pros/cons lists are scored on your own entry only, so a balanced "Strengths / Weaknesses" entry is not marked negative. |
| `snippet` | The text around your first mention. |
| `answer` | The full answer. |
| `inputTokens`, `outputTokens`, `aiCostUsd`, `aiCostSource` | AI usage and cost of this call, billed to your key. |
| `status`, `error` | `ok` or `error` with the reason. Other calls continue if one fails. |

#### `SUMMARY` record (key-value store)

For each provider (`perProvider.openai`, `perProvider.perplexity`):

| Metric | Meaning |
|---|---|
| `visibilityShare` | Share of successful answers that named your brand (0–1). |
| `avgBrandRank` | Average `brandRank` over the answers that named you (1 is best). |
| `firstPlaceShare` | Share of successful answers where you were named first. |
| `brandDomainCitedShare` | Share of answers that cited your website. |
| `shareOfVoice` | Each tracked brand's share of all tracked-brand mentions. It adds up to 1 and is empty if every call to that provider failed. |
| `answersMentioning` | Number of answers naming each brand. |
| `sentiment` | Count of positive / neutral / negative answers. |
| `aiCostUsd`, `inputTokens`, `outputTokens` | AI usage for that provider. |

At the top level, you also get `overallVisibilityShare`, `topCitedDomains`, the run's total `aiCostUsd`, and call counts (`totalCalls`, `succeeded`, `failed`).

Failed calls (bad key, no credit, outage) are recorded as error rows and left out of every metric. 429 errors, timeouts and 5xx errors are retried with backoff. The run is marked failed only if **every** call failed.

### Limitations

- API answers can differ from what people see in the ChatGPT or Perplexity apps. The apps use different system prompts, browsing and personalization. Treat the results as a consistent benchmark, not a screenshot of one user's screen.
- OpenAI answers come from the model without live web search, so they show what the model "knows" and usually have no citations. Perplexity searches the web and cites sources.
- Only brands you list are tracked. Rank and share of voice ignore brands you did not list.
- Text matching is literal. Add aliases for nicknames ("Zoho" for "Zoho CRM"). Common-word brand names can over-match.
- Rule-based sentiment is a rough signal. Use `sentimentMode: "llm"` for better labels.
- Answers are random. Use `runsPerPrompt` of 3 or more for decisions.
- Only OpenAI and Perplexity are supported for now.

### FAQ

**Do I need both keys?** No. Select only the providers you have keys for.

**Who pays for the AI calls?** You do, directly to OpenAI or Perplexity, through your own key. This Actor has no built-in keys.

**Is my API key safe?** Keys are Apify secret inputs, encrypted at rest, sent only to the provider's official API, and never logged or saved to the dataset.

**Can I test for free?** Yes. Turn on `dryRun` for fake answers with no keys and no AI calls.

**Why is my Perplexity call failing with 401/403?** The key is wrong, or the account has no paid credit. Top up at console.perplexity.ai. The error row shows the provider's message.

**How do I stop a run from costing too much?** Keep `maxCalls` low. The run will not start any AI call if the plan is bigger. You can also set a maximum run cost in Apify.

**How do I track over time?** Save a task, schedule it weekly, and compare the `SUMMARY` records or export the dataset.

# Actor input Schema

## `brand` (type: `string`):

The brand to track, e.g. "Notion". Matching is case-insensitive and on word boundaries.

## `brandAliases` (type: `array`):

Other names that count as a brand mention (product names, old names, misspellings), e.g. "Notion AI".

## `brandDomain` (type: `string`):

Your website domain, e.g. notion.so. Used to check whether the AI cites your site. Subdomains count.

## `competitors` (type: `array`):

Optional. Either plain names \["Coda", "Obsidian"] or objects \[{"name": "Coda", "aliases": \["Coda.io"], "domain": "coda.io"}]. Up to 25.

## `prompts` (type: `array`):

Questions to ask each AI, written the way a buyer would ask. Up to 100. Leave empty to generate prompts from category + keywords.

## `category` (type: `string`):

Used only when Prompts is empty, e.g. "note-taking app". Combined with templates like "What is the best {category}?".

## `keywords` (type: `array`):

Optional use cases combined with the category, e.g. "for small teams", "for students".

## `providers` (type: `array`):

Which AI assistants to ask. Each selected provider needs your own API key below. AI usage is billed by that provider to your key.

## `openaiApiKey` (type: `string`):

Required if OpenAI is selected. Create one at https://platform.openai.com/api-keys (needs a few dollars of prepaid credit). Billed by OpenAI to you; stored encrypted by Apify and never written to the output.

## `perplexityApiKey` (type: `string`):

Required if Perplexity is selected. Create one at https://console.perplexity.ai (paid credit required; $10 minimum top-up). Billed by Perplexity to you; stored encrypted and never written to the output.

## `models` (type: `object`):

Optional overrides. Defaults: {"openai": "gpt-6-luna", "perplexity": "sonar"}. For Perplexity, "sonar" = Agent API effort "fast"; you can also use "low", "medium" or a provider/model id.

## `runsPerPrompt` (type: `integer`):

AI answers vary from one ask to the next. Ask each prompt this many times per provider to measure how consistently your brand appears (3 is a good weekly setting).

## `sentimentMode` (type: `string`):

How to label the tone around the brand mention.

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

Calls in flight at once, across providers.

## `maxRetries` (type: `integer`):

Retries on rate limits (429), timeouts and 5xx, with exponential backoff (honours Retry-After).

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

Per-call timeout.

## `maxCalls` (type: `integer`):

Safety cap on prompts × providers × runs per prompt, so a typo can't run up your AI bill. The run stops before any AI call if the plan is bigger than this.

## `dryRun` (type: `boolean`):

Use built-in fake answers instead of calling any AI. No keys needed. For testing the setup and output format.

## Actor input object example

```json
{
  "brand": "Notion",
  "brandAliases": [
    "Notion AI"
  ],
  "brandDomain": "notion.so",
  "competitors": [
    "Coda",
    "Obsidian",
    "Confluence"
  ],
  "prompts": [
    "What is the best note-taking app for small teams?",
    "Which tools are good alternatives to Confluence for a startup wiki?"
  ],
  "providers": [
    "openai",
    "perplexity"
  ],
  "models": {
    "openai": "gpt-6-luna",
    "perplexity": "sonar"
  },
  "runsPerPrompt": 1,
  "sentimentMode": "rules",
  "maxConcurrency": 3,
  "maxRetries": 3,
  "requestTimeoutSecs": 90,
  "maxCalls": 50,
  "dryRun": false
}
```

# Actor output Schema

## `answers` (type: `string`):

Per-answer rows: brand mention, rank, competitors, citations, sentiment.

## `fullAnswers` (type: `string`):

Full answer text and cited URLs.

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

SUMMARY record: visibility share, average rank, share of voice and top cited domains per provider.

# 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 = {
    "brand": "Notion",
    "brandAliases": [
        "Notion AI"
    ],
    "brandDomain": "notion.so",
    "competitors": [
        "Coda",
        "Obsidian",
        "Confluence"
    ],
    "prompts": [
        "What is the best note-taking app for small teams?",
        "Which tools are good alternatives to Confluence for a startup wiki?"
    ],
    "providers": [
        "openai",
        "perplexity"
    ],
    "models": {
        "openai": "gpt-6-luna",
        "perplexity": "sonar"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfeed/ai-search-visibility-tracker").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 = {
    "brand": "Notion",
    "brandAliases": ["Notion AI"],
    "brandDomain": "notion.so",
    "competitors": [
        "Coda",
        "Obsidian",
        "Confluence",
    ],
    "prompts": [
        "What is the best note-taking app for small teams?",
        "Which tools are good alternatives to Confluence for a startup wiki?",
    ],
    "providers": [
        "openai",
        "perplexity",
    ],
    "models": {
        "openai": "gpt-6-luna",
        "perplexity": "sonar",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("steadfeed/ai-search-visibility-tracker").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 '{
  "brand": "Notion",
  "brandAliases": [
    "Notion AI"
  ],
  "brandDomain": "notion.so",
  "competitors": [
    "Coda",
    "Obsidian",
    "Confluence"
  ],
  "prompts": [
    "What is the best note-taking app for small teams?",
    "Which tools are good alternatives to Confluence for a startup wiki?"
  ],
  "providers": [
    "openai",
    "perplexity"
  ],
  "models": {
    "openai": "gpt-6-luna",
    "perplexity": "sonar"
  }
}' |
apify call steadfeed/ai-search-visibility-tracker --silent --output-dataset

```

## MCP server setup

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

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/lgj095X3MdnmfNBQe/builds/xY1c7jilQUbbWSIF8/openapi.json
