Model Leaderboard
Per-channel performance ranking for a tracked domain across every AI search engine.
curl --request GET \
--url 'https://api.aiclicks.io/api/v1/model-leaderboard?domain_id=8f1d3c0a-2f9b-4c11-9b80-7a82e1f0c3f3&days=30' \
--header 'Authorization: Bearer ak_live_xxx'
import httpx, os
resp = httpx.get(
"https://api.aiclicks.io/api/v1/model-leaderboard",
params={
"domain_id": "8f1d3c0a-2f9b-4c11-9b80-7a82e1f0c3f3",
"days": 30,
},
headers={"Authorization": f"Bearer {os.environ['AICLICKS_API_KEY']}"},
)
resp.raise_for_status()
for row in resp.json()["data"]["items"]:
pos = row["avg_position"] if row["avg_position"] is not None else "n/a"
print(f"{row['model']:<14} visibility={row['visibility']:>6.2f}% avg_position={pos}")
const url = new URL("https://api.aiclicks.io/api/v1/model-leaderboard");
url.searchParams.set("domain_id", "8f1d3c0a-2f9b-4c11-9b80-7a82e1f0c3f3");
url.searchParams.set("days", "30");
const resp = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.AICLICKS_API_KEY}` },
});
const { data } = await resp.json();
for (const row of data.items) {
const pos = row.avg_position ?? "n/a";
console.log(`${row.model}: visibility=${row.visibility}% avg_position=${pos}`);
}
{
"data": {
"items": [
{ "model": "Perplexity", "visibility": 32.38, "avg_position": 2.92 },
{ "model": "ChatGPT", "visibility": 27.55, "avg_position": 3.40 },
{ "model": "Gemini", "visibility": 18.20, "avg_position": 4.10 },
{ "model": "AI Overviews", "visibility": 10.79, "avg_position": 5.10 },
{ "model": "Claude", "visibility": 6.40, "avg_position": null }
]
},
"domain_id": "8f1d3c0a-2f9b-4c11-9b80-7a82e1f0c3f3",
"days": 30,
"generated_at": "2026-06-17T10:00:11.218Z"
}
{
"detail": "domain_id query parameter is required. List the domains this key can access with GET /api/v1/domains, then pass ?domain_id=<uuid>."
}
{
"detail": "Invalid or revoked API key"
}
{
"detail": "API access is not enabled for this team. Contact support@aiclicks.io."
}
{
"detail": "Domain not found"
}
{
"detail": "Too many requests. Please try again later."
}
Returns one row per AI search channel — ChatGPT, Perplexity, Gemini, AI Overviews, AI Mode, Claude, Grok, Copilot — ranking each by how prominently your brand appears in that channel's responses over the lookback window. Use it to answer "which AI search engine surfaces my brand best?" and "where should I invest first?" in one call.
The list is sorted by visibility descending.
domain_id is a required query parameter. Use GET /api/v1/domains to discover which domains the calling key can access.
Authorizations
Your API key formatted as Bearer ak_live_<your-key>. Create one in the dashboard under Settings → Developers.
Optional UUID for log correlation. If omitted, we generate one and echo it back in the response.
Query parameters
UUID of the domain. Find domains via GET /api/v1/domains. Omitting this returns 400.
Look-back window, 1–365. Defaults to 30. Snapped to the nearest known bucket: 1, 7, 14, 30, 90, 180, 270, or 365 days.
Response
The leaderboard payload.
One row per AI search channel. Sorted by visibility descending. A channel with zero responses in the window is omitted entirely — not returned with visibility: 0.
Display name of the AI search channel. One of: "ChatGPT", "Perplexity", "Gemini", "AI Overviews", "AI Mode", "Claude", "Grok", "Copilot". Raw model IDs (gpt-4o-search, etc.) are not returned — all variants of a provider collapse into the channel.
Percentage of this channel's responses where the brand was mentioned at least once, 0–100, rounded to two decimals. Computed per-channel, so values do not sum to 100 across rows — each row is an independent "how often did I show up here" reading.
Average rank where the brand appeared in this channel's cited / answered list, weighted by response volume. null when the brand never appeared in any cited list in this window — null does not mean position 0.
Echo of the requested domain.
Echo of the requested window.
ISO-8601 timestamp of when the server produced (or cached) this response.
Response headers
| Header | Description |
|---|---|
X-Cache | HIT or MISS. Indicates whether the response came from cache. |
X-Request-Id | Unique request id. Echoes incoming if you set one. |
X-RateLimit-Limit | Max requests per minute for this key. |
X-RateLimit-Remaining | Requests remaining in current minute. |
X-RateLimit-Reset | Unix epoch seconds when the window resets. |
Caching
Cached for 1 hour per (domain_id, days). Newly completed analyses appear after the TTL expires.
How the leaderboard is calculated
For each AI channel in the window:
-
Count every model response that ran on a tracked prompt and resolves to this channel after rollup — that's the denominator.
-
Count responses where the brand appears at least once — that's the numerator for
visibility. -
Average the
positionfield across responses where the brand actually appeared (position > 0) — that'savg_position. -
Drop channels with zero responses, sort by
visibilitydescending.
A response with five brand mentions counts as one — visibility is presence-based, not frequency.
Errors
domain_id query parameter missing. Body points you at /api/v1/domains.
Missing, malformed, or revoked API key.
Team's developer_access flag is off, the domain belongs to a team your API key is not scoped to, or your allowed_domains allowlist excludes it.
domain_id is malformed, does not exist, or your user is not a member of its team.
Rate limit exceeded. Inspect the Retry-After header for how long to wait.
Empty result
If no analyses ran in the window, the response is a successful 200 with an empty list:
{
"data": { "items": [] },
"domain_id": "8f1d3c0a-2f9b-4c11-9b80-7a82e1f0c3f3",
"days": 30,
"generated_at": "2026-06-17T10:00:11.218Z"
}