ChatGPT Ads Leaderboard
Brands advertising inside ChatGPT answers for a tracked domain's prompts, ranked by prompt coverage.
curl --request GET \
--url 'https://api.aiclicks.io/api/v1/ads/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/ads/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"]["leaderboard"]:
you = " (you)" if row["isYou"] else ""
print(f"{row['brand']:<20}{you} coverage={row['coveragePct']:>5}% "
f"({row['coverageNum']}/{row['coverageDen']} prompts, {row['cardsSeen']} cards)")
const url = new URL("https://api.aiclicks.io/api/v1/ads/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.leaderboard) {
console.log(`${row.brand}: ${row.coveragePct}% coverage, ${row.cardsSeen} cards`);
}
{
"data": {
"leaderboard": [
{
"id": "acme",
"brand": "Acme",
"domain": "acme.com",
"color": "#2563EB",
"favicon": "https://acme.com/favicon.ico",
"isYou": true,
"coveragePct": 61.9,
"coverageNum": 26,
"coverageDen": 42,
"prompts": 26,
"cardsSeen": 74
},
{
"id": "globex",
"brand": "Globex",
"domain": "globex.io",
"color": "#DB2777",
"favicon": null,
"isYou": false,
"coveragePct": 45.2,
"coverageNum": 19,
"coverageDen": 42,
"prompts": 19,
"cardsSeen": 51
}
],
"advertiserMap": {
"acme": {
"id": "acme",
"brand": "Acme",
"domain": "acme.com",
"color": "#2563EB",
"favicon": "https://acme.com/favicon.ico",
"isYou": true,
"coveragePct": 61.9,
"coverageNum": 26,
"coverageDen": 42,
"prompts": 26,
"cardsSeen": 74
}
},
"topics": ["Analytics", "Onboarding", "Pricing"],
"brandOptions": [
{ "id": "you", "brand": "Your brand", "domain": "acme.com", "color": "#2563EB", "count": 74 },
{ "id": "globex", "brand": "Globex", "domain": "globex.io", "color": "#DB2777", "count": 51 }
],
"promptOptions": [
{ "id": "b0a1...c9", "text": "best analytics tool for startups" }
]
},
"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 advertiser competing for your domain's tracked prompts inside ChatGPT, ranked by how many of those prompts each brand's ads covered. Alongside the ranked list you get lookup maps and the filter option lists (topics, brands, prompts) the dashboard uses to slice the view. Use it to answer "who is advertising against me on ChatGPT, and how widely?" in one call.
Rows are sorted by coveragePct descending, breaking ties by cardsSeen then brand name. Your own brand, when present, is flagged with isYou: true.
domain_id is a required query parameter. Use `` 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 ``. Omitting this returns 400.
Look-back window, 1–365. Defaults to 30. Only responses created within the window are ranked.
Response
The leaderboard payload plus the lookup maps and filter option lists.
Ranked advertiser rows, sorted by coveragePct descending (ties broken by cardsSeen descending, then brand name). Advertisers with no ads in the window are absent.
The same rows as leaderboard, keyed by brand id for O(1) lookup. Values are identical objects to the array entries.
Topic names defined for the domain, sorted alphabetically. Populates the topic filter dropdown. Array of strings.
Brand filter options derived from the leaderboard, in the same order. Your own brand collapses to id: "you" / brand: "Your brand".
Every analyzable prompt for the domain, sorted by text — populates the prompt filter dropdown regardless of whether the prompt drew ads.
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 30 minutes server-side per (domain_id, days). Newly completed analyses appear after the TTL expires.
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 ads ran in the window, the response is a successful 200 with an empty leaderboard. topics and promptOptions still list the domain's configured filters:
{
"data": {
"leaderboard": [],
"advertiserMap": {},
"topics": ["Analytics", "Onboarding", "Pricing"],
"brandOptions": [],
"promptOptions": [
{ "id": "b0a1...c9", "text": "best analytics tool for startups" }
]
},
"domain_id": "8f1d3c0a-2f9b-4c11-9b80-7a82e1f0c3f3",
"days": 30,
"generated_at": "2026-06-17T10:00:11.218Z"
}