AI SEO Experiment: Bing Topic Portfolio for an enterprise B2B SaaS Client

Bing’s new Topics Portfolio has changed how I approach AI SEO for one of my clients.

Instead of looking at individual search queries and trying to work out where to create more content, I can now look at entire topics, understand how much AI search activity they attract and identify where competitors are being cited while my client’s website is missing.

This is a much more useful way to plan an AI SEO strategy.

For example, I recently analysed a B2B SaaS company operating in the technology sector. Its main services include vibe coding, Model Context Protocol (MCP) and AI-assisted data modelling.

Using Bing Webmaster Tools, I could see how these topics were performing, which questions were already generating citations for the client and which opportunities were being taken by other websites.

One finding stood out.

Vibe coding was the biggest content opportunity for the business.

The individual search query “vibecoding” had an estimated 10,000 to 50,000 citations, compared with 5,000 to 10,000 for “what is mcp”.

I then downloaded the available data, analysed approximately 100,000 opportunity queries and turned the findings into a content plan.

In this article, I’ll explain how I approached the experiment, what I found and how SEO, content and marketing teams can use the same process to identify opportunities for their own websites.

The main takeaway: Bing’s Topics Portfolio gives SEO teams a better way to understand where AI search opportunities exist. Rather than creating content based on assumptions or a handful of keywords, you can use topic size, citation activity and competitor gaps to decide which subjects deserve your attention.

The Experiment: Finding AI Search Opportunities Using Bing Webmaster Tools Topic Portfolio

Before getting into the details, here is a summary of the experiment.

AreaDetails
ClientAn anonymised enterprise B2B SaaS company operating in the technology sector
Main servicesVibe coding, Model Context Protocol (MCP) and AI-assisted data modelling
ToolBing Webmaster Tools, AI Performance report, Topics Portfolio beta
Data periodSeptember 2026
What I analysedTopic size, client citations, grounding queries, existing citations and opportunity queries
Main findingVibe coding offered the biggest opportunity among the client’s three service areas
Top opportunity“vibecoding”, with an estimated 10,000 to 50,000 citations
Next stepBuild a content cluster around vibe coding and measure its performance across Bing and other AI assistants

The objective was to find out which of the client’s core services offered the biggest opportunity to gain visibility in AI-generated answers.

Rather than guessing which topic to prioritise, I wanted to use actual citation data to make the decision.

1. What Has Changed in Bing Webmaster Tools?

On 13 October 2026, Bing introduced Topics Portfolio as a beta feature within its AI Performance report in Bing Webmaster Tools.

It provides a different way to look at the search queries behind AI-generated answers.

Instead of showing only the questions where your website already appears, it groups related queries into broader topics and identifies additional opportunities where other websites are cited but yours is not.

This is particularly useful because you can start seeing the size of a topic and the opportunities available before investing time and money in creating content.

What Are Grounding Queries?

Grounding queries are the searches an AI system carries out to find information before producing an answer.

For example, someone might ask Microsoft Copilot:

“Which are the best AI-powered development tools for a B2B software company?”

Before answering, the system may search for information about:

  • AI development tools.
  • Vibe coding platforms.
  • Software development solutions.
  • Product comparisons.
  • Pricing and available features.

These searches help the AI system find information that it can use when generating its response.

Some of the websites it finds may then be cited as sources.

Bing’s AI Performance report provides information about these grounding queries and the pages receiving citations.

How Has Bing’s AI Performance Report Developed?

Bing initially introduced its AI Performance report as a public preview in February 2026.

The first version provided information about citations by page and grounding query.

A further update in June 2026 introduced additional reporting, including:

  • Search intent.
  • Topics.
  • Citation share.
  • Competitor comparisons.

However, the earlier topic reporting mainly grouped queries where a website was already receiving citations.

Topics Portfolio goes further.

It provides a broader view of the topic, including queries where other websites receive citations but your own website does not.

This is the part I find particularly useful for content planning.

You are no longer looking only at the subjects where you already have some visibility. You can also investigate the areas where competitors are getting attention.

Where Can You Find Topics Portfolio?

You can access it through Bing Webmaster Tools.

Open the AI Performance report and select the Topics Portfolio tab next to Citations & Pages.

The report provides one-month, two-month and three-month reporting periods.

The feature only appears when Bing has enough data to build topics for your website.

Smaller or newer websites might therefore see very few topics or none at all.

For those websites, the Citations & Pages report is still available.

What Information Does Topics Portfolio Provide?

There are several measurements worth understanding before analysing the data.

MetricWhat it tells you
TopicA group of related grounding queries that Bing’s AI has placed within the same broader subject.
Topic sizeThe estimated number of citations across all websites for the topic during the selected period.
Sites citedThe approximate number of different websites receiving citations for queries within the topic.
QueriesThe estimated number of distinct grounding queries within the topic.
Your citationsHow many times your website was cited for queries within the topic.
Your grounding queriesThe individual questions for which your website is already receiving citations.
Opportunity queriesQuestions where other websites are receiving citations but yours is not. Bing provides estimated citation ranges rather than exact figures.

The Technology topic for the anonymised client, September 2026. Website names, existing citations and grounding queries have been hidden.

For my client, the Technology topic showed:

  • An estimated 1 to 5 billion citations across all websites.
  • Approximately 1 to 5 million distinct grounding queries.
  • Existing client citations and grounding queries.
  • Additional opportunity queries where other websites were being cited.

Two queries immediately caught my attention:

Opportunity queryEstimated citations
vibecoding10,000–50,000
what is mcp5,000–10,000

The difference was interesting because both topics were directly related to the client’s services.

It provided an early indication that vibe coding deserved more attention.

However, I didn’t stop at the headline figures.

I downloaded the topic data and used it to identify the individual questions that could become part of a much larger content strategy.

2. Why Is Bing’s Topics Portfolio Important for SEO Teams?

The biggest benefit of Topics Portfolio is that it changes how you can approach AI search content planning.

Before this type of reporting was available, understanding AI visibility often meant looking at individual grounding queries and trying to group them manually.

You might have hundreds or thousands of questions, but it was difficult to understand which broader subjects were worth investing in.

Topics Portfolio gives you a way to look at the bigger picture.

Moving from Individual Queries to Entire Topics

Traditional keyword research often starts with individual search terms.

You might find a keyword, check its search volume and decide whether to create a page around it.

The same approach can be applied to AI search, but grounding queries introduce another challenge.

There can be thousands of different questions around the same subject.

For example, a technology company offering vibe coding services might encounter questions such as:

  • What is vibe coding?
  • How does vibe coding work?
  • Which vibe coding tools are best?
  • Is vibe coding suitable for enterprise development?
  • What are the risks of vibe coding?
  • Vibe coding vs traditional software development.

These are different questions, but they all relate to the same broader topic.

Rather than creating a separate content strategy around every query, I would group them into a content cluster.

This is where Topics Portfolio becomes useful.

Content planning questionWhat individual queries tell youWhat Topics Portfolio adds
Is this topic worth investing in?A selection of individual questionsEstimated topic size and query count.
How competitive is the topic?Difficult to establish from isolated queriesThe estimated number of websites being cited.
Where are we already visible?Individual queries where your website receives citationsExisting citations and grounding queries grouped by topic.
Where are competitors getting visibility?Often difficult to identify through citation reporting aloneOpportunity queries where other websites are cited instead.
Which subjects should we prioritise?Keyword research and estimated search demandOpportunity queries with estimated citation ranges.

For SEO teams, this makes it easier to move from research into actual content planning.

You Can Identify Where Competitors Are Getting Cited and Your Website Is Missing

This is probably the most interesting part of the feature.

Most AI visibility reports focus on where your website already receives citations.

That information is useful, but it only tells you part of the story.

Opportunity queries show where other websites are being cited instead.

Bing does not identify the exact competitors receiving citations for every opportunity query, but it does show the questions and estimated citation ranges.

