Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.qwairy.co/llms.txt

Use this file to discover all available pages before exploring further.

Go from an API token to your GEO Score in one call, then reuse the shared client and the worked guides for full integrations.
You need an API token (Bearer qw-api-..., Growth plan or above) and your brand ID. See Authentication to create a token and List Brands to find your brand ID.

Quick Start

Get your GEO Score in one command:
curl -s -H "Authorization: Bearer $QWAIRY_API_TOKEN" \
  "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/performance?period=30" \
  | jq '.scores'
Output:
{
  "global": 62,
  "mentionRate": 45.2,
  "sourceRate": 23.9,
  "shareOfVoice": 8.13,
  "sentiment": 78.1
}

Guides

Worked, copy-pasteable integrations built on the shared client below.

Custom Dashboard

Build a branded GEO dashboard with KPIs and competitor rankings

Weekly Reports

Automate week-over-week performance reports

Competitive Analysis

Track your position against competitors

Data Export

Export data for BI tools (BigQuery, Snowflake, CSV)

API Client

Reusable client with error handling for all guides on this site.
// qwairy-client.js
class QwairyClient {
  constructor(apiToken) {
    this.baseUrl = 'https://www.qwairy.co/api/v1';
    this.headers = {
      'Authorization': `Bearer ${apiToken}`,
      'Content-Type': 'application/json',
    };
  }

  async request(endpoint, params = {}) {
    const url = new URL(`${this.baseUrl}${endpoint}`);
    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined && value !== null) {
        url.searchParams.append(key, value);
      }
    });

    const response = await fetch(url.toString(), { headers: this.headers });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      const code = error.error?.code || 'UNKNOWN_ERROR';
      const message = error.error?.message || `HTTP ${response.status}`;

      if (response.status === 429) {
        const resetTime = response.headers.get('X-RateLimit-Reset');
        throw new Error(`Rate limited. Retry after: ${resetTime}`);
      }

      throw new Error(`[${code}] ${message}`);
    }

    return response.json();
  }

  async getPerformance(brandId, params = {}) {
    return this.request(`/brands/${brandId}/performance`, params);
  }

  async getCompetitors(brandId, params = {}) {
    return this.request(`/brands/${brandId}/competitors`, params);
  }

  async getSourceDomains(brandId, params = {}) {
    return this.request(`/brands/${brandId}/source-domains`, params);
  }

  async getSourceUrls(brandId, params = {}) {
    return this.request(`/brands/${brandId}/source-urls`, params);
  }

  async getAnswers(brandId, params = {}) {
    return this.request(`/brands/${brandId}/answers`, params);
  }

  async getSearch(brandId, params = {}) {
    return this.request(`/brands/${brandId}/search`, params);
  }

  async getPerception(brandId, params = {}) {
    return this.request(`/brands/${brandId}/perception`, params);
  }

  async getContent(brandId, params = {}) {
    return this.request(`/brands/${brandId}/content`, params);
  }

  async getTechnicalAnalysis(brandId, params = {}) {
    return this.request(`/brands/${brandId}/technical-analysis`, params);
  }

  async getCompetitorEvolution(brandId, competitorId, params = {}) {
    return this.request(`/brands/${brandId}/competitors/${competitorId}/evolution`, params);
  }
}

// Usage
const client = new QwairyClient(process.env.QWAIRY_API_TOKEN);
All guides on this site use this client. Copy it to your project or adapt it to your needs.

TypeScript Types

Type definitions for the API client.
// qwairy-types.ts
interface QwairyScores {
  global: number;
  mentionRate: number;
  sourceRate: number;
  shareOfVoice: number;
  sentiment: number;
}

interface QwairyMethodology {
  promptsCount: number;
  responsesTotal: number;
  providers: string[];
}

interface QwairyPerformance {
  scores: QwairyScores;
  methodology: QwairyMethodology;
  byProvider?: Array<{ provider: string; scores: QwairyScores }>;
  byTopic?: Array<{ topic: string; scores: QwairyScores }>;
  byTag?: Array<{ tag: string; scores: QwairyScores }>;
}

interface QwairyCompetitor {
  id: string;
  name: string;
  relationship: 'SELF' | 'DIRECT' | 'INDIRECT';
  totalMentions: number;
  shareOfVoice: number;
  avgPosition: number;
  avgSentiment: number;
}

interface QwairySource {
  id: string;
  domain: string;
  type: 'INSTITUTIONAL' | 'COMMERCIAL' | 'MEDIA' | 'BLOG' | 'SOCIAL' | 'OTHER';
  isSelf: boolean;
  totalMentions: number;
  rate: number;
  avgPosition: number;
}

