Skip to main content

Page Runs (Content Evaluation)

Page runs are content evaluation crawls that analyze URLs cited by AI providers. They measure content quality, relevance, brand positioning, and other metrics to help you understand why certain pages are (or aren't) being cited.

How page runs work​

Page runs can be triggered in two ways:

  1. Automatically -- When a prompt run finishes and the project has autoCrawlEnabled = true, citation URLs matching your brand's domains are automatically crawled. URLs are skipped if they were recently crawled within the freshnessThresholdDays window.

  2. Manually -- Trigger a content evaluation for any URL via the API.

Each page run uses the Single Page Requester (SPR) to crawl the URL and run content evaluation metrics.

Status lifecycle​

status is an AiVisibilityPageRunStatus enum, not free text:

StatusTerminalDescription
PendingNoCreated and waiting to be crawled
CrawlingNoCurrently being crawled by SPR
CompletedYesSuccessfully crawled with metrics
FailedYesCrawl failed (see failureCode / failureReason)

Each run also exposes isTerminal, which is true once the run has settled. Prefer it over comparing status against a list of your own: if a status is added later, isTerminal keeps classifying runs correctly and your polling loop still terminates. Poll until isTerminal is true rather than deciding a run has finished because some amount of time has passed -- a run is finished when the API says so, not when a timer expires. A run that never settles is a fault rather than a slow evaluation, and re-triggering the URL reports it (see Trigger a page run).

A settled run never changes on its own. The one exception is a retry of a failed copywriting-agent evaluation sent with the same idempotencyKey: that reuses the existing run rather than creating a second one, so the run you already hold goes back to Pending. It only happens when you ask for it.

Why a run failed​

A failed run carries failureCode, an AiVisibilityPageRunFailureCode enum. Branch on it instead of matching failureReason text, which is prose and can be reworded at any time:

Failure codeMeaning
CrawlRequestUnconfirmedThe crawl request could not be confirmed; anything that started is not linked
ProcessingExhaustedRetriesProcessing was retried to its limit without reaching a result
ProcessingFailedThe crawl itself failed; failureReason carries the underlying reason as prose

CrawlRequestUnconfirmed is worth handling separately from the other two. It deliberately does not say whether a crawl started: the API cannot tell a request that was never published from one whose confirmation was lost. What it does guarantee is that nothing which did start will ever report back to this run, so a page may have been crawled and a credit spent. Treat it as a fault to escalate rather than something to retry silently.

failureCode is null unless status is Failed. It can also be null on a failed run whose failure was not categorised: runs that failed without recording any reason, older runs whose recorded reason cannot be resolved to exactly one code, and — until a follow-up release — failures detected while the page was being processed rather than when the crawl was requested. Treat a null code as "not categorised" rather than as "did not fail", and fall back to failureReason for those.

List page runs​

Retrieve page runs for a project. Optionally filter by URL — the URL is canonicalised before matching, so any spelling of a page returns every run of that page, whichever spelling each run was requested with (see Page Identity):

Operation: query GetAiVisibilityPageRuns( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! ) { getAiVisibilityPageRuns( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId first: 20 ) { nodes { rawId url status isTerminal precisionScore recallScore qualityScore trustScore brandMentionScore brandSentimentScore brandPositionScore failureCode failureReason createdAt updatedAt } pageInfo { hasNextPage endCursor } totalCount } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE" }
GetAiVisibilityPageRunsTry in Explorer
GraphQL
query GetAiVisibilityPageRuns(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
) {
getAiVisibilityPageRuns(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
first: 20
) {
nodes {
rawId
url
status
isTerminal
precisionScore
recallScore
qualityScore
trustScore
brandMentionScore
brandSentimentScore
brandPositionScore
failureCode
failureReason
createdAt
updatedAt
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}

Score fields​

Completed page runs include these numeric scores (0-100 scale):

ScoreDescription
precisionScoreHow precisely the content matches the intended topic
recallScoreHow comprehensively the content covers the topic
uniquenessScoreHow unique the content is compared to other sources
qualityScoreOverall content quality
trustScoreTrustworthiness of the content
brandMentionScoreHow well the brand is mentioned in the content
brandSentimentScoreBrand sentiment within the content
brandPositionScoreHow prominently the brand is positioned
topicalOpportunityScoreOpportunity to improve topical coverage
evergreenHealthScoreHow well the content maintains relevance over time
qdfScoreQuery Deserves Freshness score
gscQueryScoreGoogle Search Console query relevance

Additional fields include boolean metrics (e.g. hasPageChanged, domainInResults), text analysis fields (e.g. primaryPageIntent, topicalOpportunityReasoning), and detailed JSONB evaluation arrays.

Trigger a page run​

Manually trigger a content evaluation for a URL:

Operation: mutation TriggerAiVisibilityPageRun( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $url: String! ) { triggerAiVisibilityPageRun( input: { accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId url: $url } ) { aiVisibilityPageRun { rawId url status isTerminal failureCode failureReason sprRequestId createdAt } alreadyInProgress } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "url": "https://example.com/my-page" }
TriggerAiVisibilityPageRunTry in Explorer
GraphQL
mutation TriggerAiVisibilityPageRun(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$url: String!
) {
triggerAiVisibilityPageRun(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
url: $url
}
) {
aiVisibilityPageRun {
rawId
url
status
isTerminal
failureCode
failureReason
sprRequestId
createdAt
}
alreadyInProgress
}
}
note

Triggering a page run consumes ContentEvals credits from your account. The mutation validates credit availability before creating the run, and fails with AccountCreditsDepleted when none are left.

alreadyInProgress is true when an evaluation was already running for this URL, in which case nothing new was started, no further credit was committed, and aiVisibilityPageRun is that existing run. Only one evaluation runs per URL at a time, so a repeat trigger converges on the one run instead of failing -- treat it as success. Previously this case returned an error, and it now succeeds even when the account has no credits left, since nothing new is started.

The run you get back is not necessarily one this user started. Automatic evaluations -- those created from a citation or from SERP discovery -- occupy the same per-URL slot, so surface this as "this page is already being evaluated" rather than as something started in another tab.

If the existing run has been unfinished for far longer than an evaluation takes, the mutation raises AI_VISIBILITY_PAGE_RUN_ALREADY_IN_PROGRESS instead of converging, with the run's id and status in the error extensions. That run is stuck and is blocking new evaluations of the URL; surface it rather than retrying, since retrying cannot clear it.

An evaluation's credit is only charged when it finishes, so availableCredits(creditAllocationType: ContentEvals) still counts the credit of an evaluation that is currently running. To show a balance that accounts for those, pair it with the runs whose isTerminal is false.

Page scores (aggregated)​

Get aggregated scores across multiple page runs, grouped by URL. Useful for understanding average content quality over time:

Operation: query GetAiVisibilityPageScores( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $dateRange: AiVisibilityDateRangeInput ) { getAiVisibilityPageScores( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId dateRange: $dateRange ) { url avgPrecisionScore avgRecallScore avgQualityScore avgTrustScore avgBrandMentionScore avgBrandSentimentScore avgBrandPositionScore totalRuns latestRunAt } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "dateRange": { "start": "2025-01-01", "end": "2025-01-31" } }
GetAiVisibilityPageScoresTry in Explorer
GraphQL
query GetAiVisibilityPageScores(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$dateRange: AiVisibilityDateRangeInput
) {
getAiVisibilityPageScores(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
dateRange: $dateRange
) {
url
avgPrecisionScore
avgRecallScore
avgQualityScore
avgTrustScore
avgBrandMentionScore
avgBrandSentimentScore
avgBrandPositionScore
totalRuns
latestRunAt
}
}

Page scores return average values for each score field, along with totalRuns and latestRunAt. Results are limited to 10,000 URLs.

Automatic crawling setup​

To enable automatic page run creation when AI providers cite your brand's pages:

  1. Set autoCrawlEnabled: true on your project (see Projects)
  2. Optionally adjust freshnessThresholdDays (default: 7 days)

The system will:

  • Filter citation URLs to match your primary brand's domains (including subdomains)
  • Skip URLs that have a recent page run within the freshness threshold
  • Skip URLs that already have an in-flight page run (pending or crawling)

Schema reference​