For my client, this meant I could identify questions about vibe coding and MCP where other websites were already appearing in AI-generated answers.

I could then investigate whether the client had suitable content covering those subjects.

This is a much better starting point for finding content gaps.

You Can Understand the Size of an Opportunity Before Creating Content

Another benefit is that you can assess the scale of a topic before committing resources.

For example, a topic containing billions of estimated citations across websites and millions of distinct grounding queries is clearly attracting substantial AI search activity.

Likewise, an individual opportunity query with an estimated 10,000 to 50,000 citations deserves more attention than a query with very limited citation activity, assuming both are relevant to the business.

This helps when presenting content recommendations to marketing directors.

Instead of saying that a particular subject looks interesting, you can explain:

  • How large the topic is.
  • How many grounding queries it contains.
  • How many websites are receiving citations.
  • How much citation activity is already associated with your website.
  • Where additional opportunities exist.

It gives your content plan a stronger commercial starting point.

Can Bing’s Data Help with ChatGPT, Gemini and Perplexity?

This is something I find particularly interesting.

Bing’s Topics Portfolio provides information relating to Microsoft Copilot, Bing’s AI experiences and selected partner integrations.

It does not directly report what happens in ChatGPT, Gemini or Perplexity.

However, these AI assistants also carry out searches to find information before generating answers.

That means a topic attracting considerable citation activity in Bing is a sensible place to investigate across other assistants.

For example, if vibe coding is generating substantial citation activity in Bing, I would want to understand whether ChatGPT, Gemini, Perplexity and Claude are also responding to similar questions.

This is a hypothesis, not something the Bing report proves.

The way to test it is to track the same questions across those assistants and compare the results.

Important Limitations of Topics Portfolio

There are a few things I would keep in mind when interpreting the data.

The numbers are estimates

Bing describes the figures as directional information for planning rather than an exact record of every citation.

Treat the citation ranges as a guide to the size of an opportunity, not as guaranteed traffic or a target for reporting.

Topic names are generated by AI

The topics are automatically grouped and labelled.

They might not always match the way your business organises its services or products.

You may need to combine, separate or filter topics when preparing your content strategy.

Competitors’ brand names can appear as opportunities

Bing can include branded queries relating to other businesses.

These are not necessarily suitable content opportunities.

I would remove them unless there is a genuine reason to create an honest comparison page.

The feature is still in beta

Topics Portfolio is a beta feature, and Bing has not provided a confirmed date for when it will leave beta.

The reporting and available information may change.

I would therefore use it as one source of research rather than making it the only measure of AI search performance.

3. Which Businesses Should Use Bing’s Topics Portfolio?

Any website with enough AI citation activity for Bing to generate topic data can benefit from the feature.

In practice, I would pay particular attention to established brands, publishers, B2B businesses and companies with substantial content libraries.

The way you use the report will depend on your industry and the services or products you sell.

How Different Industries Can Use Topics Portfolio

Business typeWhat I would investigateHow I would use the information
B2B SaaS and technologyProduct category topics, definitions, technical guides and how-to questions where competing websites receive citations.Identify the biggest opportunity related to core services and build a supporting content cluster.
Ecommerce and retailProduct categories, buying guides and competitor-related queries.Prioritise product categories based on topic size and remove irrelevant competitor-branded queries.
Publishers and mediaLarge topics with many competing websites.Look at the number of sites cited and prioritise subjects where there is a realistic opportunity to gain visibility.
Professional services and agenciesTopics related to individual services and competing businesses.Connect topic opportunities to actual services and compare results across clients.
Finance, healthcare and other regulated industriesTopics where AI assistants rely heavily on comparison websites and established information sources.Create accurate, expert-reviewed content around important topics.
Smaller or newer websitesWhether sufficient topic data is available.Continue using Citations & Pages until Bing has enough information to generate topics.

Which Teams Should Be Involved?

I would involve several teams when turning Topics Portfolio data into a content strategy.

SEO team

The SEO team should download the reports, organise the opportunity queries, assess their commercial value and manage the overall content plan.

Content team

The content team should create the main topic page and supporting articles based on the selected opportunities.

Product marketing

Product marketing needs to make sure that the proposed topics are relevant to what the business actually sells.

They should also provide technical knowledge, product information and examples.

Marketing leadership

Marketing directors should decide which topics deserve investment and provide the budget and resources required to produce the content.

Development team

Development should make sure that the new pages are accessible to search engines and AI crawlers, load properly and are included in the website’s sitemaps.

Communications team

Communications should look at opportunities to get the business mentioned on relevant third-party websites that are already receiving citations for the topic.

4. How to Turn Bing Topics Portfolio Data into a Content Strategy

I approached this experiment as a content planning exercise.

The objective was not simply to download a list of queries and give it to a writer.

I wanted to understand which of the client’s services had the biggest opportunity, which questions were worth targeting and what content needed to be created.

Here is the process I followed.

Step 1: Open Topics Portfolio and Choose Your Reporting Period

Open Bing Webmaster Tools and navigate to the AI Performance report.

Select the Topics Portfolio tab next to Citations & Pages.

You can choose between one-month, two-month and three-month reporting periods.

For this experiment, I used one month of data from September 2026.

I wanted a relatively recent view of the available opportunities, and I’ll continue using the same reporting period each month to measure changes.

I would also recommend starting with the topics where your website already receives citations.

Bing suggests looking at these areas first.

Pay attention to the Sites cited measurement.

A topic with fewer competing websites may be easier to gain visibility in than one where thousands of different websites are already receiving citations.

Step 2: Download the Topic Data

Once you’ve identified the relevant topics, open each one and select Download.

The report used in my experiment included two main groups of information:

Your grounding queries

These are the questions where your website is already receiving citations, alongside the number of citations attributed to your site.

Opportunity queries

These are the questions where other websites are being cited but yours is not.

Bing provides estimated citation ranges for these queries.

For my client, the Technology topic contained approximately 100,000 opportunity queries.

That’s far too many to review manually.

And that’s exactly why the next step is important.

The value is not in reading every query individually. It’s in grouping the information into subjects that match what the business actually sells.

Step 3: Clean the Opportunity Query List

Topics are automatically grouped by AI, which means a broad subject such as Technology can contain many questions that have nothing to do with your business.

Before scoring the opportunities, I recommend removing three types of queries.

1. Competitors’ branded queries

Bing can include questions containing competitors’ brand names.

For example, someone might ask:

“What is the best alternative to Competitor X?”

You do not necessarily need to create a page targeting every competitor name that appears in the report.

Only keep these opportunities where a comparison page would genuinely help potential customers make a decision.

2. Queries outside your services

A Technology topic might include:

  • General programming questions.
  • Consumer technology.
  • Software development news.
  • Programming tutorials.
  • Technical questions unrelated to your products.

A B2B SaaS company should not create content around every query simply because it appears in the same topic.

Focus on subjects that have a clear connection to the services you sell.

3. Queries with different spellings that mean the same thing

Look for variations of the same query.

For example:

  • vibecoding.
  • vibe coding.
  • vibe coder.

These should be grouped together rather than counted as completely separate opportunities.

Otherwise, you risk overstating the amount of demand associated with a topic.

If Bing has grouped a query under the wrong topic, you can also use the thumbs-down feedback option next to it.

Bing says this feedback helps it decide which issues to address.

Step 4: Group Opportunities by Your Products and Services

This is where the analysis becomes useful.

My client had three main service areas:

  1. Vibe coding.
  2. Model Context Protocol.
  3. AI data modelling.

I created a list of related terms for each service and used them to group the opportunity queries.

Here is how I approached the initial classification.

