This guide uses the API client from the Guides index. Copy it to your project first.
What You’ll Build
A dashboard with:- KPI cards: Mention Rate, Source Rate, Coverage, Share of Voice, Sentiment
- Competitor ranking: Share of Voice comparison with gap analysis
- Source ranking: Share of Citations by domain
Fetch Dashboard Data
Fetch all data in parallel for optimal performance.async function fetchDashboardData(client, brandId, period = 30) {
// Parallel API calls
const [performance, competitors, sources] = await Promise.all([
client.getPerformance(brandId, { period }),
client.getCompetitors(brandId, { period, limit: 10, sort: 'shareOfVoice', order: 'desc' }),
client.getSourceDomains(brandId, { period, limit: 10, sort: 'mentions', order: 'desc' }),
]);
// Find your brand in competitors list
const yourBrand = competitors.competitors.find(c => c.relationship === 'SELF');
const directCompetitors = competitors.competitors.filter(c => c.relationship === 'DIRECT');
return {
kpis: {
mentionRate: performance.scores.mentionRate,
sourceRate: performance.scores.sourceRate,
coverage: performance.scores.coverage,
shareOfVoice: performance.scores.shareOfVoice,
sentiment: performance.scores.sentiment,
},
yourBrand: yourBrand ? {
name: yourBrand.name,
mentions: yourBrand.totalMentions,
shareOfVoice: yourBrand.shareOfVoice,
avgPosition: yourBrand.avgPosition,
sentiment: yourBrand.avgSentiment,
} : null,
competitorRanking: directCompetitors.map(c => ({
name: c.name,
shareOfVoice: c.shareOfVoice,
mentions: c.totalMentions,
gap: yourBrand ? (c.shareOfVoice - yourBrand.shareOfVoice).toFixed(2) : null,
})),
sourceRanking: sources.sources.map(s => ({
domain: s.domain,
shareOfCitations: s.rate,
mentions: s.totalMentions,
isSelf: s.isSelf,
})),
methodology: {
period: `${period} days`,
promptsAnalyzed: performance.methodology.promptsCount,
responsesGenerated: performance.methodology.responsesTotal,
providers: performance.methodology.providers,
},
};
}
from dataclasses import dataclass
from typing import List, Optional
from concurrent.futures import ThreadPoolExecutor
@dataclass
class DashboardData:
kpis: dict
your_brand: Optional[dict]
competitor_ranking: List[dict]
source_ranking: List[dict]
methodology: dict
def fetch_dashboard_data(client, brand_id: str, period: int = 30) -> DashboardData:
"""Fetch all dashboard data in parallel."""
with ThreadPoolExecutor(max_workers=3) as executor:
perf_future = executor.submit(client.get_performance, brand_id, period=period)
comp_future = executor.submit(client.get_competitors, brand_id, period=period, limit=10, sort='shareOfVoice')
src_future = executor.submit(client.get_source_domains, brand_id, period=period, limit=10, sort='mentions')
performance = perf_future.result()
competitors = comp_future.result()
sources = src_future.result()
your_brand = next((c for c in competitors['competitors'] if c['relationship'] == 'SELF'), None)
direct_competitors = [c for c in competitors['competitors'] if c['relationship'] == 'DIRECT']
return DashboardData(
kpis={
'mention_rate': performance['scores']['mentionRate'],
'source_rate': performance['scores']['sourceRate'],
'coverage': performance['scores']['coverage'],
'share_of_voice': performance['scores']['shareOfVoice'],
'sentiment': performance['scores']['sentiment'],
},
your_brand={
'name': your_brand['name'],
'mentions': your_brand['totalMentions'],
'share_of_voice': your_brand['shareOfVoice'],
'avg_position': your_brand['avgPosition'],
'sentiment': your_brand['avgSentiment'],
} if your_brand else None,
competitor_ranking=[
{
'name': c['name'],
'share_of_voice': c['shareOfVoice'],
'mentions': c['totalMentions'],
'gap': round(c['shareOfVoice'] - your_brand['shareOfVoice'], 2) if your_brand else None,
}
for c in direct_competitors
],
source_ranking=[
{
'domain': s['domain'],
'share_of_citations': s['rate'],
'mentions': s['totalMentions'],
'is_self': s['isSelf'],
}
for s in sources['sources']
],
methodology={
'period': f"{period} days",
'prompts_analyzed': performance['methodology']['promptsCount'],
'responses_generated': performance['methodology']['responsesTotal'],
'providers': performance['methodology']['providers'],
},
)
Usage
const client = new QwairyClient(process.env.QWAIRY_API_TOKEN);
const dashboard = await fetchDashboardData(client, 'your-brand-id', 30);
console.log('KPIs:', dashboard.kpis);
console.log('Your position:', dashboard.yourBrand);
console.log('Top competitors:', dashboard.competitorRanking);
console.log('Top sources:', dashboard.sourceRanking);
client = QwairyClient()
dashboard = fetch_dashboard_data(client, 'your-brand-id', period=30)
print(f"Mention Rate: {dashboard.kpis['mention_rate']}%")
print(f"Your Share of Voice: {dashboard.your_brand['share_of_voice']}%")
print(f"Top competitor gap: {dashboard.competitor_ranking[0]['gap']}%")
Example Output
{
"kpis": {
"mentionRate": 45.2,
"sourceRate": 23.9,
"coverage": 33.33,
"shareOfVoice": 8.13,
"sentiment": 78.1
},
"yourBrand": {
"name": "My Brand",
"mentions": 104,
"shareOfVoice": 8.13,
"avgPosition": 2.1,
"sentiment": 78.1
},
"competitorRanking": [
{ "name": "Competitor A", "shareOfVoice": 12.5, "mentions": 156, "gap": "4.37" },
{ "name": "Competitor B", "shareOfVoice": 9.8, "mentions": 122, "gap": "1.67" }
],
"sourceRanking": [
{ "domain": "industry-news.com", "shareOfCitations": 5.10, "mentions": 102, "isSelf": false },
{ "domain": "mybrand.com", "shareOfCitations": 2.25, "mentions": 45, "isSelf": true }
],
"methodology": {
"period": "30 days",
"promptsAnalyzed": 156,
"responsesGenerated": 312,
"providers": ["chatgpt", "perplexity"]
}
}
Data Structure Reference
| Field | Type | Description |
|---|---|---|
kpis.mentionRate | number | % of brand-mentioning AI responses that mention yours |
kpis.sourceRate | number | % of source-citing AI responses that cite your domain |
kpis.coverage | number | % of all monitored AI responses mentioning your brand |
kpis.shareOfVoice | number | Your % of all brand mentions (SELF + DIRECT) |
kpis.sentiment | number | Average sentiment score (0-100) |
competitorRanking[].gap | string | Difference in Share of Voice vs your brand |
sourceRanking[].shareOfCitations | number | Source’s % of all citations (rate field) |
sourceRanking[].isSelf | boolean | Whether this is your own domain |
LLM Diagnostics (Optional)
Go beyond aggregate scores: identify responses where your brand is mentioned but not cited (missed link opportunities), and discover what web queries AI models trigger before answering.async function fetchDiagnostics(client, brandId, period = 30) {
const [mentionedNotCited, searchQueries] = await Promise.all([
client.getAnswers(brandId, {
period,
hasSelfMention: true,
hasSelfSource: false,
limit: 5,
sort: 'createdAt',
order: 'desc',
}),
client.getSearch(brandId, { period, limit: 5, sort: 'createdAt', order: 'desc' }),
]);
return {
// Responses where AI mentions you but doesn't link to your content
missedCitations: mentionedNotCited.answers.map(a => ({
prompt: a.promptText,
provider: a.provider,
position: a.selfMentionPosition,
competitors: a.competitorsCount,
})),
// Web queries AI models run before generating a response
aiSearchQueries: searchQueries.searches.map(s => ({
query: s.query,
prompt: s.prompt,
provider: s.provider,
})),
};
}
def fetch_diagnostics(client, brand_id: str, period: int = 30) -> dict:
"""Fetch LLM diagnostic data."""
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=2) as executor:
mention_future = executor.submit(
client.get_answers, brand_id, period=period,
has_self_mention=True, has_self_source=False, limit=5,
)
search_future = executor.submit(
client.get_search, brand_id, period=period, limit=5,
)
mentioned_not_cited = mention_future.result()
search_queries = search_future.result()
return {
'missed_citations': [
{
'prompt': a['promptText'],
'provider': a['provider'],
'position': a.get('selfMentionPosition'),
'competitors': a['competitorsCount'],
}
for a in mentioned_not_cited['answers']
],
'ai_search_queries': [
{
'query': s['query'],
'prompt': s['prompt'],
'provider': s.get('provider'),
}
for s in search_queries['searches']
],
}
Missed citations reveal where AI talks about you but doesn’t link to your content — your highest-ROI content opportunities. AI search queries show what AI models actually search for before responding, giving you direct content targeting signals.
Next Steps
- Add weekly reports to track changes over time
- Use competitive analysis for deeper insights
- Export data to your BI tools

