Skip to main content

Projects

A project is the top-level container for AI Visibility monitoring. Each project has a primary brand, a set of AI providers, and a schedule that controls automatic prompt execution.

List projects​

Retrieve all AI Visibility projects for an account. Results include metadata like topic and prompt counts, configured AI providers, and the primary brand domain.

Operation: query GetAiVisibilityProjects($accountId: ObjectID!) { getAiVisibilityProjects( accountId: $accountId first: 10 ) { nodes { id rawId name totalTopics totalPrompts aiProviderTypes primaryBrandDomain primaryBrandRawId scheduleCadence nextScheduleRunAt autoCrawlEnabled freshnessThresholdDays country language createdAt updatedAt } pageInfo { hasNextPage endCursor } totalCount } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ" }
GetAiVisibilityProjectsTry in Explorer
GraphQL
query GetAiVisibilityProjects($accountId: ObjectID!) {
getAiVisibilityProjects(
accountId: $accountId
first: 10
) {
nodes {
id
rawId
name
totalTopics
totalPrompts
aiProviderTypes
primaryBrandDomain
primaryBrandRawId
scheduleCadence
nextScheduleRunAt
autoCrawlEnabled
freshnessThresholdDays
country
language
createdAt
updatedAt
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}

You can search projects by name using the search parameter:

Operation: query SearchAiVisibilityProjects($accountId: ObjectID!, $search: String!) { getAiVisibilityProjects( accountId: $accountId first: 10 search: $search ) { nodes { id name primaryBrandDomain } totalCount } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "search": "my brand" }
SearchAiVisibilityProjectsTry in Explorer
GraphQL
query SearchAiVisibilityProjects($accountId: ObjectID!, $search: String!) {
getAiVisibilityProjects(
accountId: $accountId
first: 10
search: $search
) {
nodes {
id
name
primaryBrandDomain
}
totalCount
}
}

Sorting​

Projects can be sorted by: name, primaryBrandDomain, totalTopics, totalPrompts.

Get a single project​

Fetch a specific project by its ID:

Operation: query GetAiVisibilityProject( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! ) { getAiVisibilityProject( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId ) { id rawId name scheduleCadence nextScheduleRunAt autoCrawlEnabled freshnessThresholdDays primaryBrand { id rawId name } aiProviders { id name type } createdAt updatedAt } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE" }
GetAiVisibilityProjectTry in Explorer
GraphQL
query GetAiVisibilityProject(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
) {
getAiVisibilityProject(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
) {
id
rawId
name
scheduleCadence
nextScheduleRunAt
autoCrawlEnabled
freshnessThresholdDays
primaryBrand {
id
rawId
name
}
aiProviders {
id
name
type
}
createdAt
updatedAt
}
}

Project statistics and setup progress​

Two fields summarise a project without paging through its runs. Both are available on getAiVisibilityProject and on every node of getAiVisibilityProjects, and each is priced as a single aggregate, so a whole page of projects with both fields selected still fits in one request.

Period statistics​

periodStats returns the primary brand's headline metrics over dateRange (default: the last 30 days, maximum 730 days), its rank among the project's brands and its trend across the range. Use it to paint a projects list in one request:

Operation: query GetAiVisibilityProjectsWithStats( $accountId: ObjectID! $dateRange: AiVisibilityDateRangeInput ) { getAiVisibilityProjects(accountId: $accountId, first: 20) { nodes { id name primaryBrandDomain periodStats(dateRange: $dateRange) { avgVisibilityIndex avgPresenceRate avgQualityScore primaryBrandRank totalRankedBrands trend(timeBucket: Week) { date avgVisibilityIndex } } setupStatus { isComplete } } pageInfo { hasNextPage endCursor } totalCount } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "dateRange": { "start": "2026-08-01T00:00:00.000Z", "end": "2026-08-31T23:59:59.999Z" } }
GetAiVisibilityProjectsWithStatsTry in Explorer
GraphQL
query GetAiVisibilityProjectsWithStats(
$accountId: ObjectID!
$dateRange: AiVisibilityDateRangeInput
) {
getAiVisibilityProjects(accountId: $accountId, first: 20) {
nodes {
id
name
primaryBrandDomain
periodStats(dateRange: $dateRange) {
avgVisibilityIndex
avgPresenceRate
avgQualityScore
primaryBrandRank
totalRankedBrands
trend(timeBucket: Week) {
date
avgVisibilityIndex
}
}
setupStatus {
isComplete
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}

The values are the primary brand's row of getAiVisibilityTopBrands with its default ranking, so they match that query for the same range:

  • avgVisibilityIndex, avgPresenceRate and avgQualityScore are computed once over the whole range, not averaged across days or weeks. They can therefore differ from the mean of the time-series points getAiVisibilityVisibilityIndexTrend returns for the same range, which gives each time bucket equal weight however many runs it holds.
  • primaryBrandRank is the primary brand's position by Visibility Index among totalRankedBrands, every non-merged brand in the project. Brands with no data in the range still hold a rank, tied at 0 and ordered by brand ID.
  • periodStats is null for a project without a primary brand.

trend is the primary brand's series over the same range, one point per timeBucket (default Day), for a sparkline beside the headline values. It is the series getAiVisibilityVisibilityIndexTrend returns for the primary brand and range with no filters, so each point carries the same fields. A range with no finished prompt runs has an empty trend. Select only the fields you plot: trend on its own skips the brand ranking.

periodStats is limited to 100 projects per request. A request that selects it with a larger first fails with AI_VISIBILITY_PROJECT_PERIOD_STATS_PAGE_TOO_LARGE; page through larger accounts with after. The limit counts every aliased periodStats and trend too, so a page of 100 projects fits one date range and one trend, and comparing two date ranges in one request fits 50 projects. setupStatus has no such limit.

Setup progress​

setupStatus reports how far a project has got through its first pass of work, so a client can show a progress indicator and poll a single field rather than counting runs itself:

Operation: query GetAiVisibilityProjectSetupStatus( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! ) { getAiVisibilityProject( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId ) { id setupStatus { expectedPromptRuns completedPromptRuns totalPages evaluatedPages discoveredPages citedPages inProgress isComplete } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE" }
GetAiVisibilityProjectSetupStatusTry in Explorer
GraphQL
query GetAiVisibilityProjectSetupStatus(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
) {
getAiVisibilityProject(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
) {
id
setupStatus {
expectedPromptRuns
completedPromptRuns
totalPages
evaluatedPages
discoveredPages
citedPages
inProgress
isComplete
}
}
}
FieldMeaning
expectedPromptRunsActive prompts (not paused, not deleted) multiplied by the AI providers enabled on the project.
completedPromptRunsPrompt and provider pairs with at least one run that finished, failed or got no response. Never exceeds expectedPromptRuns: later scheduled runs of the same pair do not count again.
totalPages / evaluatedPagesDistinct pages with a page evaluation run, and how many of them completed one.
discoveredPagesDistinct pages SERP discovery found, the same count as getAiVisibilityDiscoveredPages.totalCount over the project's whole history.
citedPagesDistinct pages cited for the primary brand, the same count as getAiVisibilityBrandCitations.totalCount over the project's whole history.
inProgresstrue while any prompt run, SERP discovery run or page evaluation is still running, and for 30 seconds after any of them last changed, so the page evaluations and SERP discovery a finished prompt run starts are counted before setup reports complete. A run that has not changed for 6 hours is treated as stalled and ignored.
isCompletetrue once every expected prompt run has settled and nothing is in progress.

Pages are counted by canonical URL (see Page identity), so a page evaluated twice counts once. All counts cover the project's whole history rather than a date range.

isComplete is not permanent. Adding a prompt or enabling an AI provider that has never run on the project adds prompt and provider pairs with no runs yet, so the project reports incomplete until they settle. Setup counts each pair once it has ever had a settled run, so resuming a paused prompt or re-enabling a provider that ran before does not reopen setup for those pairs. A project with no active prompts or no AI providers is never complete. A failed page evaluation does not hold setup open: evaluatedPages can stay below totalPages on a complete project.

Create a project​

Creating a project requires a name, brand name, and brand domain. The primary brand is automatically created as an own type brand.

Operation: mutation CreateAiVisibilityProject( $accountId: ObjectID! $name: String! $brandName: String! $brandDomain: String! $scheduleCadence: AiVisibilityScheduleCadence $brandThemeSentimentCaptureEnabled: Boolean! $country: String $language: String ) { createAiVisibilityProject( input: { accountId: $accountId name: $name brandName: $brandName brandDomain: $brandDomain scheduleCadence: $scheduleCadence brandThemeSentimentCaptureEnabled: $brandThemeSentimentCaptureEnabled country: $country language: $language } ) { aiVisibilityProject { id rawId name scheduleCadence nextScheduleRunAt brandThemeSentimentCaptureEnabled country language createdAt } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "name": "My Brand Monitoring (UK)", "brandName": "Lumar", "brandDomain": "lumar.io", "scheduleCadence": "Daily", "brandThemeSentimentCaptureEnabled": false, "country": "GB", "language": "en" }
CreateAiVisibilityProjectTry in Explorer
GraphQL
mutation CreateAiVisibilityProject(
$accountId: ObjectID!
$name: String!
$brandName: String!
$brandDomain: String!
$scheduleCadence: AiVisibilityScheduleCadence
$brandThemeSentimentCaptureEnabled: Boolean!
$country: String
$language: String
) {
createAiVisibilityProject(
input: {
accountId: $accountId
name: $name
brandName: $brandName
brandDomain: $brandDomain
scheduleCadence: $scheduleCadence
brandThemeSentimentCaptureEnabled: $brandThemeSentimentCaptureEnabled
country: $country
language: $language
}
) {
aiVisibilityProject {
id
rawId
name
scheduleCadence
nextScheduleRunAt
brandThemeSentimentCaptureEnabled
country
language
createdAt
}
}
}

Project limit​

Your subscription caps how many projects an account may hold. Account.aiVisibilityProjectsLimit is that cap and Account.aiVisibilityProjectsCount is the number of projects already created, so gate a "create project" action on those two fields rather than re-deriving the limit: createAiVisibilityProject enforces exactly these two numbers and fails with AI_VISIBILITY_PROJECTS_LIMIT_REACHED once they meet. Deleted projects are not counted.

aiVisibilityProjectsLimit is null when the account has no project cap, and the mutation then allows creation without limit, so test for it before comparing:

aiVisibilityProjectsLimit == null || aiVisibilityProjectsCount < aiVisibilityProjectsLimit

Comparing a null limit numerically would disable creation on exactly the accounts that have no cap.

Schedule cadence​

The scheduleCadence field controls how often prompts are automatically sent to AI providers:

ValueDescription
DailyEvery day (default)
WeeklyOnce per week
EveryTwoWeeksEvery two weeks
MonthlyOnce per month
EveryTwoMonthsEvery two months
QuarterlyOnce every three months

The schedule cadence also affects the maximum number of prompts allowed in a project. More frequent cadences allow fewer prompts per project.

AI providers​

By default, all AI providers included in your subscription are linked to the project. You can optionally specify aiVisibilityAiProviderIds to select a subset of providers.

Market: country and language​

A project can carry a market: country (ISO 3166-1 alpha-2, e.g. GB) and language (ISO 639-1, e.g. en). One project targets one market; to compare the same prompts across the US and the UK, create two projects.

Both fields are optional and three-state on createAiVisibilityProject:

InputStored value
Field omittedCopied from the account's defaultCountry / defaultLanguage, if set
Explicit nullnull (worldwide / unspecified), even when the account has a default
A value ("gb")Validated and normalised (GB, en)

The value is copied at creation time. Changing an account default later never rewrites existing projects. See Account settings.

When language is omitted and the account has no defaultLanguage, the project does not stay language-less: the language is detected from the brand domain you create the project with, and stored. Detection is best-effort and time-boxed, so a site that cannot be read, or that establishes no clear language, leaves language as null. It also runs only for requests authenticated as a user: a service-account API key creates the project without detection, so pass language explicitly on those calls. An explicit null is never detected over -- it keeps its meaning of "unspecified on purpose". Read language back after creating a project rather than assuming the value you sent.

What the fields do:

  • country is copied onto every prompt created without an explicit country (both createAiVisibilityPrompt and createAiVisibilityTopicsBulk). An explicit prompt country, including null for worldwide, always wins, so accounts that set country per prompt are unaffected. Prompt runs keep using the prompt's stored country; the existing country read filter is unchanged.
  • language steers generateAiVisibilitySuggestedTopics, generateAiVisibilitySuggestedPrompts and generateAiVisibilityTopicMetadata, which also use country for local context (competitors, spelling, regulations). Prompts themselves carry no language.

Generation never changes a project's language: it uses what the project carries. A project with no language -- one created before detection existed, or one whose detection found nothing -- generates as it always did, so set language on those projects yourself if the output language matters.

Unknown or malformed codes are rejected as input validation errors. Country is case-insensitive and stored upper-case; language must be lower-case.

To build a country or language picker, list the accepted codes with their English names. Neither query requires authentication:

Operation: query GetAiVisibilityMarketCodes { aiVisibilityCountries { code name } aiVisibilityLanguages { code name } }
GetAiVisibilityMarketCodesTry in Explorer
GraphQL
query GetAiVisibilityMarketCodes {
aiVisibilityCountries {
code
name
}
aiVisibilityLanguages {
code
name
}
}

Country codes apply to account defaults, projects, prompts, and the free scan. Language codes apply to account defaults, projects, and the free scan; prompts carry no language.

Update a project​

Update a project's name, schedule cadence, market, or content evaluation settings:

Operation: mutation UpdateAiVisibilityProject( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $name: String $scheduleCadence: AiVisibilityScheduleCadence $autoCrawlEnabled: Boolean $freshnessThresholdDays: Int $brandThemeSentimentCaptureEnabled: Boolean $country: String $language: String ) { updateAiVisibilityProject( input: { accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId name: $name scheduleCadence: $scheduleCadence autoCrawlEnabled: $autoCrawlEnabled freshnessThresholdDays: $freshnessThresholdDays brandThemeSentimentCaptureEnabled: $brandThemeSentimentCaptureEnabled country: $country language: $language } ) { aiVisibilityProject { id name scheduleCadence autoCrawlEnabled freshnessThresholdDays brandThemeSentimentCaptureEnabled country language } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "name": "Updated Project Name", "scheduleCadence": "Weekly", "autoCrawlEnabled": true, "freshnessThresholdDays": 14, "brandThemeSentimentCaptureEnabled": true }
UpdateAiVisibilityProjectTry in Explorer
GraphQL
mutation UpdateAiVisibilityProject(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$name: String
$scheduleCadence: AiVisibilityScheduleCadence
$autoCrawlEnabled: Boolean
$freshnessThresholdDays: Int
$brandThemeSentimentCaptureEnabled: Boolean
$country: String
$language: String
) {
updateAiVisibilityProject(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
name: $name
scheduleCadence: $scheduleCadence
autoCrawlEnabled: $autoCrawlEnabled
freshnessThresholdDays: $freshnessThresholdDays
brandThemeSentimentCaptureEnabled: $brandThemeSentimentCaptureEnabled
country: $country
language: $language
}
) {
aiVisibilityProject {
id
name
scheduleCadence
autoCrawlEnabled
freshnessThresholdDays
brandThemeSentimentCaptureEnabled
country
language
}
}
}

Omitted fields are left unchanged. For country and language, pass null to clear the value. Changing the market only affects prompts created afterwards and future generation; existing prompts and their runs are not rewritten, so retargeting a market is normally a new project.

Content evaluation settings​

  • autoCrawlEnabled -- When true, cited pages from your brand's domains are automatically crawled and evaluated after each prompt run
  • freshnessThresholdDays -- Number of days (1-90, default 7) before a previously crawled URL is considered stale and re-crawled

Theme sentiment capture​

  • brandThemeSentimentCaptureEnabled -- When true, the project requests per-theme brand sentiment capture on its prompt runs. Off by default. This is a request, not a guarantee: capture is performed by the prompt tracker, which may not honour the request, so theme sentiment data can remain empty even while the setting is enabled. An empty sentiment series is therefore a normal state, not an error. The setting can also be passed on createAiVisibilityProject, so a project can opt in before its first prompt runs.
caution

Changing scheduleCadence may reduce the allowed prompt count. If the project's active prompt count exceeds the new per-project limit, the update will fail with AI_VISIBILITY_PROMPTS_LIMIT_REACHED. A project sitting exactly at the new limit can still change cadence, and the account-wide limit is not checked here because cadence does not affect it -- see Create a prompt.

Check run capacity before adding prompts​

getAiVisibilityRunUsage models how a project's configuration consumes the account's AI Visibility capacity, so a client can tell how many prompts can still be added before saving. Pass a draft promptCount, scheduleCadence or aiProviderTypes to model a change that is not saved yet; each defaults to the project's current value, and previewAiProviderType models enabling one more provider.

Operation: query GetAiVisibilityRunUsage( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $promptCount: Int $scheduleCadence: AiVisibilityScheduleCadence ) { getAiVisibilityRunUsage( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId promptCount: $promptCount scheduleCadence: $scheduleCadence ) { promptCount effectivePromptsLimit accountPromptsLimit accountPromptsRoom roomToAddPrompts bindingConstraint bindingPoolKey overLimit pools { key type totalRuns availableRuns overLimit } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "promptCount": 60 }
GetAiVisibilityRunUsageTry in Explorer
GraphQL
query GetAiVisibilityRunUsage(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$promptCount: Int
$scheduleCadence: AiVisibilityScheduleCadence
) {
getAiVisibilityRunUsage(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
promptCount: $promptCount
scheduleCadence: $scheduleCadence
) {
promptCount
effectivePromptsLimit
accountPromptsLimit
accountPromptsRoom
roomToAddPrompts
bindingConstraint
bindingPoolKey
overLimit
pools {
key
type
totalRuns
availableRuns
overLimit
}
}
}

The response weighs three constraints and reports the tightest one:

  • Credit pools (pools) -- the provider-specific and shared credit buckets the selected providers draw on, with demand normalised to the fixed planningPeriodDays window. bindingPoolKey names the pool when it is the binding constraint.

    Normalising can leave a project owing a fraction of a run: at cadenceFactor 1/3, a quarterly prompt costs a third of a run over a 30-day window. Demand is summed across every project and provider drawing on a pool before being rounded up, so three quarterly prompts cost one run rather than three. Rounding once per pool means usedRuns, projectedRuns, availableRuns, overageRuns and each entry in segments are whole runs that add up to the pool total, while overLimit and roomToAddPrompts follow the unrounded demand. A configuration with fractional demand -- any cadence other than daily or monthly -- can therefore report slightly lower usage than the sum of its parts suggests.

  • Per-project prompt limit (effectivePromptsLimit) -- the project's own cap, from the subscription, provider count and schedule cadence.

  • Account-wide prompt limit (accountPromptsLimit, accountPromptsRoom) -- the budget shared by every project on the account and the part of it this project can still use (the limit minus active prompts on the account's other projects). Both are null when the account has no account-wide limit. See Create a prompt for how the two prompt limits are enforced.

roomToAddPrompts is the number of prompts the modelled configuration can still add under the binding constraint, and bindingConstraint says which one binds: Pool, ProjectPromptLimit or AccountPromptLimit. overLimit is true when the modelled prompt count already exceeds any of them, and previewCanBeEnabled answers whether a previewAiProviderType would still fit.

Tags​

Projects can be grouped with the account's tags — the same pool that covers Analyze projects and Protect test suites. AiVisibilityProject.tags returns a project's tags, getAiVisibilityProjects takes a tagIds filter, and createAiVisibilityProject takes accountTagIds. Applying tags requires the account's admin role, while creating a project only requires editor, so an editor passing accountTagIds creates nothing.

See Project Tags for the tag lifecycle and the linking mutations.

Delete a project​

Soft-deletes a project. This is a destructive operation.

Operation: mutation DeleteAiVisibilityProject( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! ) { deleteAiVisibilityProject( input: { accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId } ) { aiVisibilityProject { id } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE" }
DeleteAiVisibilityProjectTry in Explorer
GraphQL
mutation DeleteAiVisibilityProject(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
) {
deleteAiVisibilityProject(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
}
) {
aiVisibilityProject {
id
}
}
}

Schema reference​