Service areaMatching termsMain opportunity identifiedWhat I assessed
Vibe codingvibe coding, vibecoding, vibe codervibecoding (10,000–50,000 citations)Combined citation ranges across matching opportunity queries.
Model Context ProtocolMCP, Model Context Protocolwhat is mcp (5,000–10,000 citations)Share of the total opportunity and existing client citations.
AI data modellingdata model, data modelling, schemaRelated queries from the exportRelevant opportunities and the client’s existing citations.

I then compared the opportunities for each service area.

Because Bing provides citation ranges rather than exact figures, I used the midpoint of each range on a multiplying scale.

The calculation was:

[
\text{Central estimate} = \sqrt{\text{Lower range} \times \text{Upper range}}
]

For example, a query with an estimated 10,000 to 50,000 citations would receive a central estimate of approximately 22,400.

I then added the estimates for queries belonging to the same service area.

The objective was not to produce an exact number of expected citations.

It was to give each service area a comparable score so I could see which one offered the biggest opportunity.

Vibe coding came out on top by a clear margin.

I would use this information to prioritise the first content cluster, while keeping the other service areas in the plan for future work.

Step 5: Check Whether the Opportunity Is Worth Pursuing

Before committing a content budget, I recommend assessing the opportunity from three perspectives.

1. Is there enough demand?

Look at the overall topic size, the number of grounding queries and the citation ranges for the most important opportunities.

In this experiment, the Technology topic contained an estimated:

  • 1 to 5 billion citations across all websites.
  • 1 to 5 million distinct grounding queries.

The top vibe coding query alone had an estimated 10,000 to 50,000 citations.

This suggested that there was enough activity to justify further investigation.

2. Can your business realistically gain citations?

Look at the number of different websites receiving citations within the topic.

A topic with fewer competing websites may present a more realistic opportunity.

You should also consider whether your website already receives citations for related queries.

If Bing is already using your website for similar questions, you may have a useful starting point.

This doesn’t guarantee that new pages will receive citations, but it can help you decide where to invest your resources.

3. Does the topic match what your business sells?

This is particularly important.

You should not build a content cluster simply because a topic has a large number of citations.

The subject needs to connect to your products or services.

For my client, vibe coding was one of its core service areas.

This meant that the supporting pages could explain the subject, answer technical questions and lead interested visitors towards relevant product pages or a demo.

Vibe coding passed all three checks, so it became the first priority.

Step 6: Build a Content Cluster Around the Winning Topic

Once you’ve selected the topic, the next job is to turn the opportunity queries into a content plan.

For my client, the vibe coding queries could be grouped into several different search intents:

  • What vibe coding is.
  • How to use vibe coding.
  • Which tools and platforms are available.
  • How vibe coding compares with traditional software development.
  • Examples and use cases.
  • Security and governance.

I would use the main topic as the pillar page and create supporting articles around each important subject.

Here is an example of how I would structure the cluster.

PagePurposeExample queries to answerMain team
Vibe coding (pillar page)Explain the topic, how it works, available tools, risks and how the client’s product fits.vibecoding, vibe codingContent and product marketing
What is vibe coding?Provide a clear explanation and practical examples.what is vibe coding, vibe coding meaningContent
How to start vibe codingExplain the process for developers and software teams.how to start vibe coding, vibe coding tutorialContent and engineering
Vibe coding tools and platformsCompare available tools, including the client’s product.best vibe coding tools, vibe coding platformsProduct marketing
Vibe coding vs traditional developmentExplain the differences, costs and situations where each approach makes sense.vibe coding vs coding, alternatives to vibe codingContent
Vibe coding examples and use casesShow practical business applications and customer examples.vibe coding examples, vibe coding projectsProduct marketing
Vibe coding security and governanceExplain security risks, code reviews, data handling and controls.vibe coding security risks, is vibe coding safeContent and security

The example questions demonstrate the different search intents.

The final page plan should use the actual queries and citation ranges from the Topics Portfolio export.

I would prioritise pages based on their commercial relevance, citation opportunity and how well they support the main pillar page.

How I Would Write Content for AI Search

There is an important difference between creating a list of articles and creating content that genuinely answers the questions AI assistants are trying to resolve.

I would follow five writing rules.

1. Answer the main question immediately

The first two sentences should answer the main question in straightforward language.

If someone searches for “What is vibe coding?”, they should not have to read five paragraphs about the development of AI before finding the definition.

2. Give each section one clear purpose

Each H2 should answer a specific question or cover one subject.

Avoid headings such as “More information” or “Things to consider”.

Make each section understandable on its own.

3. Use practical examples rather than making general claims

Include information that helps readers understand the subject.

Depending on the topic, this might include:

  • Step-by-step instructions.
  • Code examples.
  • Comparison tables.
  • Screenshots.
  • Original research.
  • Company data.
  • Comments from engineers.
  • Customer examples.

For my client, input from its own engineers would be particularly useful because it provides technical information that competing websites cannot simply reproduce.

4. Use consistent product and company names

Make sure the company name, product names and technical terminology are used consistently across the content.

For this client, that means using the terms vibe coding, MCP and data modelling correctly.

5. Keep the content up to date

Vibe coding is a fast-moving subject.

I would add a last updated date to each page and review the content at least quarterly.

Technical examples, tools and recommendations can become outdated quickly.

Step 7: Implement the Content Cluster Across Your Website

Once the content is ready, the development and marketing teams need to make sure it is properly implemented.

I would split the work between development, content and marketing.

Development Team

Publish the content under one main folder

For example:

/vibe-coding/

The pillar page and supporting articles should sit within a clear website structure.

Use appropriate breadcrumbs and BreadcrumbList structured data.

Make the content available in the initial HTML

Where possible, make sure the main content is included in server-rendered HTML.

Some AI crawlers have limited JavaScript capabilities. Content that only appears after scripts run may not be accessible to every crawler.

Connect the pages

The pillar page should reference its supporting articles, and those articles should point back to the pillar where relevant.

Each page should also provide a clear route towards the relevant product or service page.

You should also update existing pages that already receive citations for related grounding queries, so visitors and search engines can discover the new content.

Implement the relevant structured data

Use suitable structured data for each page type.

For example:

  • Article structured data for supporting articles.
  • Organization structured data for the business.
  • SoftwareApplication structured data for relevant software products.

Include accurate authorship and publication dates where appropriate.

Submit the new pages to Bing

Update your XML sitemap with the new and modified URLs.

Make sure the last modification dates are correct and submit the URLs through IndexNow, which Bing supports.

You can then check the URLs using URL Inspection in Bing Webmaster Tools.

Check crawler access

Review robots.txt and your CDN settings to make sure Bingbot can access the new pages.

Also decide which other AI crawlers you want to allow access to your website.

Content and Product Marketing Teams

Write the pillar page first

Start with the main topic before publishing the supporting articles.

This gives the content cluster a clear foundation.

Prioritise supporting pages

Use the citation ranges and commercial relevance of each opportunity to decide which articles should be written first.

Get technical input

Ask the client’s engineers to review technical claims and contribute examples.

This is particularly important for subjects such as vibe coding, MCP and AI-assisted development.

Update existing content

Review pages that already receive citations.

Where appropriate, update them with relevant information and make sure they connect to the new content cluster.

Marketing and Communications Teams

Bing’s opportunity queries show where other websites receive citations, but they do not identify every competitor website responsible.

I would use another AI visibility tool to investigate which websites assistants rely on when answering the main questions.

We use SD SEO Suite for this type of research.

Once you have identified the important websites, look for opportunities to get the business and its expertise featured.

For a technology company focusing on vibe coding, this could include:

  • Guest tutorials.
  • Developer documentation.
  • Open-source examples.
  • Technical contributions.
  • Conference presentations.
  • Specialist technology publications.
  • Developer communities.

The objective is to build a stronger presence across the websites and communities influencing the answers.

Step 8: Measure the Results of Your Content Experiment

The September 2026 data provides the starting point for this experiment.

Once the new content has been published, I would continue measuring the same areas each month.

