Skip to main content
POST
Start a competitor scan

Authorizations

Authorization
string
header
required

Partner API key as a Bearer token: Authorization: Bearer gsk_live_.... Live keys (gsk_live_) run real scans; test keys (gsk_test_) run request validation then return deterministic sandbox mocks (sandbox: true, nothing persisted, no rate-limit consumption). Keys are issued per partner and are revokable.

Body

application/json
company
object
required
tier

"basic" (default) covers the core AI assistant set. "full" adds the consumer AI search surfaces — Bing Copilot, Google AI Mode, and Google AI Overviews (copilot, google_ai_mode, google-aio). "instant" and "non-instant" are accepted legacy aliases of "basic" and "full" respectively. An array requests multiple packages: ["basic", "full"] runs both. Full/both requires per-partner entitlement (403 tier_not_allowed otherwise). Billing follows the surfaces actually requested: a full request whose llm_set includes none of the full-only surfaces is billed as basic.

Available options:
basic,
full,
instant,
non-instant
llm_set
enum<string>[]

Explicit provider slugs to dispatch. Omit for the tier default: basic = [chatgpt, gemini, perplexity]; full/both = the basic default plus [copilot, google_ai_mode, google-aio]. The basic package accepts only the 6 assistant slugs (chatgpt, claude, gemini, perplexity, deepseek, grok); the 3 consumer-search surfaces are Full-tier only and are rejected with 400 on basic requests.

Available options:
chatgpt,
claude,
gemini,
perplexity,
deepseek,
grok,
copilot,
google_ai_mode,
google-aio
callback_url
string<uri>

Webhook URL for progressive delivery. When set, you receive up to two signed webhook fires per scan: an optional best-effort scan.preliminary, then exactly one terminal fire — scan.completed if the scan produced a result, scan.failed if it did not. See the webhooks section; note the two terminal events carry DIFFERENT payload shapes.

client_ref
string

Opaque reference echoed back on 202 responses, for request correlation.

Maximum string length: 500

Response

The scan reached a TERMINAL state inside the sync-wait window. Two bodies: a full result (status: "completed"), or a PartnerScanFailedOutput with a required error block when the scan failed. Check status — or the presence of error — before reading any score field; a 200 says a terminal payload is enclosed, not that the scan succeeded. The failed body echoes client_ref like every other response from this endpoint. Test keys (gsk_test_) always return 200 with a deterministic completed mock plus sandbox: true.

The single scan result contract. Returned by POST (sync completion), GET status polls, and inside both webhook events — partners always see this one shape regardless of entry point. Sandbox (test-key) responses add a top-level sandbox: true extension field.

scan_id
string<uuid>
required

Stable scan identifier.

tier
enum<string>
required

Effective tier of the scan. A request for both packages reports as "full" (the run includes the Full-tier surfaces). See the request tier field for how this can differ from the tier you requested (EXPX-1582 billing-follows-what-ran).

Available options:
basic,
full
status
string
required

Scan lifecycle status. "completed" = final result. "preliminary" = mid-run snapshot: geo_score/band are computed live from the observations collected so far (best-effort, may change). "failed" = terminal: no result will be produced (the scan is not billed and does not consume your monthly allowance) — the body is a PartnerScanFailedOutput and its required error block carries the machine-readable reason, on the status endpoint and the scan.failed webhook alike. Other values (e.g. "processing") mirror internal lead state.

Examples:

"completed"

"preliminary"

"processing"

"failed"

company
object
required
generated_at
string<date-time> | null
required

When the final result was assembled. Null while the scan is in flight.

llms_used
string[]
required

Provider IDs dispatched for this scan. NOTE: these are provider IDs, not request slugs — the request slug "chatgpt" appears here as "openai"; all other slugs are identical to their provider ID.

prompts_count
integer
required

Number of prompts run per provider.

geo_score
number | null
required

Overall GEO visibility score (0-100). On a preliminary payload this is a live mid-run computation and may change in the final result. Null until at least one provider has reported.

band
string | null
required

Qualitative band derived from geo_score (e.g. "emerging").

competitors
object[]
required

At most 10 eligible competitor groups on newly generated results, strongest first. Eligibility, grouping and priority scores use the full assessed pool and all observation denominators before this presentation limit. Fewer than 10 may qualify; all accepted member scopes within each returned group are retained. competitor_quality counts describe the full assessment, not this list length. Previously saved deliveries remain unchanged and may contain more than 10 groups.

coverage
object
required

Per-scan provider request/response coverage — how much of what was requested actually reported a real (non-errored) observation. Present on BOTH preliminary and final output. While status is "preliminary", providers_missing reads as "still pending"; once status is "completed" it reads as "failed" (the run is done, so anything still missing never arrived). All values are provider IDs (see llms_used), not request slugs.

revision
integer
required

Delivery revision identifying the payload generation. 1 = preliminary (or a final with no prior preliminary delivery); 2 = final after a preliminary was already delivered. Deduplicate deliveries on (scan_id, revision) — the same scan_id can legitimately arrive twice with different revisions.

score_confidence
enum<string>
required

Provider coverage behind geo_score; competitor_quality separately reports identity and relevance. The scan achieved. "high" — the scan covered the full set of surfaces the score is calibrated on. "reduced" — coverage was partial; treat the score as indicative and consider re-scanning. "low" — significant coverage gaps; the score should not be relied on. See coverage.providers_missing for exactly which requested surfaces did not report. On a preliminary payload this reflects what has reported so far and can only improve in the final revision.

Available options:
high,
reduced,
low
meta
object
required
competitor_quality
object

Business identity and competitor eligibility, separate from provider coverage. New-version unavailable or unresolved assessments never return unassessed competitors. Legacy results preserve historical lists and are explicitly unassessed. Preliminary results may have no assessment yet.