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 brand’s performance scores 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'
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}%`);
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']}%")
{
"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.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);
# 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()
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 {
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.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}`);
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)}")
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) |
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.
