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

# Démarrage rapide de l’API

> Faites votre premier appel à l’API Qwairy, puis appuyez-vous sur le client partagé et les guides.

Passez d’un jeton d’API aux scores de performance de votre marque en un appel, puis réutilisez le client partagé et les guides détaillés pour des intégrations complètes.

<Note>
  Il vous faut un jeton d’API (`Bearer qw-api-...`, sur une [offre éligible à l’API](/fr/developers/api-keys)) et l’identifiant de votre marque. Consultez [Authentification](/fr/developers/authentication) pour créer un jeton et [Lister les marques](/fr/developers/endpoints/brands/list) pour trouver l’identifiant de votre marque.
</Note>

## Faire votre première requête

Obtenez les scores de performance de votre marque en une commande :

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

**Résultat :**

```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

Des intégrations détaillées, prêtes à copier, construites sur le client partagé ci-dessous.

<CardGroup cols={2}>
  <Card title="Tableau de bord sur mesure" icon="chart-line" href="/fr/developers/guides/custom-dashboard">
    Construisez un tableau de bord GEO à vos couleurs, avec KPI et classement des concurrents
  </Card>

  <Card title="Rapports hebdomadaires" icon="calendar-week" href="/fr/developers/guides/weekly-reports">
    Automatisez des rapports de performance d’une semaine sur l’autre
  </Card>

  <Card title="Analyse concurrentielle" icon="ranking-star" href="/fr/developers/guides/competitive-analysis">
    Suivez votre position face aux concurrents
  </Card>

  <Card title="Export de données" icon="database" href="/fr/developers/guides/data-export">
    Exportez les données vers vos outils BI (BigQuery, Snowflake, CSV)
  </Card>
</CardGroup>

***

## API client

Client réutilisable, avec gestion des erreurs, pour tous les guides de ce 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 body = await response.json().catch(() => ({}));

        if (response.status === 429) {
          const retryAfter = Number(response.headers.get('Retry-After') ?? '60');
          throw new Error(`Rate limited. Retry after ${retryAfter} second(s).`);
        }

        const nested = typeof body.error === 'object' ? body.error : null;
        const code = nested?.code ?? `HTTP_${response.status}`;
        const message = nested?.message ?? body.message ?? body.error ?? `HTTP ${response.status}`;
        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:
              try:
                  error_data = response.json() if response.content else {}
              except ValueError:
                  error_data = {}

              if response.status_code == 429:
                  retry_after = int(response.headers.get('Retry-After', '60'))
                  raise QwairyError(
                      'RATE_LIMITED',
                      f'Retry after {retry_after} second(s).',
                      429,
                  )

              error = error_data.get('error')
              nested = error if isinstance(error, dict) else {}
              code = nested.get('code', f'HTTP_{response.status_code}')
              message = nested.get('message') or error_data.get('message') or (
                  error if isinstance(error, str) else f'HTTP {response.status_code}'
              )
              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>
  Tous les guides de ce site utilisent ce client. Copiez-le dans votre projet ou adaptez-le à vos besoins.
</Note>

***

## Types TypeScript

Définitions de types pour le client de l’API.

```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 | null; end: string | null };
  scores: QwairyScores;
  methodology: QwairyMethodology;
  topCompetitors: QwairyCompetitor[];
  topSources: QwairySource[];
  byTopic?: QwairyTopicBreakdown[];
  byTag?: QwairyTagBreakdown[];
}

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

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

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 | null;
  createdAt: string;
}

interface QwairySearchQuery {
  query: string;
  occurrences: number;
  priority: 'very-high' | 'high' | 'medium' | 'low';
  brandPresence: number;
  competitorPresence: number;
  competitors: string[];
  uniquePrompts: number;
  prompts: Array<{ id: string; text: string }>;
  uniqueAnswers: number;
  uniqueModels: number;
  models: string[];
  topics: string[];
  tags: string[];
  firstSeen: string | null;
  lastSeen: string | null;
}

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 | null; isComplete: boolean; issuesCount: number; optimizationsCount: number } | null;
  robotsAnalysis: Record<string, unknown> | null;
  llmsAnalysis: Record<string, unknown> | null;
  sitemapAnalysis: Record<string, unknown> | null;
  lastAnalyzedAt: string | null;
}
```

***

## Pagination

Récupérez tous les résultats lorsque vous avez plus de 100 éléments.

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

***

## Référence des champs

Correspondance entre indicateurs métier et champs de l’API :

| Indicateur métier | Endpoint | Champ | Description |
| - | - | - | - |
| Taux de mention | `/performance` | `scores.mentionRate` | Réponses avec une mention SELF divisées par les réponses avec une mention SELF ou DIRECT |
| Taux de citation | `/performance` | `scores.sourceRate` | Réponses avec une citation SELF divisées par les réponses avec une citation SELF ou DIRECT |
| Couverture | `/performance` | `scores.coverage` | Réponses avec une mention SELF divisées par l’ensemble des réponses du périmètre sélectionné |
| Part de voix | `/performance` | `scores.shareOfVoice` | Occurrences de mentions SELF divisées par les occurrences SELF et DIRECT |
| Part de voix (par concurrent) | `/competitors` | `shareOfVoice` | Part des occurrences du concurrent dans l’ensemble de relations sélectionné par l’endpoint |
| Part des citations | `/source-domains` | `rate` | Occurrences de citation du domaine divisées par l’ensemble des occurrences de citation |
| Sentiment | `/performance` | `scores.sentiment` | Score de sentiment SELF moyen sur l’échelle de `0` à `100` |
| Position moyenne | `/competitors` | `avgPosition` | Rang moyen dans les réponses IA (1 = premier) |
| Mentionné mais non cité | `/answers` | `hasSelfMention` + `hasSelfSource` | Demandez `hasSelfMention=true`, puis filtrez côté client les lignes où `hasSelfSource` vaut `false` |
| Query Fan-Out | `/search` | `query` | Requêtes web conservées associées aux réponses enregistrées |
| Scores de perception | `/perception` | `current.scores` | Sentiment, alignement, cohérence, alignement factuel (0 à 100) |
| Articles de contenu | `/content` | `articles` | Articles Content Studio (Markdown via `/content/{id}`) |
| Score de préparation du site | `/technical-analysis` | `aiReadiness.score` | Préparation enregistrée de robots.txt, llms.txt et sitemap.xml (`0-100`, ou `null` si la vérification est incomplète) |

<Tip>
  Utilisez `/performance` pour les KPI agrégés, `/competitors` et `/source-domains` pour les ventilations détaillées, `/answers` et `/search` pour les diagnostics au niveau des LLM, et `/perception`, `/content` et `/technical-analysis` pour la réputation, le contenu et la préparation technique.
</Tip>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.