> ## 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.

# Quick Start

> Make your first Qwairy API call, then build on the shared client and guides

Go from an API token to your brand's performance scores in one call, then reuse the shared client and the worked guides for full integrations.

<Note>
  You need an API token (`Bearer qw-api-...`, Growth plan or above) and your brand ID. See [Authentication](/developers/authentication) to create a token and [List Brands](/developers/endpoints/brands/list) to find your brand ID.
</Note>

## Quick Start

Get your brand's performance scores in one command:

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -H "Authorization: Bearer $QWAIRY_API_TOKEN" \
    "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/performance?period=30" \
    | jq '.scores'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/performance?period=30',
    { headers: { 'Authorization': `Bearer ${process.env.QWAIRY_API_TOKEN}` } }
  );
  const { scores } = await response.json();
  console.log(`Mention Rate: ${scores.mentionRate}% | Share of Voice: ${scores.shareOfVoice}%`);
  ```

  ```python Python theme={null}
  import requests
  import os

  response = requests.get(
      'https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/performance?period=30',
      headers={'Authorization': f'Bearer {os.environ["QWAIRY_API_TOKEN"]}'}
  )
  scores = response.json()['scores']
  print(f"Mention Rate: {scores['mentionRate']}% | Share of Voice: {scores['shareOfVoice']}%")
  ```
</CodeGroup>

**Output:**

```json theme={null}
{
  "mentionRate": 45.2,
  "mentionCount": 104,
  "mentionTotal": 230,
  "coverage": 33.33,
  "sourceRate": 23.9,
  "sourceCount": 44,
  "sourceTotal": 184,
  "sourcePages": 27,
  "sentiment": 78.1,
  "shareOfVoice": 8.13
}
```

***

## Guides

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

<CardGroup cols={2}>
  <Card title="Custom Dashboard" icon="chart-line" href="/developers/guides/custom-dashboard">
    Build a branded GEO dashboard with KPIs and competitor rankings
  </Card>

  <Card title="Weekly Reports" icon="calendar-week" href="/developers/guides/weekly-reports">
    Automate week-over-week performance reports
  </Card>

  <Card title="Competitive Analysis" icon="ranking-star" href="/developers/guides/competitive-analysis">
    Track your position against competitors
  </Card>

  <Card title="Data Export" icon="database" href="/developers/guides/data-export">
    Export data for BI tools (BigQuery, Snowflake, CSV)
  </Card>
</CardGroup>

***

## API Client

Reusable client with error handling for all guides on this site.

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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);
  ```

  ```python Python theme={null}
  # qwairy_client.py
  import requests
  import os
  from typing import Optional, Dict, Any


  class QwairyError(Exception):
      """Custom exception for Qwairy API errors."""
      def __init__(self, code: str, message: str, status_code: int):
          self.code = code
          self.message = message
          self.status_code = status_code
          super().__init__(f"[{code}] {message}")


  class QwairyClient:
      def __init__(self, api_token: Optional[str] = None):
          self.base_url = 'https://www.qwairy.co/api/v1'
          self.api_token = api_token or os.environ.get('QWAIRY_API_TOKEN')
          if not self.api_token:
              raise ValueError("API token required. Set QWAIRY_API_TOKEN or pass to constructor.")

          self.session = requests.Session()
          self.session.headers.update({
              'Authorization': f'Bearer {self.api_token}',
              'Content-Type': 'application/json',
          })

      def _request(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
          url = f"{self.base_url}{endpoint}"
          clean_params = {k: v for k, v in (params or {}).items() if v is not None}
          response = self.session.get(url, params=clean_params)

          if not response.ok:
              error_data = response.json() if response.content else {}
              error = error_data.get('error', {})
              code = error.get('code', 'UNKNOWN_ERROR')
              message = error.get('message', f'HTTP {response.status_code}')

              if response.status_code == 429:
                  reset_time = response.headers.get('X-RateLimit-Reset', 'unknown')
                  raise QwairyError('RATE_LIMITED', f'Retry after: {reset_time}', 429)

              raise QwairyError(code, message, response.status_code)

          return response.json()

      def get_performance(self, brand_id: str, period: Optional[int] = None,
                          start_date: Optional[str] = None, end_date: Optional[str] = None) -> Dict:
          return self._request(f'/brands/{brand_id}/performance', {
              'period': period, 'startDate': start_date, 'endDate': end_date,
          })

      def get_competitors(self, brand_id: str, period: Optional[int] = None,
                          limit: int = 50, offset: int = 0,
                          sort: str = 'mentions', order: str = 'desc') -> Dict:
          return self._request(f'/brands/{brand_id}/competitors', {
              'period': period, 'limit': limit, 'offset': offset, 'sort': sort, 'order': order,
          })

      def get_source_domains(self, brand_id: str, period: Optional[int] = None,
                             limit: int = 50, offset: int = 0,
                             sort: str = 'mentions', order: str = 'desc') -> Dict:
          return self._request(f'/brands/{brand_id}/source-domains', {
              'period': period, 'limit': limit, 'offset': offset, 'sort': sort, 'order': order,
          })

      def get_source_urls(self, brand_id: str, period: Optional[int] = None,
                          limit: int = 50, offset: int = 0,
                          sort: str = 'mentions', order: str = 'desc') -> Dict:
          return self._request(f'/brands/{brand_id}/source-urls', {
              'period': period, 'limit': limit, 'offset': offset, 'sort': sort, 'order': order,
          })

      def get_answers(self, brand_id: str, period: Optional[int] = None,
                      limit: int = 50, offset: int = 0,
                      provider: Optional[str] = None,
                      has_self_mention: Optional[bool] = None,
                      has_self_source: Optional[bool] = None) -> Dict:
          return self._request(f'/brands/{brand_id}/answers', {
              'period': period, 'limit': limit, 'offset': offset,
              'provider': provider, 'hasSelfMention': has_self_mention, 'hasSelfSource': has_self_source,
          })

      def get_search(self, brand_id: str, period: Optional[int] = None,
                     limit: int = 50, offset: int = 0,
                     provider: Optional[str] = None) -> Dict:
          return self._request(f'/brands/{brand_id}/search', {
              'period': period, 'limit': limit, 'offset': offset, 'provider': provider,
          })

      def get_perception(self, brand_id: str, months: Optional[int] = None) -> Dict:
          return self._request(f'/brands/{brand_id}/perception', {
              'months': months,
          })

      def get_content(self, brand_id: str, status: Optional[str] = None,
                       limit: int = 50, offset: int = 0) -> Dict:
          return self._request(f'/brands/{brand_id}/content', {
              'status': status, 'limit': limit, 'offset': offset,
          })

      def get_technical_analysis(self, brand_id: str) -> Dict:
          return self._request(f'/brands/{brand_id}/technical-analysis', {})

      def get_competitor_evolution(self, brand_id: str, competitor_id: str,
                                    period: Optional[int] = None) -> Dict:
          return self._request(f'/brands/{brand_id}/competitors/{competitor_id}/evolution', {
              'period': period,
          })


  # Usage
  client = QwairyClient()
  ```
