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.
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).mentionShareOfVoiceis unmeasured exactly whentotalMentionAnswerAppearancesis0andmentionShareOfVoiceLeaderisnull; likewisetotalCitationAnswerAppearances/citationShareOfVoiceLeaderfor the citation share. A real0%has a non-zero total and a leader on that same side. - The requested brand is outside the universe (a
Contextbrand, or one excluded by thebrandTypesyou passed): both totals stay non-zero, because other brands did appear. Here the leader is present but itsgapisnull. 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:
| Field | Description |
|---|---|
avgVisibilityScore | Composite visibility score for the topic |
avgCitationQualityScore | Average citation quality across prompts in this topic |
avgBrandMentionQualityScore | Average mention quality across prompts in this topic |
avgBrandSentiment | Average sentiment score of the brand's mentions; null when never mentioned |
totalBrandCitations | Total citations for the specified brand |
totalBrandMentions | Total mentions for the specified brand |
totalPrompts | Number of prompts in this topic |
totalRuns | Finished prompt runs behind this topic's metrics in the window |
topBrands | Top 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
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:
query SearchAiVisibilityTopics(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$search: String!
) {
searchAiVisibilityTopics(
input: {
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
search: $search
}
) {
id
rawId
name
}
}
Create a topic
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:
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.
mutation DeleteAiVisibilityTopic(
$accountId: ObjectID!
$aiVisibilityTopicId: ObjectID!
) {
deleteAiVisibilityTopic(
input: {
accountId: $accountId
aiVisibilityTopicId: $aiVisibilityTopicId
}
) {
aiVisibilityTopic {
id
}
}
}
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
query GetAiVisibilitySuggestedTopics(
$accountId: ObjectID!
$aiVisibilityProjectId: ObjectID!
$aiVisibilityBrandId: ObjectID!
) {
getAiVisibilitySuggestedTopics(
accountId: $accountId
aiVisibilityProjectId: $aiVisibilityProjectId
aiVisibilityBrandId: $aiVisibilityBrandId
) {
id
name
prompts {
content
branded
}
}
}
Generate suggestions
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.
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
AiVisibilityTopic-- Topic typeAiVisibilityTopicDetails-- Topic details typeAiVisibilitySuggestedTopic-- Suggested topic type