Skip to main content

Page Identity

The same page shows up in four AI Visibility surfaces, each of which stores the URL it was given:

SurfaceWhere the URL comes from
getAiVisibilityEvaluatedPagesthe URLs page runs were requested for
getAiVisibilityBrandCitationsthe URLs AI providers cited
getAiVisibilityDiscoveredPagesthe URLs SERP discovery returned
getAiVisibilityPageRunsthe URL each individual content evaluation ran on

Those spellings differ. A provider may cite https://example.com/guides/seo-basics?ref=chat, a SERP result may carry https://www.example.com/guides/seo-basics/, and the page run may have been triggered on http://example.com/guides/seo-basics. They are one page.

Every one of these types therefore returns:

FieldDescriptionWhere
pageIdStable, opaque id for the page. Identical across all four surfaces. Safe in a URL path segment.all four
canonicalUrlThe canonical form of the URL. This is the value pageId is derived from.all four
urlA representative raw URL, for linking to the pageall four
titleThe page title as seen in search results, or null (see below)page scores, cited pages, discovered pages

AiVisibilityPageRun carries no title: it describes one evaluation run rather than the page, and its pageId resolves to a page-score, cited-page or discovered-page row that does carry one.

Use pageId to join the surfaces and to key client-side routes and caches. Use url only for display and outbound links.

How the canonical URL is derived​

In order:

  1. Surrounding whitespace and the #fragment are removed.
  2. An http or https scheme is replaced with https — the two are one page. A URL carrying any other explicit scheme (ftp://, android-app://) is left as it is, so it stays a distinct page; a URL with no scheme is treated as https.
  3. A leading www. is removed, the host is lowercased, and a default :80 / :443 port is removed.
  4. Trailing slashes are removed from the path. Path case is preserved, because paths are case-sensitive.
  5. Tracking query parameters are removed. The remaining parameters keep their original order and case.

The parameters removed in step 5 are anything beginning with utm_, plus ref, ref_src, referrer, gclid, gbraid, wbraid, dclid, fbclid, msclkid, yclid, ttclid, twclid, igshid, mc_cid, mc_eid, _ga, _gl, s_kwcid, li_fat_id and epik. Matching is case-insensitive and on whole parameter names, so ?ref=chat is removed while ?refresh=1 is kept.

Parameters that are not in that list are part of the page's identity: https://example.com/p?id=2 and https://example.com/p?id=3 remain two pages.

canonicalUrl uses https for web pages, so for an http-only host it is an identifier rather than a fetchable address. Link to url instead.

Rows are grouped by the canonical URL​

getAiVisibilityEvaluatedPages, getAiVisibilityBrandCitations and getAiVisibilityDiscoveredPages return one row per page, not one row per URL spelling. A page cited once as …?ref=chat and once as … is a single row with totalBrandCitations: 2, and totalCount counts pages.

url on such a row is a representative spelling: the most recent run's URL for a page score, and the best-positioned discovery's URL for a discovered page. It can differ between two windows of the same query, so compare pageId or canonicalUrl — never url — when matching a row against its previousPeriod entry or against a row from another query.

getAiVisibilityPageRuns still returns one row per run; each run carries the pageId of the page it evaluated.

Filtering by page​

getAiVisibilityEvaluatedPages accepts either filter, or both, which combine:

  • pageIds — pageIds returned by any AI Visibility query. A value that is not a page id at all — not base64url, or not encoding a scheme:// URL — is rejected with the error code AI_VISIBILITY_INVALID_PAGE_ID. One that decodes to a URL is canonicalised before matching, like the urls filter, so it resolves to the page it names.
  • urls — any spelling of each page. Each is canonicalised before matching, so http://www.example.com/p/?utm_source=email selects the same page as https://example.com/p.

The deprecated getAiVisibilityPageScores takes the same filters one page at a time, as pageId and url, and pageId takes precedence over url there.

Operation: query GetAiVisibilityEvaluatedPageByPageId( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $aiVisibilityBrandId: ObjectID $pageIds: [String!] ) { getAiVisibilityEvaluatedPages( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId aiVisibilityBrandId: $aiVisibilityBrandId pageIds: $pageIds ) { nodes { pageId canonicalUrl url title totalRuns latestRunAt avgQualityScore totalBrandCitations } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "aiVisibilityBrandId": "QWlWaXNpYmlsaXR5QnJhbmQx", "pageIds": ["aHR0cHM6Ly9leGFtcGxlLmNvbS9ndWlkZXMvc2VvLWJhc2ljcw"] }
GetAiVisibilityEvaluatedPageByPageIdTry in Explorer
GraphQL
query GetAiVisibilityEvaluatedPageByPageId(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$aiVisibilityBrandId: ObjectID
$pageIds: [String!]
) {
getAiVisibilityEvaluatedPages(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
aiVisibilityBrandId: $aiVisibilityBrandId
pageIds: $pageIds
) {
nodes {
pageId
canonicalUrl
url
title
totalRuns
latestRunAt
avgQualityScore
totalBrandCitations
}
}
}

Searching pages​

getAiVisibilityEvaluatedPages, getAiVisibilityBrandCitations, getAiVisibilityDiscoveredPages and getAiVisibilityPageRuns accept search, a case-insensitive substring match on the page's canonical URL:

  • The search text is canonicalised the same way as the stored URL before matching, so a pasted URL finds its page whatever its scheme, www., default port, trailing slashes, #fragment or tracking parameters such as utm_source. Searching for https://www.example.com/blog/ finds every page under https://example.com/blog.
  • Part of a URL, such as example.com/blog or /guides/, matches anywhere in the canonical URL.
  • % and _ match literally.
  • It narrows which pages are returned without changing any page's metrics: a matching page still aggregates every spelling of its URL, and totalCount counts only the matching pages (runs, for page runs).
  • Blank search text is ignored.

getAiVisibilityDiscoveredPages also accepts url, which selects a single page by any spelling of its URL, like the urls filter on evaluated pages and the url filter on page runs.

Titles​

title is the page title as it appeared in search results, taken from the best-positioned SERP discovery of that page in the project. It is current state, not scoped to the query's dateRange — on every surface that returns it, including discovered pages, where all the other fields are windowed. That is deliberate: a page joined across surfaces on pageId must report one title, and a windowed title would flip between a period and its comparison period for a page the project already has a title for.

It is null for a page SERP discovery has never returned — a URL cited by a provider but absent from search results, for example. Citations and page runs store no title of their own, so there is nothing else to fall back to. Treat null as "unknown" rather than deriving a title from the URL.