The important thing is to keep the reporting consistent so you can compare changes over time.

What I would measureWhere to measure itFrequency
Client citations across the Technology topic and vibe coding queriesBing Webmaster Tools, Topics Portfolio, using the same one-month reporting windowMonthly
Opportunity queries that become grounding queriesComparison of monthly Topics Portfolio downloadsMonthly
Citations received by each new content pageBing AI Performance, Citations & PagesWeekly
Brand mentions and citations across ChatGPT, Gemini, Perplexity and ClaudeSD SEO Suite AI VisibilityWeekly
Impressions and clicks for new cluster pages, including AI Overview and AI Mode appearancesGoogle Search Console Performance and Generative AI Performance reportsMonthly
Demo requests and sign-ups generated by the new contentWebsite analytics and CRMMonthly

There is also an important hypothesis I want to test.

If Bing’s topic data is a useful indicator of wider AI search demand, the same questions should also provide opportunities across ChatGPT, Gemini, Perplexity and Claude.

I will therefore track the same questions across all four assistants and compare the results.

The objective is to find out whether the opportunities identified through Bing translate into improved visibility elsewhere.

5. Implementation Checklists for SEO, Content and Marketing Teams

For businesses planning to use Topics Portfolio regularly, I recommend making this a shared process between SEO, content, development and marketing.

These checklists provide a starting point.

SEO Team

  • Open Topics Portfolio and record topic size, sites cited and query counts.
  • Use the same reporting period every month.
  • Download the topics relevant to the business.
  • Remove competitor-branded queries that are not suitable content opportunities.
  • Remove queries that do not relate to your products or services.
  • Group opportunity queries by service area.
  • Compare citation opportunities with your existing citations.
  • Assess topic size, competition and commercial relevance.
  • Choose the first content cluster.
  • Build the page plan using the actual opportunity queries.
  • Compare monthly reports and record changes in citations.

Content and Product Marketing Teams

  • Write the main pillar page first.
  • Create supporting articles based on the opportunity queries.
  • Answer each page’s main question in the opening paragraphs.
  • Give each H2 a clear purpose.
  • Include original examples, code, data and expert input.
  • Check technical information with the relevant product or engineering teams.
  • Update existing pages that already receive citations.
  • Keep product names, company names and technical terms consistent.
  • Review important content at least quarterly.

Development Team

  • Publish the content cluster under one clear website folder.
  • Implement breadcrumbs and appropriate BreadcrumbList structured data.
  • Make the main content available in server-rendered HTML where possible.
  • Connect pillar pages, supporting articles and relevant product pages.
  • Implement Article structured data with correct author and date information.
  • Update XML sitemaps and last modified dates.
  • Submit new and updated URLs through IndexNow.
  • Check new URLs using Bing Webmaster Tools.
  • Confirm robots.txt and CDN settings allow Bingbot access.

Marketing and Communications Teams

  • Identify third-party websites that AI assistants cite for important queries.
  • Look for relevant publishing and partnership opportunities.
  • Build relationships with specialist websites and communities.
  • Track whether ChatGPT, Gemini, Perplexity and Claude mention and cite the business.
  • Review AI visibility changes alongside traditional organic search performance.

6. Frequently Asked Questions About Bing Topics Portfolio

What Is Bing’s Topics Portfolio?

Topics Portfolio is a beta feature within the AI Performance report in Bing Webmaster Tools; it groups the grounding queries behind AI-generated answers into broader topics.

It shows the estimated size of each topic, how many websites receive citations, how often your website is cited and which additional queries present opportunities to gain visibility.

What Is the Difference Between Topic Size and Your Citations?

Topic size estimates the total number of citations across all websites for a particular topic during the selected period.

Your citations show how many of those citations went to your own website.

Together, these measurements help you understand how much AI search activity is associated with a topic and how much visibility your website currently receives.

What Are Opportunity Queries?

Opportunity queries are questions within a topic where other websites receive citations but your own website does not.

Bing provides estimated citation ranges rather than exact numbers.

For example, an opportunity query might have an estimated 10,000 to 50,000 citations.

These queries can help you identify subjects where additional content may improve your AI search visibility.

Why Can’t I See Topics Portfolio in Bing Webmaster Tools?

The feature only appears when Bing has enough information to generate topic data for your website.

Smaller or newer websites may see very few topics or none at all.

You can still use the Citations & Pages section of AI Performance to review your existing citations and grounding queries.

Can I Download the Topics Portfolio Data?

Yes.

Each topic has a Download option.

The exported data includes your existing grounding queries and opportunity queries, along with their available citation information.

You can then analyse the data using spreadsheets or a suitable analysis tool.

Does Bing’s Data Show What Happens in ChatGPT, Gemini, Perplexity and Claude?

Not directly.

Topics Portfolio reports on AI search activity associated with Microsoft Copilot, Bing and selected partner integrations.

However, the topics can provide useful ideas for investigating other AI assistants.

For example, if vibe coding attracts substantial citation activity in Bing, you can track similar questions across ChatGPT, Gemini, Perplexity and Claude to see whether the same opportunities exist.

This needs to be tested rather than assumed. I recommend starting with SD SEO Suite.

Should I Create Content Targeting Competitors’ Branded Searches?

Not automatically.

Bing can include competitor brand names in opportunity queries.

I would normally remove these from the main content plan unless there is a genuine reason to create a comparison or alternatives page.

Any comparison content should provide useful and accurate information rather than simply targeting another company’s name.

How Often Should I Review Topics Portfolio?

I recommend reviewing it monthly using the same reporting period.

This makes it easier to compare downloads and understand which opportunities are changing.

After publishing new content, I would also check the Citations & Pages report weekly to see whether individual pages are beginning to receive citations.

7. The Free Python Toolkit for Bing Topics Portfolio

To make this process easier, I’ve also created a Python script for SEO leads called topic_portfolio_planner.py.

It is designed to help analyse Topics Portfolio downloads and turn the results into a content plan.

The script uses Python’s standard library, so there is nothing additional to install.

It has four main commands.

1. The Summary Command

The summary command provides an overview of a Topics Portfolio download.

It shows:

  • Main topic figures.
  • Number of grounding queries.
  • Number of opportunity queries.
  • Highest-priority opportunities.
  • Competitor-branded queries that may need removing.

This gives you a quick overview before starting the analysis.

2. The Clusters Command

The clusters command groups opportunity queries based on your products or services.

For example, a technology company might define the following themes:

  • Vibe coding.
  • Model Context Protocol.
  • Data modelling.

The script then groups relevant queries and ranks the opportunities based on their estimated citations, share of the opportunity and existing citations.

Any remaining queries can be grouped automatically.

3. The Plan Command

The plan command turns a selected topic into a content cluster.

It suggests:

  • A main pillar page.
  • Supporting articles.
  • A main query for each page.
  • Other queries each page should answer.
  • Suggested URLs.

This gives your content team a starting point for building an article plan from the actual opportunity data.

4. The Compare Command

The compare command compares two monthly Topics Portfolio downloads.

It identifies:

  • Opportunity queries that now receive citations for your website.
  • Changes in existing citations.
  • Opportunities that are no longer appearing.
  • Changes in citation activity for selected topics.

This is particularly useful when measuring whether your new content is improving AI visibility.

How to Run the Toolkit

Place the Python script in the same folder as your Topics Portfolio download.

Open Terminal on Mac or Command Prompt on Windows and navigate to that folder.

You can then run the following commands.

Group opportunities into content themes:

python3 topic_portfolio_planner.py clusters Technology.csv --themes "vibe coding=vibe cod,vibecod|mcp=mcp,model context protocol|data modelling=data model" --exclude "rivalbrand"

Create a content plan for a selected topic:

python3 topic_portfolio_planner.py plan Technology.csv --theme "vibe coding=vibe cod,vibecod" --exclude "rivalbrand"

Compare two monthly downloads:

