# AI Visibility Monitor - ChatGPT, Perplexity, Gemini, Claude (`glueworks/ai-visibility-monitor`) Actor

Monitor how visible your brand is in AI search answers (ChatGPT via OpenAI web search, Perplexity Sonar, Gemini grounding, Claude). Tracks mentions, list rank, sentiment, competitors and citations - with built-in run-over-run diff and webhook alerts.

- **URL**: https://apify.com/glueworks/ai-visibility-monitor.md
- **Developed by:** [KAHA Chen](https://apify.com/glueworks) (community)
- **Categories:** AI, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $25.00 / 1,000 visibility checks

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## AI Visibility Monitor — ChatGPT, Perplexity, Gemini, Claude

> **Tool card (for AI agents & quick evaluation)**
>
> **What it does:** Asks your customer-intent prompts (e.g. *"best RSS monitoring tools"*) on up to four AI answer engines via their **official APIs** — OpenAI (ChatGPT-grade answers with web search), Perplexity Sonar, Gemini with Google Search grounding, and Claude with web search — then analyzes each answer for **brand mentions, list rank, sentiment, competitor mentions, and cited domains**. It **diffs every result against the previous run** and pushes an alert to your **webhook** when your visibility changes.
>
> **When to use:**
> - Track whether AI assistants recommend your brand/product — and at what rank — for the questions your buyers actually ask.
> - Get a Slack/Discord/n8n alert the moment your brand appears in, drops out of, or moves within an AI answer.
> - Watch which competitors AI engines recommend instead of you, and which domains they cite (your GEO/AEO target list).
>
> **Inputs:** `brandTerms` (brand + aliases), `prompts` (questions to ask), `platforms` (`openai`|`perplexity`|`gemini`|`claude`), per-platform API keys (BYOK, stored encrypted), optional `competitorTerms`, `webhookUrl` + `webhookFormat`, `language`, `maxChecksPerRun`. **Outputs:** one dataset record per prompt × platform check plus a final `run_summary`; visibility changes are POSTed to the webhook.
>
> **Key caveats:** the first run only establishes a baseline (no alerts) — run it on a schedule. With no API keys configured it runs in a free **demo mode** with mock answers. "ChatGPT" data comes from OpenAI's official search-grounded Responses API, not from scraping the ChatGPT consumer UI.

Your customers stopped asking Google and started asking ChatGPT, Perplexity, Gemini, and Claude. This Actor tells you — on a schedule, with alerts — **whether those AI answers mention you, where you rank, how you're framed, and who gets recommended instead.**

Unlike one-shot "AI brand audit" tools, this is a **monitor**: it remembers the previous run, computes the diff, and pings your webhook only when something actually changed.

### Why monitor AI visibility? (GEO / AEO in one paragraph)

An estimated majority of product research now touches an AI assistant before a search engine results page. When someone asks *"what's the best invoicing tool for freelancers"*, the AI's answer is the new page one — and there are only ~5 slots. **Generative Engine Optimization (GEO)** / **Answer Engine Optimization (AEO)** starts with measurement: you can't improve your share of AI answers if you don't know your current rank, the sentiment around your mentions, or which sources the engines cite (those citations are exactly the sites where you need coverage). AI answers are also **non-deterministic and drift over time** — a one-off audit is stale within weeks, which is why continuous monitoring with change alerts beats a single report.

### What it does

1. **Asks your prompts on each selected platform** via official APIs only — no browser automation, no scraping, nothing that breaks when a UI changes:
   - **OpenAI** — Responses API with the `web_search` tool (search-grounded, with citations)
   - **Perplexity** — Sonar API (citations built in)
   - **Google Gemini** — `generateContent` with Google Search grounding
   - **Anthropic Claude** — Messages API with the web search tool
2. **Analyzes every answer**: is any of your `brandTerms` mentioned (case-insensitive, whole-word)? At which character position and — if the answer is a list — at which **rank**? Is the surrounding context **positive / neutral / negative** (fast rule-based scoring, no extra LLM cost)? Which `competitorTerms` appear? Which **domains are cited**?
3. **Diffs against the previous run** (state persists in the Actor's key-value store): brand appeared, brand dropped, rank changed, competitor set changed.
4. **Alerts your webhook** on every change — generic JSON for n8n / Make / Zapier, or ready-to-post Slack / Discord messages, with 2 automatic retries.
5. **Stays cheap and quiet when nothing changed** — every check is still recorded, but you only get pinged on movement.

Failures are isolated per check, and a platform that fails 3 times in a row is skipped for the rest of the run (one bad API key never burns your whole run).

### Input

```json
{
    "brandTerms": ["Apify"],
    "competitorTerms": ["Zyte", "Bright Data"],
    "prompts": [
        "best web scraping and web automation platforms",
        "how do I monitor a competitor's prices automatically"
    ],
    "platforms": ["openai", "perplexity", "gemini", "claude"],
    "openaiApiKey": "sk-...",
    "perplexityApiKey": "pplx-...",
    "geminiApiKey": "AIza...",
    "anthropicApiKey": "sk-ant-...",
    "webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
    "webhookFormat": "slack"
}
````

- **BYOK (bring your own keys):** you only need keys for the platforms you select. All key fields are encrypted secrets. Model names are configurable per platform (`openaiModel`, `perplexityModel`, `geminiModel`, `anthropicModel`) and default to the cheapest suitable tier.
- **No keys at all?** The Actor runs in **demo mode** with clearly labeled mock answers (`demoMode: true`) so you can see the full output shape for free.
- **Cost guardrail:** `maxChecksPerRun` caps prompt × platform combinations per run (default 100, hard limit 500).

### Output

One record per prompt × platform check:

```json
{
    "recordType": "check",
    "prompt": "best web scraping and web automation platforms",
    "platform": "perplexity",
    "model": "sonar",
    "checkedAt": "2026-07-25T12:00:00+00:00",
    "brandMentioned": true,
    "brandFirstPosition": 214,
    "brandRank": 2,
    "sentiment": "positive",
    "competitorsMentioned": ["Bright Data"],
    "citations": ["https://example.com/top-scrapers"],
    "citationDomains": ["example.com"],
    "answerExcerpt": "There are several great options...",
    "changes": [
        { "changeType": "rank_changed", "previousRank": 1, "newRank": 2 }
    ],
    "isBaseline": false,
    "demoMode": false,
    "webhookDelivered": true
}
```

Change types: `brand_appeared`, `brand_dropped`, `rank_changed`, `competitors_changed`. Every run ends with a `run_summary` record (`checksRun`, `changesDetected`, `checkErrors`, ...), so the dataset is never empty and your schedules stay green.

### Pricing (pay per event)

- **Actor start** — small flat fee per run.
- **Visibility check** — **$0.025 per prompt × platform check** (BYOK — you pay the AI platforms directly at their API rates, typically $0.005–$0.03/check). Demo-mode mock checks are free.

For comparison, the leading AI brand-monitoring Actor charges **$0.08 per check** with managed keys — with your own keys this Actor lands at roughly a third of that, and it's the one that also does the diffing and alerting for you. A typical setup (20 prompts × 3 platforms, weekly) costs about **$6.50/month** here vs. a $29/month floor for standalone AI-visibility SaaS.

### Use via MCP / AI Agents

Any AI agent connected to Apify's hosted MCP server at **[mcp.apify.com](https://mcp.apify.com)** (Claude, ChatGPT, Cursor, VS Code, or any MCP-compatible client) can discover and run this Actor:

1. **Discover** it with the `search-actors` tool (e.g. query "ai visibility brand monitor chatgpt perplexity").
2. **Inspect** the input schema with `fetch-actor-details`.
3. **Run** it with `call-actor` using the Actor ID `glueworks/ai-visibility-monitor`.

To pin this Actor as a dedicated tool in your MCP client, connect to:

```
https://mcp.apify.com/?tools=glueworks/ai-visibility-monitor
```

Minimal input for an agent call (demo mode, no keys needed):

```json
{
    "brandTerms": ["Apify"],
    "prompts": ["best web scraping platforms"],
    "platforms": ["openai"]
}
```

**Notes for agents:**

- Without `webhookUrl`, changes are only written to the dataset — read them from there after the run finishes (`changes` array per record).
- The **first run reports zero changes** (baseline). Schedule the Actor (e.g. daily/weekly) against the same task so consecutive runs can diff.
- Provide an API key for every platform you select; platforms without a key are skipped with an error in `run_summary.checkErrors` (unless *no* keys are set at all, which activates demo mode).
- `brandRank` is only set when the answer is a list (numbered/bulleted) and the brand appears in an item; a brand can be `brandMentioned: true` with `brandRank: null` in prose answers.

### Setting it up as a monitor

1. Fill in brand terms, prompts, platforms, keys, and your webhook.
2. Create a **Schedule** in Apify Console (daily or weekly is plenty — AI answers drift over days, not minutes).
3. State lives in a **named, account-scoped** key-value store (`glueworks-ai-visibility-state`), which persists across runs, so consecutive scheduled runs only alert on *changes*. (Named stores belong to the account running the Actor — different Apify users never share one.) Within that store, state is filed under a per-config key (`VISIBILITY-STATE-<hash of your brand/prompt/platform set>`), so separate monitors run under one account keep their baselines isolated.

### Prompt injection & untrusted data

The AI answers this Actor reads are **untrusted** — an attacker can plant
content on the web that a search-grounded model later quotes, and that answer
can contain injection payloads like *"ignore previous instructions"* or fake
`<system>` tags.

**Why this Actor is structurally safe against it:** those answers only ever
flow through **rule-based parsers** — regex mention/rank detection and a
marker-word sentiment scorer — and are **never fed back into any LLM**. There
is no second "reasoning" prompt for an injection to hijack, so a malicious
answer is treated as inert data, not instructions. (The pure `analyze_answer`
and `parse_response` functions are covered by `tests/test_injection.py`, which
pins this property so a future change can't silently reintroduce an
answer → LLM feedback loop.)

Untrusted answer text is also **bounded** before it leaves the Actor: only an
`answerExcerpt` (capped at 800 chars) is stored, and the **webhook payload
never contains raw answer text** — it carries only the operator's prompt, the
platform, and the structured change types.

**Residual risk / what to watch for (agent-oriented):** the analysis is
deterministic, so brand-mention, rank, sentiment, and competitor fields are
trustworthy signals — but `citations`/`citationDomains` and `answerExcerpt` are
**verbatim untrusted content**. If you pipe those into a downstream agent
(e.g. "summarize the excerpts" or "visit the cited domains"), apply your own
injection defenses there and don't auto-follow citation URLs with privileged
tools. The rule-based sentiment is intentionally simple and can be gamed by
keyword-stuffed content; treat it as a heuristic, not ground truth.

### FAQ

**Is this the real ChatGPT / Gemini answer a user would see?**
It's the closest thing available through official channels. "ChatGPT" results come from OpenAI's **Responses API with the web\_search tool** — the same search-grounded model family, but not a scrape of the consumer chat UI (there is no official API for that, and scraping it breaks constantly and violates ToS). Perplexity's Sonar API is the closest to its consumer product. We state this openly because tools that promise "real UI answers" via browser automation are the ones that silently die.

**Why did my first run send no alerts?**
The first run per (prompt, platform) pair establishes a baseline. Alerts start from the second run. To re-baseline, delete the `VISIBILITY-STATE-*` record(s) from the named `glueworks-ai-visibility-state` key-value store.

**How is sentiment determined?**
A fast rule-based scorer over the text window around your brand mention (positive/negative marker words). It's deliberately simple — transparent, free, and stable across runs — rather than burning extra LLM tokens per check.

**What about Google AI Overviews?**
Not included: Google offers no official API for AI Overviews, and scraping SERPs violates our no-scraping rule. A BYOK integration via a third-party SERP API is under consideration — ask in the Issues tab if you need it.

**Do you support managed keys (no BYOK)?**
Not yet — this version is BYOK-only, which is also why the per-check price is low. A managed-key mode (no API keys to bring, higher per-check price) is planned; the `managedMode` input is reserved for it.

**What if one platform's API fails mid-run?**
Each check is isolated; a failing platform is retried on its next check and cut off after 3 consecutive failures. Everything that succeeded is still recorded, and failures appear in `run_summary.checkErrors`.

**Which models are used? Can I change them?**
Defaults are the cheapest search-capable tier of each platform (e.g. `sonar`, `claude-haiku-4-5`). Every model name is a plain input string — paste any newer/bigger model id without waiting for an Actor update.

# Actor input Schema

## `brandTerms` (type: `array`):

Your brand/product name plus aliases and common spellings, e.g. <code>\["Apify", "apify.com"]</code>. A prompt's answer counts as a mention if it contains any of these (case-insensitive, whole-word).

## `competitorTerms` (type: `array`):

Competitor brand names to track alongside yours, e.g. <code>\["Zyte", "Bright Data"]</code>. Each check records which competitors were mentioned; changes in the competitor set trigger alerts too.

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

The questions a potential customer would ask an AI assistant, e.g. <code>"best web scraping platforms"</code> or <code>"how do I monitor competitor prices automatically"</code>. Each prompt is asked on every selected platform (prompt × platform = one check).

## `platforms` (type: `array`):

Which AI answer engines to query, via their official APIs: <code>openai</code> (ChatGPT-grade answers via OpenAI Responses API + web\_search), <code>perplexity</code> (Sonar), <code>gemini</code> (grounding with Google Search), <code>claude</code> (Anthropic web search tool).

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

API key from platform.openai.com (used for the <code>openai</code> platform). Stored encrypted. Leave all keys empty to run a free demo with mock answers.

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

API key from perplexity.ai/settings/api (used for the <code>perplexity</code> platform). Stored encrypted.

## `geminiApiKey` (type: `string`):

API key from Google AI Studio (used for the <code>gemini</code> platform). Stored encrypted.

## `anthropicApiKey` (type: `string`):

API key from console.anthropic.com (used for the <code>claude</code> platform). Stored encrypted.

## `openaiModel` (type: `string`):

Model used with the OpenAI Responses API web\_search tool. Default: <code>gpt-5.6</code>. Any web-search-capable model id works.

## `perplexityModel` (type: `string`):

Perplexity Sonar model. Default: <code>sonar</code> (cheapest). Also: <code>sonar-pro</code>.

## `geminiModel` (type: `string`):

Gemini model used with Google Search grounding. Default: <code>gemini-3.5-flash-lite</code> (cheapest grounding-capable tier).

## `anthropicModel` (type: `string`):

Claude model used with the Anthropic web search tool. Default: <code>claude-haiku-4-5</code> (cheapest).

## `webhookUrl` (type: `string`):

Full http(s) URL to POST an alert to whenever visibility changes vs. the previous run (brand appeared/dropped, rank moved, competitor set changed) — e.g. a Slack incoming webhook or an n8n/Make/Zapier webhook node. Each delivery is retried twice. Leave empty to only store results in the dataset.

## `webhookFormat` (type: `string`):

<code>json</code> sends the full structured change event (best for n8n/Make/Zapier). <code>slack</code> sends <code>{"text": ...}</code>. <code>discord</code> sends <code>{"content": ...}</code>.

## `language` (type: `string`):

Optional. Ask the platforms to answer in this language (e.g. <code>German</code>, <code>zh-TW</code>). Leave empty or <code>en</code> for English.

## `maxChecksPerRun` (type: `integer`):

Safety cap on prompt × platform checks in a single run (cost guardrail). Default 100, hard limit 500.

## Actor input object example

```json
{
  "brandTerms": [
    "Apify"
  ],
  "competitorTerms": [
    "Zyte",
    "Bright Data"
  ],
  "prompts": [
    "best web scraping and web automation platforms"
  ],
  "platforms": [
    "openai",
    "perplexity"
  ],
  "openaiModel": "gpt-5.6",
  "perplexityModel": "sonar",
  "geminiModel": "gemini-3.5-flash-lite",
  "anthropicModel": "claude-haiku-4-5",
  "webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
  "webhookFormat": "json",
  "maxChecksPerRun": 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 = {
    "brandTerms": [
        "Apify"
    ],
    "competitorTerms": [
        "Zyte",
        "Bright Data"
    ],
    "prompts": [
        "best web scraping and web automation platforms"
    ],
    "platforms": [
        "openai",
        "perplexity"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("glueworks/ai-visibility-monitor").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 = {
    "brandTerms": ["Apify"],
    "competitorTerms": [
        "Zyte",
        "Bright Data",
    ],
    "prompts": ["best web scraping and web automation platforms"],
    "platforms": [
        "openai",
        "perplexity",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("glueworks/ai-visibility-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "brandTerms": [
    "Apify"
  ],
  "competitorTerms": [
    "Zyte",
    "Bright Data"
  ],
  "prompts": [
    "best web scraping and web automation platforms"
  ],
  "platforms": [
    "openai",
    "perplexity"
  ]
}' |
apify call glueworks/ai-visibility-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=glueworks/ai-visibility-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "AI Visibility Monitor - ChatGPT, Perplexity, Gemini, Claude",
        "description": "Monitor how visible your brand is in AI search answers (ChatGPT via OpenAI web search, Perplexity Sonar, Gemini grounding, Claude). Tracks mentions, list rank, sentiment, competitors and citations - with built-in run-over-run diff and webhook alerts.",
        "version": "0.1",
        "x-build-id": "tnkrOaDLF7Z1VnHcI"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/glueworks~ai-visibility-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-glueworks-ai-visibility-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/glueworks~ai-visibility-monitor/runs": {
            "post": {
                "operationId": "runs-sync-glueworks-ai-visibility-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/glueworks~ai-visibility-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-glueworks-ai-visibility-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "brandTerms",
                    "prompts",
                    "platforms"
                ],
                "properties": {
                    "brandTerms": {
                        "title": "Brand terms",
                        "type": "array",
                        "description": "Your brand/product name plus aliases and common spellings, e.g. <code>[\"Apify\", \"apify.com\"]</code>. A prompt's answer counts as a mention if it contains any of these (case-insensitive, whole-word).",
                        "items": {
                            "type": "string"
                        }
                    },
                    "competitorTerms": {
                        "title": "Competitor terms (optional)",
                        "type": "array",
                        "description": "Competitor brand names to track alongside yours, e.g. <code>[\"Zyte\", \"Bright Data\"]</code>. Each check records which competitors were mentioned; changes in the competitor set trigger alerts too.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "prompts": {
                        "title": "Prompts to ask the AI platforms",
                        "type": "array",
                        "description": "The questions a potential customer would ask an AI assistant, e.g. <code>\"best web scraping platforms\"</code> or <code>\"how do I monitor competitor prices automatically\"</code>. Each prompt is asked on every selected platform (prompt × platform = one check).",
                        "items": {
                            "type": "string"
                        }
                    },
                    "platforms": {
                        "title": "AI platforms to check",
                        "type": "array",
                        "description": "Which AI answer engines to query, via their official APIs: <code>openai</code> (ChatGPT-grade answers via OpenAI Responses API + web_search), <code>perplexity</code> (Sonar), <code>gemini</code> (grounding with Google Search), <code>claude</code> (Anthropic web search tool).",
                        "items": {
                            "type": "string",
                            "enum": [
                                "openai",
                                "perplexity",
                                "gemini",
                                "claude"
                            ],
                            "enumTitles": [
                                "OpenAI (ChatGPT via web search API)",
                                "Perplexity (Sonar)",
                                "Google Gemini (Search grounding)",
                                "Anthropic Claude (web search)"
                            ]
                        }
                    },
                    "openaiApiKey": {
                        "title": "OpenAI API key",
                        "type": "string",
                        "description": "API key from platform.openai.com (used for the <code>openai</code> platform). Stored encrypted. Leave all keys empty to run a free demo with mock answers."
                    },
                    "perplexityApiKey": {
                        "title": "Perplexity API key",
                        "type": "string",
                        "description": "API key from perplexity.ai/settings/api (used for the <code>perplexity</code> platform). Stored encrypted."
                    },
                    "geminiApiKey": {
                        "title": "Google Gemini API key",
                        "type": "string",
                        "description": "API key from Google AI Studio (used for the <code>gemini</code> platform). Stored encrypted."
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API key",
                        "type": "string",
                        "description": "API key from console.anthropic.com (used for the <code>claude</code> platform). Stored encrypted."
                    },
                    "openaiModel": {
                        "title": "OpenAI model",
                        "type": "string",
                        "description": "Model used with the OpenAI Responses API web_search tool. Default: <code>gpt-5.6</code>. Any web-search-capable model id works.",
                        "default": "gpt-5.6"
                    },
                    "perplexityModel": {
                        "title": "Perplexity model",
                        "type": "string",
                        "description": "Perplexity Sonar model. Default: <code>sonar</code> (cheapest). Also: <code>sonar-pro</code>.",
                        "default": "sonar"
                    },
                    "geminiModel": {
                        "title": "Gemini model",
                        "type": "string",
                        "description": "Gemini model used with Google Search grounding. Default: <code>gemini-3.5-flash-lite</code> (cheapest grounding-capable tier).",
                        "default": "gemini-3.5-flash-lite"
                    },
                    "anthropicModel": {
                        "title": "Claude model",
                        "type": "string",
                        "description": "Claude model used with the Anthropic web search tool. Default: <code>claude-haiku-4-5</code> (cheapest).",
                        "default": "claude-haiku-4-5"
                    },
                    "webhookUrl": {
                        "title": "Webhook URL (alerts)",
                        "type": "string",
                        "description": "Full http(s) URL to POST an alert to whenever visibility changes vs. the previous run (brand appeared/dropped, rank moved, competitor set changed) — e.g. a Slack incoming webhook or an n8n/Make/Zapier webhook node. Each delivery is retried twice. Leave empty to only store results in the dataset."
                    },
                    "webhookFormat": {
                        "title": "Webhook payload format",
                        "enum": [
                            "json",
                            "slack",
                            "discord"
                        ],
                        "type": "string",
                        "description": "<code>json</code> sends the full structured change event (best for n8n/Make/Zapier). <code>slack</code> sends <code>{\"text\": ...}</code>. <code>discord</code> sends <code>{\"content\": ...}</code>.",
                        "default": "json"
                    },
                    "language": {
                        "title": "Answer language",
                        "type": "string",
                        "description": "Optional. Ask the platforms to answer in this language (e.g. <code>German</code>, <code>zh-TW</code>). Leave empty or <code>en</code> for English."
                    },
                    "maxChecksPerRun": {
                        "title": "Max checks per run",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Safety cap on prompt × platform checks in a single run (cost guardrail). Default 100, hard limit 500.",
                        "default": 100
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
