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.
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:
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:
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:
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,avgPresenceRateandavgQualityScoreare computed once over the whole range, not averaged across days or weeks. They can therefore differ from the mean of the time-series pointsgetAiVisibilityVisibilityIndexTrendreturns for the same range, which gives each time bucket equal weight however many runs it holds.primaryBrandRankis the primary brand's position by Visibility Index amongtotalRankedBrands, 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.periodStatsisnullfor 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:
query GetAiVisibilityProjectSetupStatus(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
) {
getAiVisibilityProject(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
) {
id
setupStatus {
expectedPromptRuns
completedPromptRuns
totalPages
evaluatedPages
discoveredPages
citedPages
inProgress
isComplete
}
}
}
| Field | Meaning |
|---|---|
expectedPromptRuns | Active prompts (not paused, not deleted) multiplied by the AI providers enabled on the project. |
completedPromptRuns | Prompt 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 / evaluatedPages | Distinct pages with a page evaluation run, and how many of them completed one. |
discoveredPages | Distinct pages SERP discovery found, the same count as getAiVisibilityDiscoveredPages.totalCount over the project's whole history. |
citedPages | Distinct pages cited for the primary brand, the same count as getAiVisibilityBrandCitations.totalCount over the project's whole history. |
inProgress | true 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. |
isComplete | true 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.
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:
| Value | Description |
|---|---|
Daily | Every day (default) |
Weekly | Once per week |
EveryTwoWeeks | Every two weeks |
Monthly | Once per month |
EveryTwoMonths | Every two months |
Quarterly | Once 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:
| Input | Stored value |
|---|---|
| Field omitted | Copied from the account's defaultCountry / defaultLanguage, if set |
Explicit null | null (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:
countryis copied onto every prompt created without an explicitcountry(bothcreateAiVisibilityPromptandcreateAiVisibilityTopicsBulk). An explicit promptcountry, includingnullfor worldwide, always wins, so accounts that set country per prompt are unaffected. Prompt runs keep using the prompt's stored country; the existingcountryread filter is unchanged.languagesteersgenerateAiVisibilitySuggestedTopics,generateAiVisibilitySuggestedPromptsandgenerateAiVisibilityTopicMetadata, which also usecountryfor 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:
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:
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-- Whentrue, cited pages from your brand's domains are automatically crawled and evaluated after each prompt runfreshnessThresholdDays-- Number of days (1-90, default 7) before a previously crawled URL is considered stale and re-crawled
Theme sentiment capture
brandThemeSentimentCaptureEnabled-- Whentrue, 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 oncreateAiVisibilityProject, so a project can opt in before its first prompt runs.
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.
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 fixedplanningPeriodDayswindow.bindingPoolKeynames the pool when it is the binding constraint.Normalising can leave a project owing a fraction of a run: at
cadenceFactor1/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 meansusedRuns,projectedRuns,availableRuns,overageRunsand each entry insegmentsare whole runs that add up to the pool total, whileoverLimitandroomToAddPromptsfollow 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 arenullwhen 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.
mutation DeleteAiVisibilityProject(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
) {
deleteAiVisibilityProject(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
}
) {
aiVisibilityProject {
id
}
}
}
Schema reference
AiVisibilityProject-- Project typeCreateAiVisibilityProjectInput-- Creation inputUpdateAiVisibilityProjectInput-- Update inputAiVisibilityScheduleCadence-- Schedule optionsAiVisibilityProjectPeriodStats-- Primary brand statistics for a date rangeAiVisibilityProjectSetupStatus-- Setup progressAiVisibilityRunUsage-- Run capacity model returned bygetAiVisibilityRunUsage(schema page appears once the query is released)AiVisibilityRunUsageBindingConstraint-- Which limit bindsAiVisibilityAccountSettings-- Account defaults new projects inherit (see Account settings)