python3 topic_portfolio_planner.py compare September.csv October.csv --theme "vibe coding=vibe cod,vibecod"

The words after each equals sign define which terms should be used to identify a service area.

For example:

vibe coding=vibe cod,vibecod

This allows the script to identify queries containing variations such as vibe coding, vibecoding and vibe coder.

Short terms such as mcp are matched as whole words to avoid including unrelated queries.

Each command also creates a CSV file that you can open in Excel and share with your content team.

Example Output: Grouping the Opportunity Queries

The following example uses a small test file based on the type of data shown in the client experiment.

This is sample data, not the client’s actual results.

CLUSTERS  4 opportunity theme(s)

cluster            kind         queries  citations*  share  you   top query

vibe coding        your service       9       35.8K    73%    -   vibecoding (10K-50K)

mcp                your service       3         10K    20%  1.5K  what is mcp (5K-10K)

data modelling     your service       1        2.2K     5%   800  ai data modeling (1K-5K)

python             auto               2         931     2%    -   python list comprehension (500-1K)

* Central estimate from Bing's citation ranges.
  'you' = your current citations in that cluster.

The result identifies vibe coding as the largest opportunity among the three service areas in this test dataset.

It accounts for 73% of the opportunity.

Example Output: Building a Content Cluster

The plan command takes the selected topic and turns it into a suggested page structure.

Here is an abbreviated example using the same test data.

PLAN  content cluster for "vibe coding":
8 page(s), 9 opportunity queries, about 35.8K citations

[pillar] overview
  /vibe-coding/

  main query: vibecoding (10K-50K)

  9 queries, about 35.8K citations


[supporting] Tools and platforms
  /vibe-coding/vibe-coding-tools/

  main query: vibe coding tools (5K-10K)

  2 queries, about 9.3K citations

  also answer: best vibe coding platform for teams


[supporting] What it is
  /vibe-coding/what-is-vibe-coding/

  main query: what is vibe coding (1K-5K)

  1 query, about 2.2K citations

This gives your team an initial content structure based on the opportunities in the export.

Rather than manually reviewing thousands of queries, you can identify the main themes, prioritise them and build a page plan.

Full Kit:

#!/usr/bin/env python3
"""
topic_portfolio_planner.py - turn a Bing Webmaster Tools Topics Portfolio
download into a prioritised AI SEO content plan.

Szymaniak Digital. Standard library only (Python 3.8+). Nothing to install.
Built for SEO leads: it works on the CSV you download from a topic in
Bing Webmaster Tools > AI Performance > Topic Portfolio (beta).

Four commands:

  summary   What's in the download: the topic's headline figures (if the
            file includes them), how many of your grounding queries and
            opportunity queries it holds, the opportunity queries with the
            highest citation ranges, and any branded queries to remove.
              python3 topic_portfolio_planner.py summary Technology.csv

  clusters  Groups the opportunity queries into themes and ranks them by
            opportunity. Give it your service areas as themes, so it can
            tell you which of YOUR services has the biggest gap; anything
            that doesn't match a theme is grouped automatically.
              python3 topic_portfolio_planner.py clusters Technology.csv \
                  --themes "vibe coding=vibe cod,vibecod|mcp=mcp,model context protocol|data modelling=data model"

  plan      Builds a content cluster plan for one theme: a pillar page and
            supporting pages grouped by what people want (definitions,
            how-to guides, tools, comparisons, examples, pricing, security),
            each with its main query, the queries it should answer, and a
            suggested URL. Also lists the queries you're already cited for,
            so the new pages can link to and from them.
              python3 topic_portfolio_planner.py plan Technology.csv \
                  --theme "vibe coding=vibe cod,vibecod"

  compare   Compares two downloads of the same topic (for example
            September and October) and lists the opportunity queries you're
            now cited for, the citations you've lost, and how your citations
            changed. Use it to measure whether the new content worked.
              python3 topic_portfolio_planner.py compare September.csv \
                  October.csv --theme "vibe coding=vibe cod,vibecod"

How the numbers work
  Bing shows opportunity citations as ranges such as "10K-50K". The script
  reads each range as low and high values and uses the geometric midpoint
  (the square root of low x high; 22,361 for 10K-50K) as a central estimate,
  because the ranges grow by multiples. "<1K" counts as 0 to 1,000 and
  "50K+" as 50,000. Bing says to treat these figures as directional, so use
  the ranking, not the totals, for decisions.

The download
  The script finds the columns itself. It looks for a column containing
  "query" and one containing "citation", and works out whether each row is
  one of your grounding queries or an opportunity query from a "section" or
  "type" column, or from the table headings in the file ("Your Grounding
  Query", "Opportunity Query"). It also accepts several files at once.

Plain-English run guide: put this file in the same folder as your download,
open Terminal (Mac) or Command Prompt (Windows) in that folder and type one
of the commands above ("python" instead of "python3" on Windows). Add
--help after a command to see its options. Each command prints a summary and
writes a CSV you can open in Excel.
"""

import argparse
import csv
import math
import re
import sys
from collections import Counter, defaultdict

STOP = set("""a an and are as at be best by can do does for from get how i in
into is it its me my of on or our the to vs what when where which who why will
with you your using use used""".split())

INTENTS = [
    ("definition", "What it is",
     r"^(what is|what are|what's|whats|define|definition of)\b|\bmeaning\b|"
     r"\bdefinition\b|\bexplained\b"),
    ("how-to", "How-to guides",
     r"\bhow (to|do|can|does)\b|\btutorial\b|\bguide\b|\bstep by step\b|"
     r"\bset ?up\b|\binstall\b|\bgetting started\b|\blearn\b|\bcourse\b"),
    ("comparison", "Comparisons and alternatives",
     r"\bvs\b|\bversus\b|\bcompare\b|\bcomparison\b|\balternatives?\b|"
     r"\bdifference\b"),
    ("tools", "Tools and platforms",
     r"\btools?\b|\bsoftware\b|\bplatforms?\b|\bapps?\b|\bbest\b|\btop\b|"
     r"\bide\b|\beditors?\b|\bextensions?\b|\bservers?\b|\bframeworks?\b"),
    ("examples", "Examples and use cases",
     r"\bexamples?\b|\buse cases?\b|\btemplates?\b|\bideas\b|\bprojects?\b"),
    ("pricing", "Pricing and cost",
     r"\bprices?\b|\bpricing\b|\bcosts?\b|\bfree\b|\bcheap\b"),
    ("security", "Security and risks",
     r"\bsecur\w*\b|\brisks?\b|\bsafe\w*\b|\bvulnerab\w*\b|\bprivacy\b"),
]
INTENT_NAMES = dict((k, n) for k, n, _ in INTENTS)
INTENT_NAMES["general"] = "General questions"
SUFFIX = {"k": 1e3, "m": 1e6, "b": 1e9}
KPI_NAMES = {"topic size": "topic_size", "your citations": "your_citations",
             "queries": "queries", "sites cited": "sites_cited"}


# --------------------------------------------------------------------------
# reading the download
# --------------------------------------------------------------------------

def parse_number(s):
    s = s.strip().lower().replace(",", "").replace("~", "")
    m = re.fullmatch(r"(\d+(?:\.\d+)?)\s*([kmb])?", s)
    if not m:
        return None
    return float(m.group(1)) * SUFFIX.get(m.group(2) or "", 1)


def parse_range(value):
    """'10K-50K' -> (10000, 50000); '1,234' -> (1234, 1234); '<1K' ->
    (0, 1000); '50K+' -> (50000, 50000); anything else -> None."""
    if value is None:
        return None
    s = str(value).strip().replace("–", "-").replace("—", "-")
    s = s.replace(" to ", "-").replace(" ", "")
    if not s or s in ("-", "--"):
        return None
    m = re.fullmatch(r"<(.+)", s)
    if m:
        hi = parse_number(m.group(1))
        return (0.0, hi) if hi is not None else None
    if "-" in s.strip("-"):
        lo, _, hi = s.partition("-")
        lo, hi = parse_number(lo), parse_number(hi)
        if lo is not None and hi is not None:
            return (min(lo, hi), max(lo, hi))
        return None
    n = parse_number(s.lstrip(">").rstrip("+"))
    return (n, n) if n is not None else None


