PendiumDocs

Trigger a Scan

Start an AI visibility scan for any brand across ChatGPT, Claude, Gemini, AI Overviews, and, on Pro, Perplexity.

POST /api/visibility/scan

Authentication: Required. An API key whose account is in read-only mode gets a 403 with code: "READ_ONLY" — scans are a write + spend, so they're refused. A free-plan key gets a 403 with code: "PLAN_UPGRADE_REQUIRED" before anything else is checked. A paid key rescanning a group-shared brand whose owner is on the free plan gets a 403 with code: "OWNER_PLAN_UPGRADE_REQUIRED": the scan bills the brand's owner, so the owner needs to upgrade.

Start a visibility scan that checks how AI platforms perceive and recommend a brand. You can scan a new URL (which creates a brand agent if one doesn't exist) or re-scan an existing agent by ID.

The scan runs in the background — this endpoint returns immediately. Use Poll Scan Status to check when results are ready.

Request body

{
  "url": "https://example.com",
  "mode": "batch",
  "maxQueries": 30
}
FieldTypeRequiredDefaultDescription
urlstringOne of url or syntheticId—Website URL to scan. If no agent exists for this domain yet, one is created automatically.
syntheticIdintegerOne of url or syntheticId—ID of an existing brand agent to re-scan. Use Account to find your agent IDs.
modestringNo"batch"Both "batch" and "full" scan every platform on your plan with fast, cost-effective models. (Premium-model runs are an advanced opt-in, not selected by "full" on its own.)
maxQueriesintegerNoyour plan's capHow many queries to run across all platforms. Capped at the brand owner's plan: 50 on Starter, 160 on Pro, 200 with an enterprise plan. Leave it out to run the plan's full cap. 20–40 is the sweet spot for coverage vs. cost.
platformConfigobjectNo—Per-platform overrides, e.g. { "Gemini": { "enabled": false } }. Your plan's platforms are on by default: ChatGPT, Claude, Gemini, and AI Overviews on Starter, plus Perplexity on Pro. Grok, DeepSeek, Qwen, MiniMax, Nemotron, and Llama require the enterprise plan. See Platforms by plan.
publishToBrandIndexbooleanNofalseSet to true to create or update a public Brand Index page for this brand at /brands/{slug}. Leaving it off doesn't create a page, but a brand that already has one keeps showing its newest scan there.
webhookUrlstringNo—HTTPS URL we POST the results to when the scan finishes, so you don't have to poll. Max 2048 chars; must be HTTPS and public (private/internal addresses are rejected). The response echoes webhookRegistered: true when accepted. See Webhook callback.

Platforms by plan

PlanPlatforms each scan runs
Free (web app only)Gemini, AI Overviews
StarterChatGPT, Claude, Gemini, AI Overviews
ProChatGPT, Claude, Gemini, AI Overviews, Perplexity
EnterpriseYour plan's platforms (at least the Starter four), plus Grok, DeepSeek, Qwen, MiniMax, Nemotron, and Llama when you enable them in platformConfig (e.g. { "Grok": { "enabled": true } })

The platforms and the query cap follow the plan of the account that owns the brand, whoever calls the API. If you request a platform your plan doesn't include, it's dropped and the scan still runs on your plan's platforms. scanScope.droppedEnterprisePlatforms lists what was skipped (the field name predates Perplexity moving to Pro), scanScope.enterpriseNotice says which plan each one needs, and scanScope.enabledPlatforms lists what the scan runs on.

How it works

Scanning an existing agent (syntheticId):

  1. Verifies you own the agent
  2. Checks your credit balance
  3. If a scan is already running, returns its progress instead of starting a new one
  4. Otherwise, kicks off the scan and returns immediately

Scanning a new URL (url):

  1. Extracts the domain from your URL
  2. Checks if you already have an agent for that domain — if so, re-scans it
  3. If not, creates a new brand agent (scrapes the site, builds a knowledge base) and starts the scan

A public Brand Index page is only created when you send publishToBrandIndex: true. The reportUrl link works for anyone who has it, whatever this setting.

Example

curl -X POST https://pendium.ai/api/visibility/scan \
  -H "Content-Type: application/json" \
  -H "x-api-key: pk_live_xxxxxxxxxxxx" \
  -d '{"url": "https://example.com", "mode": "batch", "maxQueries": 30}'

Response

{
  "success": true,
  "syntheticId": 1234,
  "agentName": "Example",
  "scanTriggered": true,
  "isNewAgent": false,
  "mode": "batch",
  "maxQueries": 30,
  "message": "Scan triggered. Poll GET /api/visibility/scan?syntheticId=1234 for results."
}

If a scan is already running:

{
  "success": true,
  "scanAlreadyRunning": true,
  "syntheticId": 1234,
  "progress": {
    "completedQueries": 12,
    "totalQueries": 30
  }
}

If you're out of credits:

{
  "success": false,
  "error": "Insufficient credits",
  "creditBalance": 0,
  "upgradeUrl": "https://pendium.ai/pricing"
}

If scans are paused for maintenance: during a platform upgrade we occasionally pause all new scans. New triggers return 503 with code: "SCANS_PAUSED" and a short message — retry once it lifts (existing scans and polling/reports are unaffected):

{
  "error": "Visibility scans are temporarily paused for maintenance. Please try again shortly.",
  "code": "SCANS_PAUSED"
}

Webhook callback

If you passed webhookUrl, we POST the finished results there when the scan completes, so you don't have to poll. The payload describes the scan that just ran:

{
  "success": true,
  "syntheticId": 1234,
  "scanId": "8f3c…",
  "overallScore": 62,
  "visibilityLevel": "moderate",
  "summary": "…",
  "platformScores": [],
  "topCompetitors": [],
  "recommendations": [],
  "reportUrl": "https://pendium.ai/r/abc12345"
}

success reflects the real outcome of that run. If the scan couldn't reach any AI engine (a transient provider/network failure), success is false and overallScore is 0 — don't ingest the zero as a real result; trigger another scan. The same condition surfaces as scanFailed: true when you poll.

Per-platform honesty follows the same rule: a platformScores entry for a platform the scan didn't query (a scoped or single-engine run) comes back with queried: false and a null score, not a 0.

On this page