Skip to main content

Topics

Topics group related prompts together. For example, you might have topics like "Technical SEO", "Content Marketing", or "Site Performance". Each topic can contain multiple prompts that are sent to AI providers.

List topics​

Retrieve topics for a project with visibility metrics. Requires aiVisibilityBrandId to specify which brand's perspective to use for metrics.

Operation: query GetAiVisibilityTopics( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $aiVisibilityBrandId: ObjectID! $dateRange: AiVisibilityDateRangeInput ) { getAiVisibilityTopics( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId aiVisibilityBrandId: $aiVisibilityBrandId dateRange: $dateRange first: 20 orderBy: [{ field: avgVisibilityScore, direction: DESC }] ) { nodes { id rawId name totalPrompts totalRuns avgVisibilityScore avgCitationQualityScore avgBrandMentionQualityScore avgBrandSentiment totalBrandCitations totalBrandMentions topBrands { name type avgVisibilityScore } } pageInfo { hasNextPage endCursor } totalCount } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "aiVisibilityBrandId": "QWlWaXNpYmlsaXR5QnJhbmQx", "dateRange": { "start": "2025-01-01", "end": "2025-01-31" } }
GetAiVisibilityTopicsTry in Explorer
GraphQL
query GetAiVisibilityTopics(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$aiVisibilityBrandId: ObjectID!
$dateRange: AiVisibilityDateRangeInput
) {
getAiVisibilityTopics(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
aiVisibilityBrandId: $aiVisibilityBrandId
dateRange: $dateRange
first: 20
orderBy: [{ field: avgVisibilityScore, direction: DESC }]
) {
nodes {
id
rawId
name
totalPrompts
totalRuns
avgVisibilityScore
avgCitationQualityScore
avgBrandMentionQualityScore
avgBrandSentiment
totalBrandCitations
totalBrandMentions
topBrands {
name
type
avgVisibilityScore
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}

Sorting​

Topics can be sorted by: name, avgVisibilityIndex, avgPresenceRate, avgQualityScore, totalPrompts, avgBrandMentionQualityScore, avgCitationQualityScore, avgBrandSentiment, totalBrandMentions, totalBrandCitations, mentionShareOfVoice, citationShareOfVoice. Sorting happens server side across the whole project, so ordering by avgBrandSentiment surfaces the most negatively perceived topics first regardless of page size; topics without a brand mention in the window sort last in either direction.

Default sort is avgVisibilityScore DESC when a date range is provided, or name ASC when omitted.

Sorting by Share of Voice​

mentionShareOfVoice and citationShareOfVoice order on the same figures the corresponding fields return, including the renormalization brandTypes applies — sorting with brandTypes: [Own, Competitor] ranks each topic by the brand's share of the competitor set, not of the whole market.

The two sides are independent: a topic can have in-scope citations but no mentions, which makes its mention share unmeasured while its citation share is real. A share is unmeasured when the window holds no in-scope appearances of that kind for the topic, or when the requested brand is outside the Share of Voice universe (a Context brand, or one excluded by brandTypes). The fields return 0 for those, but sorting distinguishes them: unmeasured topics sort last in both directions, so ascending order starts at the lowest real share rather than at topics with no data. A topic where the brand is genuinely absent from a non-empty comparison set is a real 0 and sorts ahead of them.

Because mentionShareOfVoice is non-nullable, an unmeasured topic and a real 0% both read 0 — so an ascending page shows 0 at the top and 0 again at the bottom. The two unmeasured cases are told apart differently:

  • Nothing of that kind in the topic: check the total and the leader belonging to the field you sorted on, never the other side's and never brandsInScope (which counts brands with an appearance of either kind). mentionShareOfVoice is unmeasured exactly when totalMentionAnswerAppearances is 0 and mentionShareOfVoiceLeader is null; likewise totalCitationAnswerAppearances / citationShareOfVoiceLeader for the citation share. A real 0% has a non-zero total and a leader on that same side.
  • The requested brand is outside the universe (a Context brand, or one excluded by the brandTypes you passed): both totals stay non-zero, because other brands did appear. Here the leader is present but its gap is null. This case is a property of the request, not of a topic — when it applies, every topic in the response is unmeasured on both sides.

Metrics​

Each topic includes these computed fields when a dateRange is provided:

FieldDescription
avgVisibilityScoreComposite visibility score for the topic
avgCitationQualityScoreAverage citation quality across prompts in this topic
avgBrandMentionQualityScoreAverage mention quality across prompts in this topic
avgBrandSentimentAverage sentiment score of the brand's mentions; null when never mentioned
totalBrandCitationsTotal citations for the specified brand
totalBrandMentionsTotal mentions for the specified brand
totalPromptsNumber of prompts in this topic
totalRunsFinished prompt runs behind this topic's metrics in the window
topBrandsTop 4 brands by visibility score (see Brand types)

A topic with totalRuns: 0 was not measured in the window: none of its prompts finished a run, so every metric above reads zero because there is nothing to average, not because the brand scored zero. Read totalRuns first when rendering a topic's metrics, the same way AiVisibilityPromptListItem.totalRuns works for prompts.

Brand types​

brandTypes narrows the brands the per-topic metrics consider. It scopes the Share of Voice denominators and leader election (where Context brands — platforms and surfaces such as Google or ChatGPT — are always excluded), and it also restricts which brands may appear in topBrands. Without it, topBrands ranks every brand in the project, platforms included; pass [Own, Competitor, Other] to keep them out of the top 4 so the next ranked brands take those slots. The argument never changes which topics are returned.

Get a single topic​

Operation: query GetAiVisibilityTopic( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $aiVisibilityTopicId: ObjectID! ) { getAiVisibilityTopic( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId aiVisibilityTopicId: $aiVisibilityTopicId ) { id rawId name } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "aiVisibilityTopicId": "QWlWaXNpYmlsaXR5VG9waWMx" }
GetAiVisibilityTopicTry in Explorer
GraphQL
query GetAiVisibilityTopic(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$aiVisibilityTopicId: ObjectID!
) {
getAiVisibilityTopic(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
aiVisibilityTopicId: $aiVisibilityTopicId
) {
id
rawId
name
}
}

Search topics​

Lightweight search for typeahead scenarios. Returns a simple array without pagination:

Operation: query SearchAiVisibilityTopics( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $search: String! ) { searchAiVisibilityTopics( input: { accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId search: $search } ) { id rawId name } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "search": "SEO" }
SearchAiVisibilityTopicsTry in Explorer
GraphQL
query SearchAiVisibilityTopics(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$search: String!
) {
searchAiVisibilityTopics(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
search: $search
}
) {
id
rawId
name
}
}

Create a topic​

Operation: mutation CreateAiVisibilityTopic( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $name: String! ) { createAiVisibilityTopic( input: { accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId name: $name } ) { aiVisibilityTopic { id rawId name } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "name": "Technical SEO" }
CreateAiVisibilityTopicTry in Explorer
GraphQL
mutation CreateAiVisibilityTopic(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$name: String!
) {
createAiVisibilityTopic(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
name: $name
}
) {
aiVisibilityTopic {
id
rawId
name
}
}
}

Update a topic​

Rename an existing topic:

Operation: mutation UpdateAiVisibilityTopic( $accountId: ObjectID! $aiVisibilityTopicId: ObjectID! $name: String! ) { updateAiVisibilityTopic( input: { accountId: $accountId aiVisibilityTopicId: $aiVisibilityTopicId name: $name } ) { aiVisibilityTopic { id rawId name } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityTopicId": "QWlWaXNpYmlsaXR5VG9waWMx", "name": "Advanced Technical SEO" }
UpdateAiVisibilityTopicTry in Explorer
GraphQL
mutation UpdateAiVisibilityTopic(
$accountId: ObjectID!
$aiVisibilityTopicId: ObjectID!
$name: String!
) {
updateAiVisibilityTopic(
input: {
accountId: $accountId
aiVisibilityTopicId: $aiVisibilityTopicId
name: $name
}
) {
aiVisibilityTopic {
id
rawId
name
}
}
}

Delete a topic​

Soft-deletes a topic and cascades the deletion to all prompts under it.

Operation: mutation DeleteAiVisibilityTopic( $accountId: ObjectID! $aiVisibilityTopicId: ObjectID! ) { deleteAiVisibilityTopic( input: { accountId: $accountId aiVisibilityTopicId: $aiVisibilityTopicId } ) { aiVisibilityTopic { id } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityTopicId": "QWlWaXNpYmlsaXR5VG9waWMx" }
DeleteAiVisibilityTopicTry in Explorer
GraphQL
mutation DeleteAiVisibilityTopic(
$accountId: ObjectID!
$aiVisibilityTopicId: ObjectID!
) {
deleteAiVisibilityTopic(
input: {
accountId: $accountId
aiVisibilityTopicId: $aiVisibilityTopicId
}
) {
aiVisibilityTopic {
id
}
}
}
caution

Deleting a topic will also soft-delete all prompts within it. Existing prompt run data is preserved but no new runs will be created for those prompts.

Suggested topics​

Use AI to generate topic suggestions based on your brand. Suggestions are cached per project and brand -- calling generate again replaces the previous suggestions.

Get suggestions​

Operation: query GetAiVisibilitySuggestedTopics( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $aiVisibilityBrandId: ObjectID! ) { getAiVisibilitySuggestedTopics( accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId aiVisibilityBrandId: $aiVisibilityBrandId ) { id name prompts { content branded } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "aiVisibilityBrandId": "QWlWaXNpYmlsaXR5QnJhbmQx" }
GetAiVisibilitySuggestedTopicsTry in Explorer
GraphQL
query GetAiVisibilitySuggestedTopics(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$aiVisibilityBrandId: ObjectID!
) {
getAiVisibilitySuggestedTopics(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
aiVisibilityBrandId: $aiVisibilityBrandId
) {
id
name
prompts {
content
branded
}
}
}

Generate suggestions​

Operation: mutation GenerateAiVisibilitySuggestedTopics( $accountId: ObjectID! $aiVisibilityProjectId: ObjectID! $aiVisibilityBrandId: ObjectID! ) { generateAiVisibilitySuggestedTopics( input: { accountId: $accountId aiVisibilityProjectId: $aiVisibilityProjectId aiVisibilityBrandId: $aiVisibilityBrandId } ) { suggestions { id name prompts { content branded } } } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityProjectId": "QWlWaXNpYmlsaXR5UHJvamVjdDE", "aiVisibilityBrandId": "QWlWaXNpYmlsaXR5QnJhbmQx" }
GenerateAiVisibilitySuggestedTopicsTry in Explorer
GraphQL
mutation GenerateAiVisibilitySuggestedTopics(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$aiVisibilityBrandId: ObjectID!
) {
generateAiVisibilitySuggestedTopics(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
aiVisibilityBrandId: $aiVisibilityBrandId
}
) {
suggestions {
id
name
prompts {
content
branded
}
}
}
}

What a suggestion looks like​

Every suggested topic comes back within the limits createAiVisibilityTopic accepts, so it can be saved as-is: a name of at most 255 characters, a description of at most 2,000, and keywords of at most 100 characters each. Generation returns at most 10 keywords, which is a generator policy rather than an API limit -- the mutations accept up to 50, so keywords you add yourself are not held to ten. Its starter prompts follow the same rules as suggested prompts -- at most 3 sentences and 600 characters, never a checklist or multi-line brief.

count is the number of suggestions requested, not a guarantee. A suggestion that comes back outside those bounds is regenerated rather than trimmed, and one that is still unusable is dropped, so a successful response can contain fewer suggestions than requested. Read the length of the returned list rather than assuming count entries. If nothing usable survives, the mutation fails with an error rather than returning an empty list -- retry it.

promptsPerTopic is a request in the same way. A topic short of it is regenerated, but if the retry is no better a topic carrying at least one starter prompt is returned rather than dropped, so count each topic's prompts instead of assuming promptsPerTopic of them. Add the rest with createAiVisibilityPrompt.

Each entry of a suggestion's prompts carries content and branded, so a topic arrives with a mix of branded and unbranded starter prompts; pass both through to createAiVisibilityTopicsBulk to keep the tags. The deprecated suggestedPrompts field returns the same prompts as bare strings, without their tags.

Generate topic metadata​

Generates a description and keywords for one topic -- an existing one via aiVisibilityTopicId, or an unsaved one via aiVisibilityProjectId + topicName (plus any prompts typed so far) so a creation form can offer them before the topic exists.

The result is a preview: nothing is stored, and the values are yours to edit before saving them -- with updateAiVisibilityTopic for an existing topic, or by passing them to createAiVisibilityTopic when you generated them for a topic that does not exist yet.

Operation: mutation GenerateAiVisibilityTopicMetadata($accountId: ObjectID!, $aiVisibilityTopicId: ObjectID!) { generateAiVisibilityTopicMetadata( input: { accountId: $accountId, aiVisibilityTopicId: $aiVisibilityTopicId } ) { description keywords } }Variables: { "accountId": "TjAwN0FjY291bnQxMjM0NQ", "aiVisibilityTopicId": "QWlWaXNpYmlsaXR5VG9waWMx" }
GenerateAiVisibilityTopicMetadataTry in Explorer
GraphQL
mutation GenerateAiVisibilityTopicMetadata($accountId: ObjectID!, $aiVisibilityTopicId: ObjectID!) {
generateAiVisibilityTopicMetadata(
input: { accountId: $accountId, aiVisibilityTopicId: $aiVisibilityTopicId }
) {
description
keywords
}
}

Generated metadata always fits what createAiVisibilityTopic and updateAiVisibilityTopic accept -- a description of at most 2,000 characters, and keywords of at most 100 characters each -- so it can be saved without editing. It returns at most 10 keywords; that is the generator's policy, not the mutation's limit, which is 50. Output that exceeds those bounds is regenerated rather than trimmed; if the retry is no better the mutation fails with an error rather than returning a shortened description, so retry it.

Schema reference​