def midpoint(rng):
    lo, hi = rng
    if lo > 0:
        return math.sqrt(lo * hi)
    return hi / 2.0


def section_of(text):
    t = (text or "").lower()
    if "opportun" in t:
        return "opportunity"
    if "grounding" in t or "your" in t or "cited" in t:
        return "grounding"
    return None


def read_rows(path):
    for enc in ("utf-8-sig", "cp1252", "latin-1"):
        try:
            with open(path, newline="", encoding=enc) as fh:
                return list(csv.reader(fh))
        except UnicodeDecodeError:
            continue
    return []


def is_header(cells):
    low = [c.strip().lower() for c in cells]
    return (any("query" in c for c in low) and
            any("citation" in c for c in low))


def load(paths, warn=print):
    """Return (rows, kpis). Each row: query, section, low, high, mid, raw,
    topic, file."""
    rows, kpis = [], {}
    for path in paths:
        qcol = vcol = scol = tcol = None
        current = None
        for cells in read_rows(path):
            cells = [c.strip() for c in cells]
            if not any(cells):
                continue
            first = cells[0].lower().rstrip(":")
            if first in KPI_NAMES and len(cells) > 1 and not is_header(cells):
                kpis[KPI_NAMES[first]] = cells[1]
                continue
            if is_header(cells):
                low = [c.lower() for c in cells]
                qcol = next(i for i, c in enumerate(low) if "query" in c)
                vcol = next(i for i, c in enumerate(low) if "citation" in c)
                scol = next((i for i, c in enumerate(low)
                             if c in ("section", "type", "table", "query type")),
                            None)
                tcol = next((i for i, c in enumerate(low) if c == "topic"),
                            None)
                current = section_of(cells[qcol]) or section_of(cells[vcol])
                continue
            if qcol is None or qcol >= len(cells) or not cells[qcol]:
                continue
            sec = current
            if scol is not None and scol < len(cells):
                sec = section_of(cells[scol]) or sec
            raw = cells[vcol] if vcol is not None and vcol < len(cells) else ""
            rng = parse_range(raw)
            rows.append({
                "query": cells[qcol], "section": sec or "unknown",
                "low": rng[0] if rng else 0.0, "high": rng[1] if rng else 0.0,
                "mid": midpoint(rng) if rng else 0.0, "raw": raw,
                "topic": cells[tcol] if tcol is not None and tcol < len(cells)
                else "", "file": path})
    unknown = sum(r["section"] == "unknown" for r in rows)
    if unknown:
        warn("  WARN  {} row(s) had no section; they're treated as "
             "opportunity queries. Check the file's headings.".format(unknown))
        for r in rows:
            if r["section"] == "unknown":
                r["section"] = "opportunity"
    return rows, kpis


# --------------------------------------------------------------------------
# themes, brands, clustering
# --------------------------------------------------------------------------

def parse_themes(spec):
    """'vibe coding=vibe cod,vibecod|mcp=mcp' -> [(name, [patterns])]."""
    themes = []
    for part in [p for p in (spec or "").split("|") if p.strip()]:
        name, _, pats = part.partition("=")
        pats = [x.strip().lower() for x in (pats or name).split(",")
                if x.strip()]
        themes.append((name.strip(), pats))
    return themes


def matches(query, patterns):
    q = query.lower()
    for p in patterns:
        if re.fullmatch(r"[a-z0-9]+", p) and len(p) <= 4:
            if re.search(r"\b" + re.escape(p) + r"\b", q):
                return True
        elif p in q:
            return True
    return False


def theme_of(query, themes):
    for name, pats in themes:
        if matches(query, pats):
            return name
    return None


def brand_flag(query, brands):
    q = query.lower()
    return next((b for b in brands if re.search(
        r"\b" + re.escape(b.lower()) + r"\b", q)), "")


def tokens(query):
    out = []
    for t in re.findall(r"[a-z0-9]+", query.lower()):
        if t in STOP or len(t) < 3 or t.isdigit():
            continue
        if len(t) > 4 and t.endswith("s") and not t.endswith("ss"):
            t = t[:-1]
        out.append(t)
    return out


def auto_cluster(queries, min_size=2):
    """Group queries by their most common meaningful word."""
    df = Counter(t for q in queries for t in set(tokens(q)))
    groups = defaultdict(list)
    for q in queries:
        ts = sorted(set(tokens(q)), key=lambda t: (-df[t], t))
        groups[ts[0] if ts and df[ts[0]] >= min_size else "other"].append(q)
    small = [k for k, v in groups.items() if len(v) < min_size and k != "other"]
    for k in small:
        groups["other"].extend(groups.pop(k))
    return groups


def intent_of(query):
    q = query.lower()
    for key, _, rx in INTENTS:
        if re.search(rx, q):
            return key
    return "general"


def slugify(text, words=6):
    parts = re.findall(r"[a-z0-9]+", text.lower())[:words]
    return "-".join(parts)


# --------------------------------------------------------------------------
# commands
# --------------------------------------------------------------------------

def fmt(n):
    n = float(n)
    for div, suf in ((1e9, "B"), (1e6, "M"), (1e3, "K")):
        if n >= div:
            v = n / div
            return ("{:.1f}".format(v).rstrip("0").rstrip(".")) + suf
    return "{:.0f}".format(n)


def write(path, rows, fields):
    with open(path, "w", newline="", encoding="utf-8") as fh:
        w = csv.DictWriter(fh, fieldnames=fields, extrasaction="ignore")
        w.writeheader()
        w.writerows(rows)


def split_list(s):
    return [x.strip() for x in re.split(r"[|;]", s or "") if x.strip()]


def cmd_summary(a):
    rows, kpis = load(a.files)
    opp = [r for r in rows if r["section"] == "opportunity"]
    own = [r for r in rows if r["section"] == "grounding"]
    print("SUMMARY  {} row(s) from {} file(s)".format(len(rows), len(a.files)))
    for k, label in (("topic_size", "Topic size"), ("your_citations",
                     "Your citations"), ("queries", "Queries"),
                     ("sites_cited", "Sites cited")):
        if k in kpis:
            print("  {:15} {}".format(label, kpis[k]))
    print("  Your grounding queries: {:,} ({} citations)".format(
        len(own), fmt(sum(r["mid"] for r in own))))
    print("  Opportunity queries:    {:,} (about {} citations, range {} to "
          "{})".format(len(opp), fmt(sum(r["mid"] for r in opp)),
                       fmt(sum(r["low"] for r in opp)),
                       fmt(sum(r["high"] for r in opp))))
    unparsed = [r for r in rows if r["raw"] and parse_range(r["raw"]) is None]
    if unparsed:
        print("  WARN  {} value(s) couldn't be read, e.g. '{}'".format(
            len(unparsed), unparsed[0]["raw"]))
    buckets = Counter(r["raw"] for r in opp if r["raw"])
    if buckets:
        print("\n  Opportunity queries by citation range:")
        for raw, n in sorted(buckets.items(), key=lambda kv: -(
                parse_range(kv[0]) or (0, 0))[1]):
            print("    {:>12}  {:,}".format(raw, n))
    print("\n  Top opportunity queries:")
    for r in sorted(opp, key=lambda r: (-r["mid"], r["query"]))[:a.show]:
        print("    {:>10}  {}".format(r["raw"], r["query"]))
    brands = split_list(a.exclude)
    if brands:
        flagged = [r for r in opp if brand_flag(r["query"], brands)]
        print("\n  {} opportunity quer{} name an excluded brand and will be "
              "left out of clusters and plans".format(
                  len(flagged), "y" if len(flagged) == 1 else "ies"))
    return 0