interface QwairyPagination {
  total: number;
  count: number;
  limit: number;
  offset: number;
}

interface QwairyCompetitorsResponse {
  success: boolean;
  pagination: QwairyPagination;
  competitors: QwairyCompetitor[];
}

interface QwairySourcesResponse {
  success: boolean;
  pagination: QwairyPagination;
  sources: QwairySource[];
}

interface QwairyAnswer {
  id: string;
  promptId: string;
  promptText: string;
  provider: string;
  model: string;
  textPreview: string;
  hasSelfMention: boolean;
  selfMentionPosition: number | null;
  hasSelfSource: boolean;
  competitorsCount: number;
  sourcesCount: number;
  sentiment: number;
  relevance: number;
  createdAt: string;
}

interface QwairySearchQuery {
  id: string;
  query: string;
  prompt: string;
  provider: string;
  createdAt: string;
}

interface QwairyAnswersResponse {
  success: boolean;
  pagination: QwairyPagination;
  answers: QwairyAnswer[];
}

interface QwairySearchResponse {
  success: boolean;
  pagination: QwairyPagination;
  searches: QwairySearchQuery[];
}

interface QwairyPerceptionScores {
  sentiment: number | null;
  alignment: number | null;
  consistency: number | null;
  factualAlignment: number | null;
}

interface QwairyPerceptionResponse {
  success: boolean;
  current: { snapshotId: string; month: number; year: number; scores: QwairyPerceptionScores } | null;
  previous: { snapshotId: string; month: number; year: number; scores: QwairyPerceptionScores } | null;
  trends: QwairyPerceptionScores;
  averages: QwairyPerceptionScores;
}

interface QwairyArticle {
  id: string;
  title: string;
  slug: string | null;
  articleType: string;
  status: 'DRAFT' | 'GENERATING' | 'LIVE' | 'ARCHIVED';
  wordCount: number;
  finalUrl: string | null;
  publishedAt: string | null;
}

interface QwairyContentResponse {
  success: boolean;
  pagination: QwairyPagination;
  articles: QwairyArticle[];
}

interface QwairyTechnicalAnalysisResponse {
  success: boolean;
  analyzed: boolean;
  aiReadiness: { score: number; issuesCount: number; optimizationsCount: number } | null;
  robotsAnalysis: Record<string, unknown> | null;
  llmsAnalysis: Record<string, unknown> | null;
  sitemapAnalysis: Record<string, unknown> | null;
  lastAnalyzedAt: string | null;
}

Pagination

Fetch all results when you have more than 100 items.
async function fetchAllCompetitors(client, brandId, period = 30) {
  const allCompetitors = [];
  let offset = 0;
  const limit = 100;

  while (true) {
    const response = await client.getCompetitors(brandId, { period, limit, offset });
    allCompetitors.push(...response.competitors);

    if (response.competitors.length < limit || allCompetitors.length >= response.pagination.total) {
      break;
    }
    offset += limit;
  }

  return allCompetitors;
}

// Usage
const allCompetitors = await fetchAllCompetitors(client, 'your-brand-id', 30);
console.log(`Total competitors: ${allCompetitors.length}`);

Field Reference

Mapping between business metrics and API fields:
Business MetricEndpointFieldDescription
GEO Score/performancescores.globalOverall optimization score (0-100)
Brand Visibility/performancescores.mentionRate% of responses mentioning your brand
Citation Rate/performancescores.sourceRate% of responses citing your content
Share of Voice/competitorsshareOfVoiceYour % of all competitor mentions
Share of Citations/source-domainsrateSource’s % of all citations
Sentiment/performancescores.sentimentAverage sentiment score (0-100)
Average Position/competitorsavgPositionAverage rank in AI responses (1 = first)
Mentioned but not cited/answershasSelfMention + hasSelfSourceResponses where you’re mentioned but not linked
AI search queries/searchqueryWeb queries triggered by AI before responding
Perception scores/perceptioncurrent.scoresSentiment, alignment, consistency, factual alignment (0-100)
Content articles/contentarticlesContent Studio articles (Markdown via /content/{id})
AI Readiness Score/technical-analysisaiReadiness.scorerobots/llms/sitemap readiness (0-100)
Use /performance for aggregated KPIs. Use /competitors and /source-domains for detailed breakdowns. Use /answers and /search for LLM-level diagnostics. Use /perception, /content and /technical-analysis for reputation, content and technical readiness.