Skip to main content
Build a custom dashboard displaying your brand’s GEO performance metrics, competitor rankings, and top sources.
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: GEO Score, Mention Rate, Source Rate, 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.getSources(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: {
      geoScore: performance.scores.global,
      mentionRate: performance.scores.mentionRate,
      sourceRate: performance.scores.sourceRate,
      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,
    },
  };
}

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);

Example Output

{
  "kpis": {
    "geoScore": 62,
    "mentionRate": 45.2,
    "sourceRate": 23.9,
    "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

FieldTypeDescription
kpis.geoScorenumberOverall GEO optimization score (0-100)
kpis.mentionRatenumber% of AI responses mentioning your brand
kpis.sourceRatenumber% of AI responses citing your domain
kpis.shareOfVoicenumberYour % of all competitor mentions
kpis.sentimentnumberAverage sentiment score (0-100)
competitorRanking[].gapstringDifference in Share of Voice vs your brand
sourceRanking[].shareOfCitationsnumberSource’s % of all citations (rate field)
sourceRanking[].isSelfbooleanWhether this is your own domain

Next Steps