def score_clusters(rows, themes, brands):
    opp = [r for r in rows if r["section"] == "opportunity"
           and not brand_flag(r["query"], brands)]
    own = [r for r in rows if r["section"] == "grounding"]
    groups = defaultdict(list)
    loose = []
    for r in opp:
        t = theme_of(r["query"], themes)
        if t:
            groups[("service", t)].append(r)
        else:
            loose.append(r)
    by_q = {r["query"]: r for r in loose}
    for label, qs in auto_cluster([r["query"] for r in loose]).items():
        groups[("auto", label)] += [by_q[q] for q in qs]
    total = sum(r["mid"] for r in opp) or 1.0
    out = []
    for (kind, name), rs in groups.items():
        mine = [r for r in own if (theme_of(r["query"], themes) == name
                                   if kind == "service" else
                                   name in tokens(r["query"]))]
        opp_mid = sum(r["mid"] for r in rs)
        own_mid = sum(r["mid"] for r in mine)
        out.append({
            "cluster": name, "kind": "your service" if kind == "service"
            else "auto", "opportunity_queries": len(rs),
            "opportunity_mid": opp_mid,
            "opportunity_low": sum(r["low"] for r in rs),
            "opportunity_high": sum(r["high"] for r in rs),
            "share_of_opportunity": opp_mid / total,
            "your_queries": len(mine), "your_citations": own_mid,
            "gap": opp_mid / (opp_mid + own_mid) if opp_mid + own_mid else 0,
            "top_query": max(rs, key=lambda r: r["mid"])["query"] if rs else "",
            "top_range": max(rs, key=lambda r: r["mid"])["raw"] if rs else ""})
    out.sort(key=lambda c: (c["cluster"] == "other", -c["opportunity_mid"]))
    return out


def cmd_clusters(a):
    rows, _ = load(a.files)
    themes = parse_themes(a.themes)
    clusters = score_clusters(rows, themes, split_list(a.exclude))
    print("CLUSTERS  {} opportunity theme(s)".format(len(clusters)))
    print("  {:20} {:>13} {:>7} {:>11} {:>6} {:>8}  {}".format(
        "cluster", "kind", "queries", "citations*", "share", "you", "top query"))
    for c in clusters[:a.show]:
        print("  {:20} {:>13} {:>7,} {:>11} {:>6.0%} {:>8}  {} ({})".format(
            c["cluster"][:20], c["kind"], c["opportunity_queries"],
            fmt(c["opportunity_mid"]), c["share_of_opportunity"],
            fmt(c["your_citations"]) if c["your_queries"] else "-",
            c["top_query"][:40], c["top_range"]))
    print("  * central estimate from Bing's citation ranges; 'you' = your "
          "current citations in that cluster")
    svc = [c for c in clusters if c["kind"] == "your service"]
    if svc:
        best = max(svc, key=lambda c: c["opportunity_mid"])
        print("\n  Biggest opportunity among your services: {} ({:.0%} of the "
              "opportunity in this download)".format(
                  best["cluster"], best["share_of_opportunity"]))
    if a.out:
        write(a.out, clusters, ["cluster", "kind", "opportunity_queries",
                                "opportunity_low", "opportunity_mid",
                                "opportunity_high", "share_of_opportunity",
                                "your_queries", "your_citations", "gap",
                                "top_query", "top_range"])
        print("  wrote", a.out)
    return 0


def build_plan(rows, theme, brands, base="", per_page=8):
    name, pats = theme
    opp = [r for r in rows if r["section"] == "opportunity"
           and matches(r["query"], pats) and not brand_flag(r["query"], brands)]
    own = [r for r in rows if r["section"] == "grounding"
           and matches(r["query"], pats)]
    if not opp:
        return None, own
    root = "/" + (base.strip("/") + "/" if base.strip("/") else "") + \
        slugify(name) + "/"
    pillar = sorted(opp, key=lambda r: (-r["mid"], len(r["query"])))[0]
    by_intent = defaultdict(list)
    for r in opp:
        if r is not pillar:
            by_intent[intent_of(r["query"])].append(r)
    pages = [{"page": "pillar", "intent": "overview", "url": root,
              "main_query": pillar["query"], "main_range": pillar["raw"],
              "queries": 1 + sum(len(v) for v in by_intent.values()),
              "citations_mid": sum(r["mid"] for r in opp),
              "answers": "; ".join(r["query"] for r in sorted(
                  opp, key=lambda r: -r["mid"])[1:per_page + 1])}]
    for key, rs in sorted(by_intent.items(),
                          key=lambda kv: -sum(r["mid"] for r in kv[1])):
        rs.sort(key=lambda r: (-r["mid"], r["query"]))
        main = rs[0]
        pages.append({
            "page": "supporting", "intent": INTENT_NAMES[key],
            "url": root + slugify(main["query"]) + "/",
            "main_query": main["query"], "main_range": main["raw"],
            "queries": len(rs), "citations_mid": sum(r["mid"] for r in rs),
            "answers": "; ".join(r["query"] for r in rs[1:per_page + 1])})
    return pages, own


def cmd_plan(a):
    rows, _ = load(a.files)
    themes = parse_themes(a.theme)
    if not themes:
        print("  Give a theme, e.g. --theme \"vibe coding=vibe cod,vibecod\"")
        return 2
    pages, own = build_plan(rows, themes[0], split_list(a.exclude), a.base,
                            a.per_page)
    if not pages:
        print("PLAN  no opportunity queries match '{}'. Check the patterns "
              "after '='.".format(themes[0][0]))
        return 1
    print("PLAN  content cluster for \"{}\": {} page(s), {:,} opportunity "
          "queries, about {} citations".format(
              themes[0][0], len(pages), pages[0]["queries"],
              fmt(pages[0]["citations_mid"])))
    for p in pages:
        print("\n  [{}] {}  {}".format(p["page"], p["intent"], p["url"]))
        print("    main query: {} ({})".format(p["main_query"], p["main_range"]))
        print("    {:,} quer{}, about {} citations".format(
            p["queries"], "y" if p["queries"] == 1 else "ies",
            fmt(p["citations_mid"])))
        if p["answers"]:
            print("    also answer: " + p["answers"][:160] + (
                "..." if len(p["answers"]) > 160 else ""))
    if own:
        print("\n  Already cited for {} quer{} in this theme: link the new "
              "pages to and from the pages cited for them:".format(
                  len(own), "y" if len(own) == 1 else "ies"))
        for r in sorted(own, key=lambda r: -r["mid"])[:a.show]:
            print("    {:>8}  {}".format(r["raw"], r["query"]))
    if a.out:
        write(a.out, pages, ["page", "intent", "url", "main_query",
                             "main_range", "queries", "citations_mid",
                             "answers"])
        print("\n  wrote", a.out)
    return 0


def compare(before, after, pats=None):
    """Which opportunity queries became grounding queries between two
    downloads, and which grounding queries were lost."""
    norm = lambda q: re.sub(r"\s+", " ", q.lower()).strip()
    keep = (lambda q: matches(q, pats)) if pats else (lambda q: True)
    b = {norm(r["query"]): r for r in before if keep(r["query"])}
    a = {norm(r["query"]): r for r in after if keep(r["query"])}
    out = []
    for q in sorted(set(b) | set(a)):
        rb, ra = b.get(q), a.get(q)
        sb = rb["section"] if rb else "absent"
        sa = ra["section"] if ra else "absent"
        if sb == "opportunity" and sa == "grounding":
            status = "won"
        elif sb == "absent" and sa == "grounding":
            status = "new citation"
        elif sb == "grounding" and sa != "grounding":
            status = "lost"
        elif sb == "grounding" and sa == "grounding":
            status = "kept"
        elif sa == "opportunity" and sb == "absent":
            status = "new opportunity"
        elif sa == "opportunity":
            status = "still open"
        else:
            status = "gone"
        out.append({"query": (ra or rb)["query"], "status": status,
                    "before": rb["raw"] if rb else "", "before_section": sb,
                    "after": ra["raw"] if ra else "", "after_section": sa,
                    "before_mid": rb["mid"] if rb else 0.0,
                    "after_mid": ra["mid"] if ra else 0.0})
    return out