</CodeGroup>

<Note>
  All guides on this site use this client. Copy it to your project or adapt it to your needs.
</Note>

***

## TypeScript Types

Type definitions for the API client.

```typescript theme={null}
// qwairy-types.ts
interface QwairyScores {
  mentionRate: number;
  mentionCount: number;
  mentionTotal: number;
  coverage: number;
  sourceRate: number;
  sourceCount: number;
  sourceTotal: number;
  sourcePages: number;
  sentiment: number;
  shareOfVoice: number;
}

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

interface QwairyTopicBreakdown {
  id: string;
  topic: string;
  score: number;
  mentionRate: number;
  sourceRate: number;
  shareOfVoice: number;
  avgSentiment: number | null;
  promptsCount: number;
}

interface QwairyTagBreakdown {
  id: string;
  name: string;
  score: number;
  mentionRate: number;
  sourceRate: number;
  shareOfVoice: number;
  avgSentiment: number | null;
  promptsCount: number;
}

interface QwairyPerformance {
  success: boolean;
  brand: { id: string; name: string; domain: string | null };
  period: { start: string; end: string };
  scores: QwairyScores;
  methodology: QwairyMethodology;
  topCompetitors: QwairyCompetitor[];
  topSources: QwairySource[];
  byTopic?: QwairyTopicBreakdown[];
  byTag?: QwairyTagBreakdown[];
}

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.

<CodeGroup>
  ```javascript JavaScript theme={null}
  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}`);
  ```

  ```python Python theme={null}
  def fetch_all_competitors(client, brand_id: str, period: int = 30) -> list:
      """Fetch all competitors with pagination."""
      all_competitors = []
      offset = 0
      limit = 100

      while True:
          response = client.get_competitors(brand_id, period=period, limit=limit, offset=offset)
          all_competitors.extend(response['competitors'])

          if len(response['competitors']) < limit or len(all_competitors) >= response['pagination']['total']:
              break
          offset += limit

      return all_competitors

  # Usage
  all_competitors = fetch_all_competitors(client, 'your-brand-id', period=30)
  print(f"Total competitors: {len(all_competitors)}")
  ```
</CodeGroup>

***

## Field Reference

Mapping between business metrics and API fields:

| Business Metric                 | Endpoint              | Field                              | Description                                                  |
| ------------------------------- | --------------------- | ---------------------------------- | ------------------------------------------------------------ |
| Mention Rate                    | `/performance`        | `scores.mentionRate`               | % of brand-mentioning responses that mention yours           |
| Citation Rate                   | `/performance`        | `scores.sourceRate`                | % of source-citing responses that cite your domain           |
| Coverage                        | `/performance`        | `scores.coverage`                  | % of all monitored responses mentioning your brand           |
| Share of Voice                  | `/performance`        | `scores.shareOfVoice`              | Your % of all brand mentions (SELF + DIRECT)                 |
| Share of Voice (per competitor) | `/competitors`        | `shareOfVoice`                     | A competitor's % of all brand mentions                       |
| Share of Citations              | `/source-domains`     | `rate`                             | Source's % of all citations                                  |
| Sentiment                       | `/performance`        | `scores.sentiment`                 | Average sentiment score (0-100)                              |
| Average Position                | `/competitors`        | `avgPosition`                      | Average rank in AI responses (1 = first)                     |
| Mentioned but not cited         | `/answers`            | `hasSelfMention` + `hasSelfSource` | Responses where you're mentioned but not linked              |
| AI search queries               | `/search`             | `query`                            | Web queries triggered by AI before responding                |
| Perception scores               | `/perception`         | `current.scores`                   | Sentiment, alignment, consistency, factual alignment (0-100) |
| Content articles                | `/content`            | `articles`                         | Content Studio articles (Markdown via `/content/{id}`)       |
| AI Readiness Score              | `/technical-analysis` | `aiReadiness.score`                | robots/llms/sitemap readiness (0-100)                        |

<Tip>
  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.
</Tip>