def cmd_compare(a):
    before, _ = load(split_list(a.before) or [a.before])
    after, _ = load(split_list(a.after) or [a.after])
    themes = parse_themes(a.theme)
    pats = themes[0][1] if themes else None
    rows = compare(before, after, pats)
    st = Counter(r["status"] for r in rows)
    own_b = sum(r["before_mid"] for r in rows if r["before_section"] ==
                "grounding")
    own_a = sum(r["after_mid"] for r in rows if r["after_section"] ==
                "grounding")
    print("COMPARE  {} quer{}{}".format(
        len(rows), "y" if len(rows) == 1 else "ies",
        " in \"{}\"".format(themes[0][0]) if themes else ""))
    for k in ("won", "new citation", "kept", "lost", "still open",
              "new opportunity", "gone"):
        if st[k]:
            print("  {:16} {:,}".format(k, st[k]))
    print("  your citations   {} -> {}".format(fmt(own_b), fmt(own_a)))
    won = [r for r in rows if r["status"] in ("won", "new citation")]
    if won:
        print("\n  Now cited for:")
        for r in sorted(won, key=lambda r: -r["after_mid"])[:a.show]:
            print("    {:>8}  {}  (was {})".format(
                r["after"], r["query"], r["before"] or "not listed"))
    lost = [r for r in rows if r["status"] == "lost"]
    if lost:
        print("\n  No longer cited for:")
        for r in sorted(lost, key=lambda r: -r["before_mid"])[:a.show]:
            print("    {:>8}  {}".format(r["before"], r["query"]))
    if a.out:
        write(a.out, rows, ["query", "status", "before_section", "before",
                            "after_section", "after"])
        print("\n  wrote", a.out)
    return 0


def main(argv=None):
    ap = argparse.ArgumentParser(description="Bing Topics Portfolio "
                                 "download to AI SEO content plan.")
    sub = ap.add_subparsers(dest="cmd", required=True)
    common = argparse.ArgumentParser(add_help=False)
    common.add_argument("files", nargs="+", help="Topics Portfolio CSV(s)")
    common.add_argument("--exclude", default="",
                        help="brand names to leave out, separated by |")
    common.add_argument("--show", type=int, default=15)
    s = sub.add_parser("summary", parents=[common],
                       help="what's in the download")
    s.set_defaults(fn=cmd_summary)
    s = sub.add_parser("clusters", parents=[common],
                       help="rank opportunity themes")
    s.add_argument("--themes", default="",
                   help="your services: 'name=pattern,pattern|name=pattern'")
    s.add_argument("--out", default="topic_clusters.csv")
    s.set_defaults(fn=cmd_clusters)
    s = sub.add_parser("plan", parents=[common],
                       help="content cluster plan for one theme")
    s.add_argument("--theme", required=True,
                   help="'name=pattern,pattern', e.g. 'vibe coding=vibe cod,"
                        "vibecod'")
    s.add_argument("--base", default="",
                   help="folder for the URLs, e.g. 'learn'")
    s.add_argument("--per-page", type=int, default=8)
    s.add_argument("--out", default="content_cluster_plan.csv")
    s.set_defaults(fn=cmd_plan)
    s = sub.add_parser("compare", help="what changed between two downloads")
    s.add_argument("before", help="earlier download(s), separated by |")
    s.add_argument("after", help="later download(s), separated by |")
    s.add_argument("--theme", default="",
                   help="only queries in this theme, 'name=pattern,pattern'")
    s.add_argument("--show", type=int, default=15)
    s.add_argument("--out", default="topic_changes.csv")
    s.set_defaults(fn=cmd_compare)
    a = ap.parse_args(argv)
    return a.fn(a)


if __name__ == "__main__":
    sys.exit(main())

8. What We’re Watching Next in AI Search

In my view, Bing is currently providing SEO teams with some of the most useful reporting on AI citations.

Topics Portfolio is particularly interesting because it gives us information about opportunities, not just existing visibility.

However, there are several developments I would continue monitoring.

1. The Results of This Content Experiment

The first thing I want to understand is whether the vibe coding content cluster turns opportunity queries into actual grounding queries for the client.

September 2026 provides the baseline.

I will compare the October, November and December downloads to see:

  • Whether the client starts receiving citations for new queries.
  • Whether existing citations increase.
  • Which supporting pages receive the most citations.
  • Whether new content contributes to wider visibility across the Technology topic.

The objective is to measure actual changes rather than simply report the number of articles published.

2. Does Bing’s Topic Data Reflect Demand in Other AI Assistants?

This is another interesting question.

If topics that attract citation activity in Copilot also attract attention in ChatGPT, Gemini, Perplexity and Claude, Topics Portfolio could become a useful planning tool for AI search more broadly.

I’m testing this by tracking the same questions across those assistants.

It will be interesting to see whether the opportunities identified through Bing translate into improved visibility elsewhere.

3. When Will Topics Portfolio Leave Beta?

Bing has not announced a date for the feature to leave beta.

I would like to see more detailed information about its export formats, topic classification and the way citation opportunities are calculated.

It will also be interesting to see how Bing improves topic labels as more website owners provide feedback.

4. Will Bing Show Which Competitors Receive Citations?

One of the current limitations is that opportunity queries show where other websites are cited without always identifying exactly which websites they are.

That makes the competitor analysis less direct.

If Bing introduces reporting showing the individual websites cited for each opportunity query, it would make content gap analysis much more useful.

SEO teams could identify exactly which competitors and publications are getting visibility for particular questions. SD SEO Suite provides data for this by the way.

5. Will Google Introduce Topic-Level AI Search Reporting?

Google’s Generative AI Performance report currently shows pages appearing in AI Overviews and AI Mode.

However, it does not provide the same topic-level and grounding-query opportunity information available through Bing’s Topics Portfolio.

More detailed topic reporting from Google would give SEO teams another way to plan content around AI search demand.

It would also help businesses understand how their content performs across Google’s AI experiences.

6. How Quickly Will Vibe Coding Search Behaviour Change?

Vibe coding is a relatively new and fast-moving subject.

The terminology people use to research AI-assisted development is likely to change as new tools, platforms and working methods become more popular.

I will use the monthly comparison process to identify which queries are increasing, which are becoming less relevant and whether the content cluster needs to change.

A good AI SEO strategy should not remain fixed when the questions people ask are changing.

9. About Szymaniak Digital

Szymaniak Digital is a UK-based enterprise AI SEO consultancy led by Konrad Szymaniak.

We help B2B SaaS and enterprise marketing teams improve their visibility across Google and AI assistants, using search data to identify opportunities, develop content strategies and support customer acquisition.

Our approach combines traditional SEO, AI search measurement, technical SEO and content strategy.

We also run experiments using tools such as Bing Webmaster Tools to understand how AI search is changing and how businesses can use the available data to make better marketing decisions.

We’ve developed SD SEO Suite to help businesses monitor their visibility across ChatGPT, Gemini, Perplexity and Claude, alongside Google rankings and AI Overview citations.

Pro includes a 30-day free trial, while custom plans provide additional projects, daily AI tracking and team access.

For businesses that want to go beyond tracking, our consultancy team works with in-house marketing departments on AI search strategy, content and technical SEO.

The objective is to help businesses understand where AI search opportunities exist, create content that answers the questions their customers ask and improve their chances of being found, cited and recommended across AI assistants.

Scroll to Top