# Manage agency clients Source: https://docs.qwairy.co/agencies/agency-guide Choose a client isolation, access, branding, and reporting model for multiple Qwairy workspaces This guide is for agencies and consultancies running GEO monitoring for more than one client brand in Qwairy. It connects the pieces you'll actually reach for: how to isolate each client's account, how much access to give them, how to make the experience look like yours, and how to report back to them. Before onboarding your first client, make three decisions: 1. **Isolation model**: does each client get their own contained account, or do you manage every brand under one team? 2. **Access model**: does the client just view reports, or do they actively manage their own brand? 3. **Branding**: does the client see Qwairy, or does the experience look like your agency's own product? The rest of this guide walks through each decision and the setup that follows from it. *** ## Choose an access model The core decision is how much a client sees and controls. Qwairy gives you two mechanisms, and they serve different situations: | | **Virtual Teams (sub-team)** | **Viewer role** | | -------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------- | | **Scope** | Full sub-team: its own members, workspaces, and credit pool | Single brand, read-only | | **Client can** | Actively manage the brand: prompts, competitors, settings | View dashboards and reports only | | **Isolation** | Separate sub-team; client access does not include the parent organization | Same organization; read-only access is limited to assigned brands | | **Billing** | Inherits your organization's plan; sub-team draws from allocated credits | N/A: no separate credit pool | | **Best for** | Clients who need hands-on management of their own brand | Stakeholders who just need to check numbers | Use a [Virtual Team](/documentation/team/virtual-teams) when the client needs to manage the brand rather than only read it. Viewer access stays read-only and brand-scoped within your organization, while a sub-team transfer gives management access in a separate team context. If neither fits: for example, you're a small team managing a handful of client brands yourself, with no client logins at all: you can simply keep everything under one team and control brand access per [team member](/documentation/team/manage-members) instead. Virtual Teams is available on **Business, Enterprise, and Agency Business plans**, or through an explicit feature override. *** ## Set up a client To give a client a fully isolated, self-service account for their brand: Go to **Team Management > Virtual Teams** and click **Create**. Name it after the client, and allocate a credit amount and workspace quota from your organization's pool. Either tick the workspace under **Move existing workspaces** while creating the sub-team, or move it afterwards with **Move workspace**. The workspace's full monitoring history travels with it: nothing resets. Switch to the client's sub-team and go to **Team Management > Members** to invite them. Their client access is limited to workspaces inside that sub-team. Only Owners and Managers of your parent organization can move workspaces, and they retain access to every sub-team automatically without a separate invitation. If you'd rather keep the client's brand inside your own organization and just let a stakeholder check the numbers, skip the sub-team and add them as a [Viewer](/documentation/workspace/viewers) scoped to that one brand instead. *** ## Apply agency branding Once a client is set up, you can make the whole experience look like your agency's own product instead of Qwairy's: Replace the Qwairy logo across the dashboard, shared reports, and login. Serve the dashboard and login from your own subdomain, e.g. `analytics.yourcompany.com`. Hide "Powered by Qwairy" across the dashboard, shared links, and exports. White-labeling is an **Enterprise** capability, configured under **Team Management > White Label**. See [Agencies overview](/agencies/introduction) for the full picture, including fully white-labeled authentication via SSO. *** ## Report to clients Two ways to get results in front of clients, depending on whether they have a Qwairy login: * **[Pitch Audits](/agencies/pitch-audits)**: create pre-sales audits for prospects before onboarding them into a full workspace, with prompt review, AI answer evidence, detected competitors, cited sources, and shareable reports. * **[Shared links](/documentation/workspace/shared-links)**: generate a public URL for a dashboard, brand perception snapshot, GEO Matrix, competitor comparison, or prompt signals view. No Qwairy account needed on the client's end, and links can be set to expire or revoked at any time. * **[Weekly report emails](/agencies/notifications)**: enable the per-workspace weekly report so clients get a recurring summary without checking the dashboard. * **Outbound webhooks**: if your agency has its own reporting systems, route Qwairy's notification events to your own endpoint as signed JSON instead of Qwairy emails. This is an Enterprise capability; see [Developers > Webhooks](/developers/webhooks) for the payload format and setup, which starts in the same **Team Management > Notifications** screen as the weekly report. *** ## Related pages * [Pitch Audits](/agencies/pitch-audits): pre-sales audits and shareable sales reports * [Virtual Teams](/documentation/team/virtual-teams): sub-teams, credit allocation, and workspace transfers * [Viewers](/documentation/workspace/viewers): read-only, brand-scoped access * [Shared Links](/documentation/workspace/shared-links): public report links for clients without an account * [Agencies Overview](/agencies/introduction): white-label capabilities at a glance * [Custom Domain Overview](/agencies/custom-domain/overview): serve Qwairy from your own subdomain * [Developers: Webhooks](/developers/webhooks): outbound notification events for your own systems # DNS Setup Source: https://docs.qwairy.co/agencies/custom-domain/dns-setup Add your custom domain in White Label settings and point it to Qwairy with a CNAME record at your DNS provider. Setting up a custom domain takes two parts: adding the subdomain in Qwairy, then creating a CNAME record at your DNS provider. ## Add your domain in Qwairy Go to **Team Management > White Label** in your Qwairy dashboard. In the custom domain section, enter the subdomain you want to use (e.g. `analytics.yourcompany.com`) and save. Qwairy displays the **CNAME target** to point your subdomain to. Keep this value handy for the next step. Use a subdomain (e.g. `analytics.yourcompany.com`). Apex (root) domains are not supported. ## Create the CNAME record In your DNS provider (Cloudflare, Route 53, GoDaddy, etc.), add a new **CNAME** record: ``` Type: CNAME Name: analytics (your subdomain) Value: ``` * **Name**: the subdomain you added in Qwairy (for `analytics.yourcompany.com`, the name is usually `analytics`) * **Value**: the CNAME target shown in your White Label settings DNS providers label these fields differently. **Name** may appear as Host or Record, and **Value** may appear as Target, Points to, or Content. Use the exact target shown in your White Label settings. DNS propagation can take some time: usually minutes, but it can take up to 48 hours depending on your provider. ## Next step Once the record is in place, head to [Verification](/agencies/custom-domain/verification) to confirm your configuration and activate the domain. # Custom domain Source: https://docs.qwairy.co/agencies/custom-domain/overview Serve the Qwairy dashboard and login from a branded subdomain such as analytics.example.com A custom domain lets you serve the Qwairy dashboard and login from your own subdomain instead of the default Qwairy URL. Your team and clients sign in and work entirely under your brand. ## What a custom domain changes * **A branded dashboard**: access Qwairy from a subdomain you control, such as `analytics.yourcompany.com` * **A branded login**: your team signs in on your domain, with your [logo](/agencies/logo) on the sign-in page * **A consistent experience**: paired with your logo and [removed branding](/agencies/remove-branding), the product looks and feels like your own The custom domain brands the **sign-in page**, but magic-link emails and the Google consent screen stay Qwairy-branded by default. For fully white-labeled authentication on your domain, connect your own identity provider with [SSO](/documentation/team/sso): your IdP handles login, and with SSO + JIT provisioning, matching users are added to your workspace automatically on first sign-in. ## Prerequisites You'll need access to your DNS provider to add a record for the subdomain you want to use (e.g. `analytics.yourcompany.com`). Apex (root) domains such as `yourcompany.com` are not supported. Choose a subdomain like `analytics.yourcompany.com` or `ai.yourcompany.com`. Root (apex) domains are not supported. Always use a subdomain. ## How setup works Add your domain in White Label settings and create the CNAME record at your DNS provider. Verify your DNS, and the Qwairy team finalizes activation so your team can sign in at your domain. # Verification Source: https://docs.qwairy.co/agencies/custom-domain/verification Verify your DNS configuration to activate your custom domain. Provisioning and the HTTPS certificate are handled automatically so your team can sign in at your domain. Once your CNAME record is in place, verify it in Qwairy to activate your custom domain. ## Verify your DNS Go to **Team Management > White Label** in your Qwairy dashboard. Next to your custom domain, click **Verify DNS Configuration**. Qwairy checks that your CNAME record points to the correct target. If verification doesn't pass right away, your DNS record may still be propagating. Wait a little and try again: propagation can take up to 48 hours. ## Activation Once your CNAME record is in place, Qwairy provisions your domain automatically and a secure HTTPS certificate is issued for it. Verification confirms everything is live, and your team can then sign in to Qwairy at your domain. Provisioning and the certificate are automatic - there is nothing else to configure on your side. If verification does not pass right away, your DNS may still be propagating; wait a little and try again. ## Sign in on your domain A few things to keep in mind once your domain is live: * **Workspace membership is required**: every user who signs in on your domain must be a member of your workspace. * **Independent sessions**: sessions on your domain are separate from sessions on [www.qwairy.co](https://www.qwairy.co). Signing in on one does not sign you in on the other. Pair your custom domain with a [custom logo](/agencies/logo) and [removed branding](/agencies/remove-branding) for a fully branded sign-in and dashboard experience. ## FAQ Confirm the CNAME record's **Name** matches the subdomain you added in Qwairy, and its **Value** matches the target shown in your White Label settings exactly. DNS changes can take up to 48 hours to propagate, so it's worth trying again after a short wait. No. Apex (root) domains are not supported. Use a subdomain such as `analytics.yourcompany.com`. Yes. Every user who signs in on your domain must be a member of your workspace. You can invite them from **Team Management**. Sessions on your custom domain are independent from sessions on [www.qwairy.co](http://www.qwairy.co). Signing in on one surface does not carry over to the other: this is expected. # Agency white label Source: https://docs.qwairy.co/agencies/introduction Configure a custom logo, dashboard domain, login branding, and removal of Qwairy attribution White label lets you apply your organization's branding to supported Qwairy surfaces. Upload a logo, serve the dashboard and login from a subdomain, and remove supported "Powered by Qwairy" attribution. ## What's included Replace the Qwairy logo with your own across the dashboard, shared reports, and your branded login. Serve the dashboard and login from your own subdomain (e.g. `analytics.yourcompany.com`). Hide the "Powered by Qwairy" branding across the dashboard, shared links, and exports. ## Authentication On a custom domain, the **sign-in page is branded** with your logo and name. By default, the sign-in methods themselves stay Qwairy-branded: magic-link emails come from Qwairy and the Google consent screen shows "Qwairy". For **fully white-labeled authentication**: where your users authenticate entirely under your brand and never see Qwairy: connect your own identity provider via [SSO](/documentation/team/sso). Your IdP then handles sign-in, and with enforcement on it becomes the only way your domain's users log in. ## Who it's for White-Label is designed for teams that share Qwairy with clients, partners, or internal stakeholders under their own brand: agencies, consultancies, and organizations that want a fully branded analytics experience. ## Enable white label White-Label is an **Enterprise** capability. White-Label is available on the **Enterprise plan**. To enable it for your team, talk to your account manager, or [book a demo](https://www.qwairy.co/demo). Once it's enabled for your team, you'll find the controls under **Team Management > White Label**. ## Explore Upload your logo and learn where it appears. Hide the "Powered by Qwairy" branding. Serve Qwairy from your own subdomain. Add your domain and create the CNAME record. Verify your DNS and activate your domain. Control which emails Qwairy sends, or route events to your own webhook. Run pre-sales prospect audits and share reports before client onboarding. Fully white-label authentication with your own identity provider. # Custom Logo Source: https://docs.qwairy.co/agencies/logo Upload your own logo to replace Qwairy branding across the dashboard, shared reports, and your branded login. Uploading a custom logo replaces the Qwairy logo with your own throughout the experience your team and clients see. ## Upload your logo Go to **Team Management > White Label** in your Qwairy dashboard. In the logo section, click **Upload** and select your image file. Your logo is applied right away. ## Accepted formats | Requirement | Detail | | ---------------- | --------------- | | **File types** | PNG, JPEG, WebP | | **Maximum size** | 5 MB | SVG files are not supported. Upload a PNG, JPEG, or WebP image instead. For the sharpest result, use a high-resolution PNG or WebP with a transparent background. ## Where your logo appears Once uploaded, your logo replaces the Qwairy logo in: * **The dashboard sidebar**: visible to everyone on your team * **Shared report pages**: visible to anyone you share a link with * **Your branded login**: when a [custom domain](/agencies/custom-domain/overview) is set up, your logo appears on the sign-in page To present a fully branded experience, pair your logo with a [custom domain](/agencies/custom-domain/overview) and [remove the Qwairy branding](/agencies/remove-branding). # Agency notifications Source: https://docs.qwairy.co/agencies/notifications Control which email notifications Qwairy sends, including the per-workspace weekly report. Notifications keep your team informed about changes in AI visibility without checking the dashboard manually. You can control which emails Qwairy sends on a per-workspace basis. ## Weekly report Each workspace can send a **weekly report** email summarizing its AI visibility. The report is controlled per workspace, so you can enable it where it's useful and turn it off elsewhere. Go to **Workspace > Notifications** in your Qwairy dashboard. Turn the **weekly report** on or off for this workspace. Changes take effect immediately. Notifications are sent to the email address associated with each recipient's Qwairy account. ## Outbound webhooks Instead of receiving Qwairy emails, a white-label team can have notifications POSTed to its own endpoint as signed JSON events: so you deliver them to your users under your own brand, from your own systems. Outbound webhooks are an **Enterprise** capability, documented in full under [Developers > Webhooks](/developers/webhooks) alongside the payload format, signature verification, and delivery behavior. The setup steps start in the same place as the weekly report: **Team Management > Notifications**. # Pitch Audits Source: https://docs.qwairy.co/agencies/pitch-audits Create pre-sales AI visibility audits for prospects before onboarding them into a full workspace. Pitch Audits are a **pre-sales** workflow for agencies. They help you qualify prospects, prepare sales conversations, and show AI visibility opportunities before creating a full client workspace. Enter a prospect domain, choose the market and AI engines, review the generated buyer prompts, then launch the audit and share the report. Use Pitch Audits when you need to: * show a prospect how AI assistants answer buyer questions in their category * compare the prospect against competitors detected in those answers * surface cited sources, content gaps, and available-to-buy opportunities * share a sales-ready report before a full client onboarding Pitch Audits are available to agency teams. ## Create a pitch audit In Qwairy, go to **Pitch Audits**. Add the prospect website and choose the country for the audit. Country selection controls which AI engines are available. Choose from 1 to 100 prompts and select which AI engines to run. The default remains **20 prompts** with **ChatGPT**, **Gemini**, and **Copilot**. Depending on country availability, you can also select **Perplexity**, **AI Mode**, and **Grok**. Qwairy generates buyer prompts for the selected market. Review, edit, add, or remove prompts before launching answer generation. Confirm the prompts to spend credits and collect AI answers. You can also enable automatic launch after prompt review when creating the audit. ## Credits Credits are charged only when AI answers are generated. Prompt preparation and prompt review do not spend response credits. Pitch Audit and admin audit creation support up to **100 prompts**. The default remains **20**. For Pitch Audits, each selected AI engine costs **1 credit per prompt**. ``` prompt count x selected engines = required credits ``` For example, a 20-prompt audit across the three default engines costs: ``` 20 x 3 = 60 credits ``` ## What the report includes Pitch Audit reports are built for pre-sales conversations. A completed report includes: * an AI visibility score for the audited brand * the underlying prompts and answer evidence * cited source domains and source URLs * available-to-buy opportunities when sources can be targeted * Social, Shopping, Local, and Ads/Sponsored insights when detected * multi-competitor reports when competitors are found * public sharing options for client-facing delivery Pitch Audit reports are snapshots. AI assistants can answer differently over time, so create a new audit when you want a fresh market read. ## Share the report Open the completed report and copy its public link to share it with a prospect or client. Public reports include the same report improvements as the dashboard report, including the detected insight sections when available. If your team uses white-label features, shared reports follow your agency branding settings. ## Read pitch audits through REST API and MCP Pitch Audit integrations are read-only. They can list audits and read complete reports, but cannot create, confirm, retry, or modify them. * REST API: `GET /api/v1/pitch-audits` and `GET /api/v1/pitch-audits/{auditId}` * MCP: `list_pitch_audits` and `get_pitch_audit` Each detailed report returns `availableSubjects` and `coCompetitors`. To open a co-competitor's derived report, reuse its `subjectId` with the same audit: ```text theme={null} get_pitch_audit({ auditId: "...", subjectId: "rival.example" }) ``` With REST API, use the nested report resource: `/api/v1/pitch-audits/{auditId}/reports/{subjectId}`. Add `includeCoCompetitorScores=true` to retrieve every co-competitor and its score summary in one request. Report summaries omit full answer text by default; pass `includeAnswers=true` only when evidence-level access is required. See [Pitch Audit API](/developers/endpoints/pitch-audits/list) and [Pitch Audit MCP tools](/mcp/tools/pitch-audits). ## Related * [Agency Guide](/agencies/agency-guide): set up clients, access, and reporting * [Shared Links](/documentation/workspace/shared-links): share dashboard views without requiring a login * [Providers & Credits](/documentation/get-started/credits): credit costs for monitoring and model usage # Remove Branding Source: https://docs.qwairy.co/agencies/remove-branding Hide the "Powered by Qwairy" branding across the dashboard, shared report links, and exports with a single toggle. Removing the Qwairy branding hides the "Powered by Qwairy" mark so the experience your team and clients see is fully yours. ## Hide the branding Go to **Team Management > White Label** in your Qwairy dashboard. Turn on the **Remove Qwairy branding** toggle. The change takes effect immediately. ## Where branding is removed With the toggle enabled, the "Powered by Qwairy" branding is hidden across: * **The dashboard**: no Qwairy mark in the interface * **Shared report links**: clean, unbranded pages for the people you share with * **Exports**: generated files come without Qwairy branding Combine this with a [custom logo](/agencies/logo) and a [custom domain](/agencies/custom-domain/overview) for an end-to-end branded experience. # API tokens Source: https://docs.qwairy.co/developers/api-keys Create and manage API keys for programmatic access to your Qwairy data. API tokens authenticate requests to the Qwairy REST API. Use a separate token for each integration so you can revoke access independently. Open [Team Management > API Access](https://www.qwairy.co/dashboard/team/api) to manage tokens. REST API access is available on active **Growth** plans and above. ## Create an API token Click **New API Token**, enter a descriptive name, and confirm. The full token is displayed once. Copy it immediately and store it securely. Full API tokens are shown only at creation. If you lose one, revoke it and create a replacement. ## API token table | Column | Description | | ----------- | ------------------------------------------------------ | | **Name** | Token label | | **Token** | Stored prefix; the full token is never displayed again | | **Created** | Creation date | | **Actions** | Revoke button | ## Revoke an API token Revoke the token and update any integration that uses it. ## Use API tokens Include the token in the `Authorization` header: ```bash theme={null} curl -H "Authorization: Bearer YOUR_API_KEY" \ https://www.qwairy.co/api/v1/brands ``` See the [API introduction](/developers/introduction) for endpoint documentation. # Authentication Source: https://docs.qwairy.co/developers/authentication Authenticate Qwairy API requests with a team-scoped Bearer token. All API requests require a Bearer token in the `Authorization` header. Tokens are scoped to your team and provide access to all brands within your account. ## Get an API token Go to [Team Management > API Access](https://www.qwairy.co/dashboard/team/api). Click **New API Token** and give it a descriptive name, such as `analytics-export`. Your token will be shown **only once**. Copy it immediately and store it securely. Never commit tokens to version control or expose them in client-side code. Revoke a compromised token from **Team Management > API Access**. ## Use your token Include the token in the `Authorization` header with the `Bearer` prefix: ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands" \ -H "Authorization: Bearer qw-api-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ``` ### Token format All Qwairy API tokens follow this format: ``` qw-api-[32 hexadecimal characters] ``` Example: `qw-api-a1b2c3d4e5f6789012345678abcdef01` ## Authentication errors Authentication failures return HTTP `401` with a flat body. `error` is a short status label and `message` explains the failure: | Condition | `message` | | ------------------------------------------------------------------ | -------------------------------------------------------------------------------- | | Missing or malformed `Authorization` header (not `Bearer `) | `Missing or invalid Authorization header. Use: Authorization: Bearer qw-api-xxx` | | Invalid or revoked token, or plan below Growth | `Invalid API token or insufficient subscription plan (Growth+ required)` | ### Synthetic error response ```json theme={null} { "error": "Unauthorized", "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qw-api-xxx" } ``` See [Error codes](/developers/errors) for both gateway and resource error shapes. ## Security practices Store tokens in environment variables, not in code. ```bash theme={null} export QWAIRY_API_TOKEN="qw-api-xxx" ``` Name tokens by their purpose for easy management. Remove tokens that are no longer attached to an active integration. Create different tokens for different integrations. ## Manage tokens Manage API tokens from [Team Management > API Access](https://www.qwairy.co/dashboard/team/api): * **View tokens**: See all active tokens with their last usage date * **Delete tokens**: Revoke access immediately by deleting a token * **Create new tokens**: Generate new tokens as needed # List actions Source: https://docs.qwairy.co/developers/endpoints/actions/list GET /api/v1/brands/{brandId}/actions List the Action Center's prioritized recommendation queue. Returns the current Action Center queue for a brand. Scores, tiers, and quick-win flags are deterministic outputs of the Action Center's current scoring rules; treat them as prioritization inputs, not predicted outcomes. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Filter by status: `SUGGESTED` (awaiting triage), `PENDING`, `IN_PROGRESS`, `COMPLETED`, `DISMISSED`, `RESOLVED` (signal disappeared, auto-closed). Defaults to the live queue: `SUGGESTED` + `PENDING` + `IN_PROGRESS`. Filter by action type: `CONTENT_GAP`, `CONTENT_OPTIMIZE`, `TECHNICAL_FIX`, `SCHEMA_ADD`, `AUTHORITY_BUILD`, `SOURCE_OUTREACH`, `COMPETITOR_ANALYSIS`, `PERCEPTION_FIX`, `SEARCH_TARGET`, `SOCIAL_ENGAGE`, `PRODUCT_OPTIMIZE`, `LOCAL_OPTIMIZE` Filter by category: `TECHNICAL`, `CONTENT`, `AEO`, `AUTHORITY`, `VISIBILITY` Filter by observed provider evidence. Accepts repeated or comma-separated provider IDs. Filter by topic IDs. Accepts repeated or comma-separated UUIDs. Filter by tag IDs. Accepts repeated or comma-separated UUIDs. Combine selected tags with `or` or `and`. Filter by `TOFU`, `MOFU`, or `BOFU`. Accepts repeated or comma-separated values. Evidence period: `1`, `7`, `14`, `30`, `90`, `all`, or `custom`. Start date in `YYYY-MM-DD` format. Required with `periodEnd` when `period=custom`. End date in `YYYY-MM-DD` format. Required with `periodStart` when `period=custom`. Maximum number of actions to return (max: 100) Pagination offset ## Response Actions ordered by priority (highest first), then creation date. Unique identifier of the action Action type (see the `type` filter for values) Action category Detector family that produced the action (e.g. `PAGE_ISSUE`, `QUESTION_GAP`, `SOURCE_OPPORTUNITY`, `SEARCH_QUERY`) Nature of the work: `ON_SITE`, `OFF_SITE` or `SETUP` Action title What to do and why External URL backing the action, when applicable AI provider this action is scoped to (null = brand-level) Provider IDs explicitly present in the underlying evidence `OBSERVED`, `CURRENT`, or `UNATTRIBUTED` Priority tier: `CRITICAL`, `HIGH` or `STANDARD` Tier-anchored score from 0 to 100: Critical 70-100, High 40-69, and Standard 10-39. It is derived from tier, demand badge, effort, and provider scope. Demand signal from 1 to 5 Whether the Action Center heuristic classifies the item as a quick win Estimated workload: `LOW`, `MEDIUM` or `HIGH` Lifecycle status Raw quantified signals behind the priority (citations, fan-out occurrences, impressions, competitive pressure, affected pages...) Stored explanation of the signals used for prioritization Explanation of the estimated effort Metric value stored when the action was created Target metric value, when applicable Linked monitored prompt, when applicable Generated checklist associated with the action ISO 8601 creation date ISO 8601 last update date `total`, `limit`, `offset`, `hasMore` ## Synthetic request ```bash cURL theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/actions?status=SUGGESTED&limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json Response theme={null} { "data": [ { "id": "action_01", "type": "SOURCE_OUTREACH", "category": "AUTHORITY", "sourceType": "SOURCE_OPPORTUNITY", "scope": "OFF_SITE", "title": "Review citation gap for Publisher Example", "description": "Stored answers cite this source 16 times without a SELF mention.", "sourceUrl": "https://publisher.example", "targetProvider": null, "evidenceProviders": ["example-provider"], "evidenceScope": "OBSERVED", "forceTier": "HIGH", "priorityScore": 53, "demandBadge": 3, "isQuickWin": false, "effort": "MEDIUM", "status": "SUGGESTED", "signals": { "citations": 16, "sourceCategory": "MEDIA", "competitivePressure": 2 }, "impactJustification": "Source cited 16x by AI answers without mentioning the brand.", "effortJustification": "Outreach: research the contact, pitch, and follow up.", "baselineMetric": null, "targetMetric": null, "questionId": null, "checklist": [ "Review what content the source already cites", "Compare the cited pages with your existing evidence", "Decide whether an outreach hypothesis is worth testing", "Record the test and monitor later observations" ], "createdAt": "2026-06-12T05:00:00.000Z", "updatedAt": "2026-06-12T05:00:00.000Z" } ], "pagination": { "total": 42, "limit": 10, "offset": 0, "hasMore": true } } ``` ## Errors | Status | Meaning | | ------ | -------------------------------------------------- | | `400` | Invalid enum, taxonomy, provider, or period filter | | `401` | Missing or invalid API token | | `404` | Brand not found or inaccessible | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get answer Source: https://docs.qwairy.co/developers/endpoints/answers/get GET /api/v1/brands/{brandId}/answers/{answerId} Retrieve a single AI response with full text and detailed analysis. Retrieve one stored answer with its prompt, provider and model labels, detected mentions, citations, query fan-outs, and SELF sentiment. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the answer ## Response Indicates if the request was successful Answer ID Associated prompt info (id, text, topic, type) AI provider name AI model name Full response text List of mentioned competitors with position, relationship, and sentiment List of cited sources with URL, domain, position, and isSelf flag Recorded search queries with `id`, `query`, raw `model` ID, and `createdAt` Sentiment of the SELF mention on a 0 to 100 scale, or `null` Generation timestamp ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/answers/ANSWER_ID" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "answer": { "id": "answer_01", "prompt": { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?", "topic": "Data exports", "type": "TOFU" }, "provider": "ChatGPT", "model": "GPT-5.4", "text": "Acme Analytics and Rival Labs both document warehouse export integrations.", "competitors": [ { "name": "Acme Analytics", "position": 3, "relationship": "SELF", "sentiment": 85 }, { "name": "Rival Labs", "position": 1, "relationship": "DIRECT", "sentiment": 80 } ], "sources": [ { "url": "https://publisher.example/warehouse-export-comparison", "domain": "publisher.example", "position": 1, "isSelf": false }, { "url": "https://acme.example/integrations", "domain": "acme.example", "position": 2, "isSelf": true } ], "queryFanOuts": [ { "id": "search_01", "query": "analytics warehouse export integrations", "model": "chatgpt-gpt-5-4", "createdAt": "2026-07-19T09:59:58.000Z" } ], "sentiment": 82, "createdAt": "2026-07-19T10:00:00.000Z" } } ``` ## Errors | Status | Code | Description | | ------ | ------------------ | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 404 | `ANSWER_NOT_FOUND` | Answer doesn't exist | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List answers Source: https://docs.qwairy.co/developers/endpoints/answers/list GET /api/v1/brands/{brandId}/answers Retrieve AI-generated responses with filters for provider, brand mentions, and sources. Returns stored answers for monitored prompts. The list contains a text preview and response-level SELF flags; use [Get Answer](/developers/endpoints/answers/get) for full evidence. See [Entities](/developers/entities#answer) for the complete Answer object structure, including [CompetitorMention](/developers/entities#competitormention) and [SourceCitation](/developers/entities#sourcecitation) details. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by prompt type: `TOFU`, `MOFU`, `BOFU` Filter by presence of brand mention: `true` or `false` Use `true` to require a SELF citation. The current route accepts `false` but does not apply an inverse filter. Maximum number of answers to return (max: 100) Pagination offset Field to sort by: `createdAt` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of answers matching filters Number of answers in this response Maximum items per page Number of items skipped Answer ID Associated prompt ID Associated prompt text AI provider name AI model name First 200 characters of the response Whether brand is mentioned Position in mention list (if mentioned) Whether brand domain is cited Number of competitors mentioned Number of sources cited SELF mention sentiment on a 0 to 100 scale, or `null` Generation timestamp (ISO 8601) ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/answers?hasSelfMention=true&limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 312, "count": 1, "limit": 10, "offset": 0 }, "answers": [ { "id": "answer_01", "promptId": "prompt_01", "promptText": "Which analytics platforms support warehouse exports?", "provider": "ChatGPT", "model": "GPT-5.4", "textPreview": "Acme Analytics and Rival Labs both document warehouse export integrations...", "hasSelfMention": true, "selfMentionPosition": 3, "hasSelfSource": false, "competitorsCount": 5, "sourcesCount": 3, "sentiment": 82, "createdAt": "2026-07-19T10:00:00.000Z" } ] } ``` ## Filtering examples ### Find answers with a SELF mention ```bash theme={null} GET /api/v1/brands/{brandId}/answers?hasSelfMention=true ``` ### Find answers with a SELF citation ```bash theme={null} GET /api/v1/brands/{brandId}/answers?hasSelfSource=true ``` ### Combine filters ```bash theme={null} # Answers with a SELF mention from the ChatGPT provider GET /api/v1/brands/{brandId}/answers?provider=openai&hasSelfMention=true ``` The endpoint does not currently support an inverse SELF-citation filter. To find answers that mention the brand without a SELF citation, request `hasSelfMention=true` and filter the returned `hasSelfSource` field in your client. ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `400` | Invalid query parameter | | `401` | Missing or invalid API token | | `404` | Brand not found or inaccessible | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get brand Source: https://docs.qwairy.co/developers/endpoints/brands/get GET /api/v1/brands/{brandId} Retrieve detailed information about a specific brand. Retrieve one accessible brand and current counts for non-deleted prompts, non-deleted answers, and SELF or DIRECT competitor domains. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique brand identifier ## Response Indicates if the request was successful Opaque brand identifier Brand display name Primary domain for the brand Brand description (can be null) ISO 8601 creation timestamp Number of prompts tracked for this brand Number of non-deleted stored answers Number of competitors (SELF + DIRECT) ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "brand_01", "name": "Acme Analytics", "domain": "acme.example", "description": "Analytics software for operations teams.", "createdAt": "2026-07-15T10:30:00.000Z", "stats": { "promptsCount": 156, "answersCount": 312, "competitorsCount": 8 } } } ``` ## Errors | Status | Code | Description | | ------ | ----------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List brands Source: https://docs.qwairy.co/developers/endpoints/brands/list GET /api/v1/brands Retrieve all brands accessible to your team. Returns the authenticated team's `LIVE` brands whose transfer status is `NONE`, ordered by name. See [Entities](/developers/entities#brand) for the complete Brand object structure. Bearer token. Example: `Bearer qw-api-xxx` ## Response Indicates if the request was successful Total number of brands Opaque brand identifier Brand display name Primary domain for the brand ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "total": 2, "brands": [ { "id": "brand_01", "name": "Acme Analytics", "domain": "acme.example" }, { "id": "brand_02", "name": "Northstar Data", "domain": "northstar.example" } ] } ``` ## Next steps Once you have your brand IDs, you can: * [Get performance metrics](/developers/endpoints/performance) * [List competitors](/developers/endpoints/competitors/list) * [View source domains](/developers/endpoints/source-domains/list) * [View source URLs](/developers/endpoints/source-urls) * [Browse prompts](/developers/endpoints/prompts/list) ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `401` | Missing or invalid API token | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Competitor evolution Source: https://docs.qwairy.co/developers/endpoints/competitors/evolution GET /api/v1/brands/{brandId}/competitors/{competitorId}/evolution Track how a competitor's metrics change over time. Returns daily mention occurrences, SELF-and-DIRECT Share of Voice, average position, and average mention sentiment for one competitor. ## Path parameters The unique identifier of the brand The unique identifier of the competitor ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/competitors/COMPETITOR_ID/evolution?period=7" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "competitor": { "id": "competitor_01", "name": "Rival Labs", "relationship": "DIRECT" }, "evolution": [ { "date": "2026-07-01", "mentions": 5, "shareOfVoice": 7.2, "avgPosition": 2.3, "avgSentiment": 76.5 }, { "date": "2026-07-02", "mentions": 8, "shareOfVoice": 8.5, "avgPosition": 2.1, "avgSentiment": 78.2 } ] } ``` ## Errors | Status | Code | Description | | ------ | ---------------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 404 | `COMPETITOR_NOT_FOUND` | Competitor doesn't exist | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | `avgSentiment` uses the 0 to 100 mention-sentiment scale and can be `null`. See [Rate limits](/developers/rate-limits) for retry behavior. # Get competitor Source: https://docs.qwairy.co/developers/endpoints/competitors/get GET /api/v1/brands/{brandId}/competitors/{competitorId} Retrieve detailed competitor info with breakdowns by provider and topic. Returns one competitor's metrics for the selected answer scope. Share of Voice divides this competitor's mention occurrences by SELF and DIRECT mention occurrences in that scope. ## Path parameters The unique identifier of the brand The unique identifier of the competitor ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/competitors/COMPETITOR_ID?period=30" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "competitor": { "id": "competitor_01", "name": "Rival Labs", "domain": "rival.example", "relationship": "DIRECT", "totalMentions": 104, "shareOfVoice": 8.13, "avgPosition": 2.1, "avgSentiment": 78.1, "byProvider": [ { "provider": "ChatGPT", "mentions": 62, "avgPosition": 2.0 }, { "provider": "Perplexity", "mentions": 42, "avgPosition": 2.3 } ], "byTopic": [ { "topic": "Product Reviews", "mentions": 45 }, { "topic": "Comparisons", "mentions": 38 } ] } } ``` `avgSentiment` is the average sentiment for this competitor's mentions on a 0 to 100 scale, or `null`. `byProvider` groups stored model IDs under their provider display name. ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `401` | Missing or invalid API token | | `404` | Brand or competitor not found | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List competitors Source: https://docs.qwairy.co/developers/endpoints/competitors/list GET /api/v1/brands/{brandId}/competitors Track competitor mentions, share of voice, and positioning. Lists competitor domains and mention metrics for the selected scope. The default relationship set is `SELF,DIRECT`; include `INDIRECT` explicitly when needed. On this endpoint, Share of Voice uses mention occurrences from the selected relationship set. See [Entities](/developers/entities#competitor) for the complete Competitor object structure. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by prompt type: `TOFU`, `MOFU`, `BOFU` Filter by relationship type. Supports comma-separated multi-select: `SELF`, `DIRECT`, `INDIRECT`. Default: `SELF,DIRECT`. Maximum number of results to return (max: 100) Number of results to skip for pagination Field to sort by: `mentions`, `position`, `sentiment`, `shareOfVoice`, `name` Sort order: `asc` or `desc` ## Response Returns competitors from the requested relationship filter. `INDIRECT` is excluded only by the default filter. Indicates if the request was successful Total number of competitors matching filters Number of competitors in this response Maximum items per page Number of items skipped Competitor ID Competitor name Competitor domain `SELF`, `DIRECT`, or `INDIRECT`, subject to the `relationship` filter Total mentions count This row's mention occurrences divided by all mention occurrences in the selected relationship set Average position in responses Average mention sentiment on a 0 to 100 scale, or `null` ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/competitors?limit=10&offset=0" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 10, "count": 2, "limit": 10, "offset": 0 }, "competitors": [ { "id": "competitor_self", "name": "Acme Analytics", "domain": "acme.example", "relationship": "SELF", "totalMentions": 104, "shareOfVoice": 8.13, "avgPosition": 2.1, "avgSentiment": 78.1 }, { "id": "competitor_01", "name": "Rival Labs", "domain": "rival.example", "relationship": "DIRECT", "totalMentions": 111, "shareOfVoice": 8.73, "avgPosition": 1.8, "avgSentiment": 75.2 } ] } ``` ## Errors | Status | Meaning | | ------ | ---------------------------------------------- | | `400` | Invalid query parameter or relationship filter | | `401` | Missing or invalid API token | | `404` | Brand not found or inaccessible | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get content Source: https://docs.qwairy.co/developers/endpoints/content/get GET /api/v1/brands/{brandId}/content/{articleId} Retrieve a full Content Studio article including the markdown body. Returns one Content Studio article with its Markdown `content`, outline, metadata, and sources. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the article ## Response Indicates if the request was successful `id`, `name`, `domain` Article ID Article title URL slug or `null` Short description or `null` Full article body in **Markdown** Article type `DRAFT`, `GENERATING`, `LIVE` or `ARCHIVED` Target word count used for generation Actual word count Generation guidelines or `null` Whether a TL;DR was requested Whether an FAQ was requested SEO/initialization metadata (JSON) or `null` Structured outline with `sections` or `null` Published URL (if set by you) or `null` Publish timestamp (ISO 8601) or `null` Content generation timestamp (ISO 8601) or `null` Creation timestamp (ISO 8601) Last update timestamp (ISO 8601) Sources used for the article. Each object has `id`, `title`, `url`, `usageType`, `order`. A `404 RESOURCE_NOT_FOUND` is returned if the article does not exist or does not belong to the brand. ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/content/ARTICLE_ID" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "brand_01", "name": "Acme Analytics", "domain": "acme.example" }, "article": { "id": "art_abc123", "title": "How to choose the right solution in 2026", "slug": "how-to-choose-the-right-solution-2026", "description": "A practical buyer's guide.", "content": "# How to choose the right solution in 2026\n\n## Introduction\n\n...", "articleType": "HOW_TO", "status": "LIVE", "targetLength": 1500, "wordCount": 1480, "guidelines": null, "includeTldr": true, "includeFaq": false, "metaData": { "seo": { "metaTitle": "How to choose the right solution (2026 guide)", "metaDescription": "A practical buyer's guide for 2026." } }, "outline": { "sections": [ { "id": "s1", "level": 2, "title": "Introduction", "order": 0 } ], "generatedAt": "2026-05-08T14:20:00.000Z" }, "finalUrl": "https://acme.example/blog/how-to-choose", "publishedAt": "2026-05-10T09:00:00.000Z", "generatedAt": "2026-05-08T14:22:00.000Z", "createdAt": "2026-05-08T13:50:00.000Z", "updatedAt": "2026-05-10T09:00:00.000Z", "sources": [ { "id": "src_1", "title": "Industry benchmark report", "url": "https://research.example/benchmark", "usageType": "AUTO", "order": 0 } ] } } ``` ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `401` | Missing or invalid API token | | `404` | Brand or article not found | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List content Source: https://docs.qwairy.co/developers/endpoints/content/list GET /api/v1/brands/{brandId}/content List Content Studio articles for syndication to an external CMS. Lists Content Studio article metadata for read-only integrations. Fetch [article detail](/developers/endpoints/content/get) to retrieve the Markdown body. The route defaults to `status=LIVE`. Pass `status` explicitly to list another lifecycle state. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Filter by status: `DRAFT`, `GENERATING`, `LIVE`, `ARCHIVED` Filter by type: `INFORMATIONAL`, `COMPARISON`, `HOW_TO`, `LISTICLE`, `REVIEW`, `CASE_STUDY`, `NEWS`, `OPINION`, `OTHER` Maximum number of articles to return (max: 100) Pagination offset Field to sort by: `createdAt`, `updatedAt`, `publishedAt`, `title`, `wordCount` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of articles matching filters Number of articles in this response Maximum items per page Number of items skipped Article ID (use with the detail endpoint) Article title URL slug or `null` Short description or `null` Article type `DRAFT`, `GENERATING`, `LIVE` or `ARCHIVED` Word count of the generated content Published URL (if set by you) or `null` Publish timestamp (ISO 8601) or `null` Content generation timestamp (ISO 8601) or `null` Creation timestamp (ISO 8601) Last update timestamp (ISO 8601) ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/content?status=LIVE&limit=20" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 12, "count": 1, "limit": 20, "offset": 0 }, "articles": [ { "id": "art_abc123", "title": "How to choose the right solution in 2026", "slug": "how-to-choose-the-right-solution-2026", "description": "A practical buyer's guide.", "articleType": "HOW_TO", "status": "LIVE", "wordCount": 1480, "finalUrl": "https://acme.example/blog/how-to-choose", "publishedAt": "2026-05-10T09:00:00.000Z", "generatedAt": "2026-05-08T14:22:00.000Z", "createdAt": "2026-05-08T13:50:00.000Z", "updatedAt": "2026-05-10T09:00:00.000Z" } ] } ``` ## Errors | Status | Meaning | | ------ | ----------------------------------------------------------- | | `400` | Invalid status, article type, pagination, or sort parameter | | `401` | Missing or invalid API token | | `404` | Brand not found or inaccessible | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get crawler analytics Source: https://docs.qwairy.co/developers/endpoints/crawler-analytics GET /api/v1/brands/{brandId}/crawler-analytics Retrieve retained AI crawler activity, delivery coverage, and page-level observations for a brand. Retrieve AI crawler activity observed through the brand's configured log integration. The endpoint returns daily aggregates and page-level observations. It never returns raw log events or human traffic. All occurrence totals, crawler breakdowns, and page rankings use the public set of supported observable AI-specific User-Agent identities. The ingestion registry can recognize additional identities for classification and compatibility, but the endpoint excludes them from its public analytics output. Classification matches the received User-Agent token; it does not verify source IP or reverse DNS. This endpoint requires an active Business, Enterprise, or Agency Business plan, or an explicit Crawler Analytics feature override for the team. Results are observational: check `coverage` before interpreting counts or comparing periods. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters Unique identifier of the brand. ## Query parameters Inclusive analytic-day preset in the integration's IANA time zone: `24h` is the current analytic day, `7d` is that day plus the preceding six, and `30d` is that day plus the preceding 29. `24h` is not a rolling 24-hour window. Page-state filter: `all`, `new`, `hot`, or `neglected`. One-based page number. Maximum: `10000`. Page size. Maximum: `100`. ## Page-state semantics * `new`: the first retained page observation falls within the last 7 days, including the boundary (`firstSeenAt >= now - 7 days`). * `hot`: the page is in the top decile by supported-crawler occurrences over the current 7 analytic days, has activity on at least 2 of those days, and is not neglected. * `neglected`: the most recent retained page observation is strictly older than the 30-day boundary. `new` and `hot` can coexist. `neglected` is exclusive. The hot window is independent of the requested `range`. ## Response Indicates whether the request succeeded. Brand `id`, `name`, and `domain`. Whether the integration is active, not disabled, and has at least one active non-revoked key. Current integration lifecycle, or `null` when no integration exists. Current integration health, or `null` when no integration exists. Requested and effective dates, observed and verified bucket counts, truncation state, and the `asOf` timestamp. Whether the selected range contains at least one supported public occurrence. `observed` when supported observations exist without a known gap; `not_measured` when none exist and no gap is known; `incomplete_ingestion` for a quota limit or provider gap, with or without observations. Bit flags: `1` delivery unverified, `2` quota limited, and `4` provider gap. `exact`; `at_least_once` when repackaged retries can duplicate observations; or `capped` when a count exceeds safe numeric precision. `verified` when every elapsed bucket is verified; `incomplete` for a known gap or profile change; otherwise `unverified`. Observed occurrences from the supported public crawler set in the effective range. A number is returned only when at least one supported public rollup has a positive request count; otherwise the value is `null`, not a measured zero. With incomplete ingestion, a numeric value can still be partial. Occurrences associated with a retained page identity. Requests that returned a redirect. Requests that returned `404`. Other observed error responses. Daily totals, crawler breakdowns, error totals, and coverage metadata. Totals by crawler in the supported public set, including its display name, bot type, and error breakdown. Stable page identity. Observed path or an internal pseudonym. When `true`, do not reconstruct or link to a URL from `path`. First observed timestamp. Most recent observed timestamp. Retained lifetime occurrence count. Computed states. `new` and `hot` can coexist; `neglected` is exclusive. Occurrences in the selected range. Occurrence count keyed by crawler name. Distinct observed crawlers. HTTP status-group totals. Live-search, training, and other crawler totals. Page-by-crawler daily rollups retain 30 analytic days. Durable page identities preserve first seen, last seen, and lifetime count beyond that window. Query strings and fragments are removed; sensitive-looking or overlong paths can be replaced with an integration-scoped one-way pseudonym. One-based `page`, `limit`, `totalItems`, `totalPages`, and `hasMore`. Always `false`. Raw log events are not exposed by this endpoint. Always `false`. ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/crawler-analytics?range=7d&state=hot&limit=25" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Interpret missing or incomplete data * Treat `totalOccurrences: null` as not measured. Do not convert it to zero. * Check `coverage.dataStatus`, `countPrecision`, and `deliveryContinuity` before comparing periods. * Use `range.startDate` and `range.endDate` for the effective comparison window. * Do not build a public URL from a page where `isPseudonymized` is `true`. ## Errors | Status | Meaning | | ------ | --------------------------------------------------------------------------------------- | | `400` | Invalid range, state, or pagination value | | `401` | Missing or invalid API token | | `403` | The team has neither an eligible active plan nor an explicit Crawler Analytics override | | `404` | Brand not found or not accessible to the token's team | | `429` | Rate limit exceeded; honor `Retry-After` | See [Crawler Analytics](/documentation/measure/crawler-analytics) for setup and the product interface, and [Rate limits](/developers/rate-limits) for retry behavior. # Topic evolution Source: https://docs.qwairy.co/developers/endpoints/keywords/evolution GET /api/v1/brands/{brandId}/keywords/{keywordId}/evolution Daily time-series metrics for a specific topic. Returns daily response-level metrics for one topic. Mention Rate and Source Rate use eligible SELF-or-DIRECT answer denominators; Share of Voice uses mention occurrences. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the topic ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by prompt type: `TOFU`, `MOFU`, `BOFU` ## Response Indicates if the request was successful Topic ID Topic text Date (YYYY-MM-DD format) Stored answers on this date Answers with a SELF mention divided by answers with a SELF or DIRECT mention SELF mention occurrences divided by SELF and DIRECT mention occurrences Average SELF mention sentiment on a 0 to 100 scale, or `null` Answers with a SELF citation divided by answers with a SELF or DIRECT citation ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/keywords/TOPIC_ID/evolution?period=30" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "keyword": { "id": "topic_01", "text": "data exports" }, "evolution": [ { "date": "2026-02-15", "answers": 42, "mentionRate": 65.2, "shareOfVoice": 48.3, "avgSentiment": 72.1, "sourceRate": 55.0 }, { "date": "2026-02-16", "answers": 38, "mentionRate": 60.5, "shareOfVoice": 45.1, "avgSentiment": 74.2, "sourceRate": 52.6 } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 404 | `KEYWORD_NOT_FOUND` | Topic doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List topics Source: https://docs.qwairy.co/developers/endpoints/keywords/list GET /api/v1/brands/{brandId}/keywords Analyze performance by topic (keyword). Lists topics with answer counts and response-level SELF metrics for the selected scope. The API route retains `keywords` and `keyword` field names for compatibility. See [Entities](/developers/entities#keyword) for the complete Topic object structure. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Maximum number of topics to return (max: 100) Number of results to skip for pagination Field to sort by: `prompts`, `answers`, `mentionRate`, `shareOfVoice`, `sentiment`, `keyword` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of topics matching filters Number of topics in this response Maximum items per page Number of items skipped Topic ID Topic text Number of prompts with this topic Stored answers in the selected scope Answers with a SELF mention divided by answers with a SELF or DIRECT mention SELF mention occurrences divided by SELF and DIRECT mention occurrences Average SELF mention sentiment on a 0 to 100 scale; `0` is returned when no scored SELF mention exists ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/keywords?limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 25, "count": 2, "limit": 10, "offset": 0 }, "keywords": [ { "id": "topic_01", "keyword": "data exports", "totalPrompts": 15, "totalAnswers": 120, "brandMentionRate": 45.5, "shareOfVoice": 32.1, "avgSentiment": 78.3 }, { "id": "topic_02", "keyword": "analytics integrations", "totalPrompts": 8, "totalAnswers": 64, "brandMentionRate": 28.1, "shareOfVoice": 18.5, "avgSentiment": 72.0 } ] } ``` ## Errors | Status | Code | Description | | ------ | ----------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List local business insights Source: https://docs.qwairy.co/developers/endpoints/local GET /api/v1/brands/{brandId}/local List local business observations extracted from stored answers. Returns local business observations extracted from stored answers. Each record is an observation, not a verified recommendation or current business listing. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/local ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by one or more provider aliases or model IDs, separated by commas. Filter by business category (case-insensitive partial match) Maximum number of businesses to return (max: 100) Number of results to skip for pagination Field to sort by: `createdAt`, `rating`, `reviewCount`, `position`, `businessName` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of businesses matching filters Number of businesses in this response Maximum items per page Number of items skipped Local insight ID Business name Business category Business address Business website URL Position in the list (1 = top result) Business rating (0-5) Number of reviews Prompt associated with the stored observation ISO 8601 timestamp ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/local?category=restaurant&limit=10" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 42, "count": 2, "limit": 10, "offset": 0 }, "local": [ { "id": "loc1", "businessName": "Northstar Café", "category": "Café", "address": "10 Example Street, Paris", "websiteUrl": "https://cafe.northstar.example", "position": 1, "rating": 4.8, "reviewCount": 2500, "prompt": "Which cafés are mentioned near the example district?", "createdAt": "2026-07-15T10:30:00Z" }, { "id": "loc2", "businessName": "Orbit Bistro", "category": "Restaurant", "address": "20 Example Avenue, Paris", "websiteUrl": "https://bistro.orbit.example", "position": 2, "rating": 4.5, "reviewCount": 1800, "prompt": "Which cafés are mentioned near the example district?", "createdAt": "2026-07-15T10:30:00Z" } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ----------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid filter, pagination, or sort value | | 401 | gateway error | Authentication failed | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # Get GEO matrix Source: https://docs.qwairy.co/developers/endpoints/matrix GET /api/v1/brands/{brandId}/matrix Get the GEO visibility matrix for your brand across providers and dimensions. Returns rows grouped by topic, tag, prompt, or funnel stage and columns grouped by provider. Each cell contains the current Matrix scoring output for its answer scope. This endpoint returns a cross-tabulation of your brand's visibility. See [Performance](/developers/endpoints/performance) for aggregated metrics and [Competitor](/developers/entities#competitor) for entity structures used in `topCompetitors`. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/matrix ``` ## Path parameters The unique identifier of the brand ## Query parameters Group rows by: `topics` (default), `tags`, `prompts`, or `funnel` Number of days to include. Default: 30 Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID (keywordId). Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Max rows to return. Default: 50, max: 100 ### Granularity options | Value | Groups rows by | | --------- | ------------------------------ | | `topics` | Keywords/topics (default) | | `tags` | Tags applied to prompts | | `prompts` | Individual monitored questions | | `funnel` | Funnel stages (TOFU/MOFU/BOFU) | ## Response Indicates if the request was successful Brand ID Brand name The granularity used for this matrix (`topics`, `tags`, `prompts`, or `funnel`) List of AI providers used as columns. Each entry is an object with an `id` and a `name`. Provider ID (e.g., `chatgpt`, `perplexity`, `claude`) Display name of the provider Matrix rows, one per topic/tag/prompt/funnel stage Row identifier (topic ID, tag ID, prompt ID, or funnel stage) Display name of the row Number of distinct prompts in this row Pooled row-level Mention Rate: answers with a SELF mention divided by answers with a SELF or DIRECT mention Provider-column coverage for this row. A provider counts when its cell has a non-zero SELF Mention Rate or SELF Citation Rate. This is not response-level or prompt-level Coverage. Provider cells with a non-zero SELF Mention Rate or Citation Rate Provider columns in the selected matrix `count / total × 100`, rounded to an integer Pooled `score`, `subScores`, `answerCount`, and brand `ranking` across every selected provider in the row One cell per provider Provider ID Composite GEO score (0-100) Brand mention rate (%) Share of voice (%) Source citation rate (%) Average SELF mention sentiment on a 0 to 100 scale, or `null` Rank and total (e.g., `{"rank": 2, "total": 8}`) Rank and total Rank and total Rank and total SELF source-mention occurrences in the cell Best (minimum) position your brand reaches across this cell's responses (lower is better; `null` if not mentioned) Name of the leading competitor in this cell (`null` if none) Top competitors in this cell, each with `name`, `mentions`, `avgPosition`, and `relationship` Top cited sources in this cell Per-provider average metrics across all returned rows. Provider ID Average composite GEO score for this provider (0-100) Average mention rate for this provider (%) Average share of voice for this provider (%) Average citation rate for this provider (%) Unweighted average of non-null row sentiment values for this provider, rounded to an integer Total number of rows Average of non-zero row Mention Rates, rounded to an integer Highest mention-rate row, or `null`. Shape: `{ rowId, rowName, mentionRate }`. API summary field containing the lowest non-zero row below the built-in 50% rule, or `null`. This rule is not an account benchmark. ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/matrix?granularity=topics&period=30&limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "brand_01", "name": "Acme Analytics" }, "granularity": "topics", "providers": [ { "id": "openai", "name": "ChatGPT" }, { "id": "perplexity", "name": "Perplexity" }, { "id": "anthropic", "name": "Claude" } ], "rows": [ { "id": "topic_01", "name": "Data exports", "promptCount": 12, "mentionRate": 52.4, "coverage": { "count": 3, "total": 3, "percentage": 100 }, "overall": { "score": 60, "subScores": { "mentionRate": 52.4, "shareOfVoice": 13.5, "citationRate": 29.1, "sentiment": 76.3 }, "answerCount": 60, "ranking": { "mentionRate": { "rank": 2, "total": 8 }, "shareOfVoice": { "rank": 3, "total": 8 }, "citationRate": { "rank": 2, "total": 8 }, "sentiment": { "rank": 1, "total": 8 }, "score": { "rank": 2, "total": 8 } } }, "cells": [ { "provider": "openai", "score": 72, "subScores": { "mentionRate": 65.0, "shareOfVoice": 18.5, "citationRate": 30.2, "sentiment": 81.0 }, "ranking": { "mentionRate": { "rank": 2, "total": 8 }, "shareOfVoice": { "rank": 3, "total": 8 }, "citationRate": { "rank": 1, "total": 8 }, "sentiment": { "rank": 1, "total": 8 } }, "citations": 12, "position": 2, "topCompetitor": "Rival Labs", "topCompetitors": [ { "name": "Rival Labs", "mentions": 9, "avgPosition": 2, "relationship": "DIRECT" }, { "name": "Northstar Data", "mentions": 6, "avgPosition": 3, "relationship": "DIRECT" } ], "topSources": [ { "domain": "publisher.example", "citations": 8 } ] }, { "provider": "perplexity", "score": 58, "subScores": { "mentionRate": 50.0, "shareOfVoice": 12.3, "citationRate": 42.1, "sentiment": 76.0 }, "ranking": { "mentionRate": { "rank": 3, "total": 7 }, "shareOfVoice": { "rank": 4, "total": 7 }, "citationRate": { "rank": 2, "total": 7 }, "sentiment": { "rank": 2, "total": 7 } }, "citations": 18, "position": 3, "topCompetitor": "Northstar Data", "topCompetitors": [ { "name": "Northstar Data", "mentions": 11, "avgPosition": 2, "relationship": "DIRECT" }, { "name": "Rival Labs", "mentions": 7, "avgPosition": 4, "relationship": "DIRECT" } ], "topSources": [ { "domain": "reviews.example", "citations": 14 } ] }, { "provider": "anthropic", "score": 45, "subScores": { "mentionRate": 35.0, "shareOfVoice": 9.8, "citationRate": 15.0, "sentiment": 72.0 }, "ranking": { "mentionRate": { "rank": 5, "total": 6 }, "shareOfVoice": { "rank": 4, "total": 6 }, "citationRate": { "rank": 3, "total": 6 }, "sentiment": { "rank": 3, "total": 6 } }, "citations": 4, "position": 4, "topCompetitor": "Rival Labs", "topCompetitors": [ { "name": "Rival Labs", "mentions": 5, "avgPosition": 3, "relationship": "DIRECT" } ], "topSources": [ { "domain": "acme.example", "citations": 3 } ] } ] } ], "providerAverages": [ { "provider": "openai", "score": 72, "mentionRate": 65.0, "shareOfVoice": 18.5, "citationRate": 30.2, "sentiment": 81.0 }, { "provider": "perplexity", "score": 58, "mentionRate": 50.0, "shareOfVoice": 12.3, "citationRate": 42.1, "sentiment": 76.0 }, { "provider": "anthropic", "score": 45, "mentionRate": 35.0, "shareOfVoice": 9.8, "citationRate": 15.0, "sentiment": 72.0 } ], "summary": { "totalRows": 12, "averageMentionRate": 50, "topPerforming": { "rowId": "topic_01", "rowName": "Data exports", "mentionRate": 52.4 }, "needsAttention": { "rowId": "topic7", "rowName": "Pricing", "mentionRate": 31.0 } } } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | --------------------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid query parameter (e.g., unknown granularity) | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get perception snapshot Source: https://docs.qwairy.co/developers/endpoints/perception/get GET /api/v1/brands/{brandId}/perception/{snapshotId} Retrieve one completed perception snapshot with scores, attribute alignment, and SWOT observations. Returns one completed perception snapshot. Get `snapshotId` from the `history` array returned by [List perception snapshots](/developers/endpoints/perception/list). Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the perception snapshot ## Response Indicates if the request was successful `id`, `name`, `domain` Snapshot ID Month (1-12) Year `sentiment`, `alignment`, `consistency`, `factualAlignment` (0-100 or `null`) Brand attributes evaluated by AI. Each object has `text` (string) and `status` (`aligned`, `partial`, `missing`). Counts: `aligned`, `partial`, `missing`, `total`. SWOT arrays of strings: `strengths`, `weaknesses`, `opportunities`, `threats`. AI providers included in the analysis Creation timestamp (ISO 8601) Completion timestamp (ISO 8601) or `null` A `404 RESOURCE_NOT_FOUND` is returned if the snapshot does not exist, is not `COMPLETED`, or does not belong to the brand. ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/perception/snap_2026_05" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "cm1234567890abcdef", "name": "Northstar Labs", "domain": "northstar.example" }, "snapshot": { "snapshotId": "snap_2026_05", "month": 5, "year": 2026, "scores": { "sentiment": 78, "alignment": 65, "consistency": 82, "factualAlignment": 71 }, "attributes": [ { "text": "Premium positioning", "status": "aligned" }, { "text": "Sustainable materials", "status": "partial" }, { "text": "Made in France", "status": "missing" } ], "attributeMetrics": { "aligned": 1, "partial": 1, "missing": 1, "total": 3 }, "insights": { "strengths": ["Strong brand recognition in core market"], "weaknesses": ["Limited awareness of product range"], "opportunities": ["Growing demand in adjacent category"], "threats": ["Aggressive competitor messaging"] }, "providers": ["chatgpt", "perplexity"], "createdAt": "2026-05-01T03:00:00.000Z", "completedAt": "2026-05-03T04:12:00.000Z" } } ``` The SWOT arrays are stored model-generated observations. Treat them as inputs to review, not verified facts or forecasts. ## Errors | Status | Code | Description | | ------ | -------------------- | ------------------------------------------------------- | | `401` | gateway error | Authentication failed | | `404` | `RESOURCE_NOT_FOUND` | Brand or completed snapshot not found or not accessible | | `429` | gateway error | Rate limit exceeded; honor `Retry-After` | # List perception snapshots Source: https://docs.qwairy.co/developers/endpoints/perception/list GET /api/v1/brands/{brandId}/perception List completed monthly perception snapshots with scores, trends, averages, and history. Returns completed perception snapshots stored by month. Each snapshot can contain four `0-100` scores: sentiment, alignment, consistency, and factual alignment. A score can be `null` when it was not calculated. There is no day-based `period` filter. Use `months` to control the history depth. `nextAnalysisDate` is derived from the latest snapshot month; it is not a delivery guarantee. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of months of history to include (max: 24). Only `COMPLETED` snapshots are returned. ## Response Indicates if the request was successful Brand ID Brand name Brand domain Latest completed snapshot, or `null` if none. Contains `snapshotId`, `month`, `year`, `completedAt`, and a `scores` object with `sentiment`, `alignment`, `consistency`, `factualAlignment` (each `0-100` or `null`). Previous completed snapshot in the same shape as `current`, or `null`. Difference (current − previous) for each score: `sentiment`, `alignment`, `consistency`, `factualAlignment`. A value is `null` when either side is missing. Average of each score across the returned history (`null` when no data points). Snapshot ID (use with the detail endpoint) Month (1-12) Year `MM/YYYY` label `sentiment`, `alignment`, `consistency`, `factualAlignment` Completion timestamp (ISO 8601) or `null` `months` (requested window) and `dataPoints` (number of snapshots returned). First day of the month after the latest snapshot, or the next calendar month when no snapshot exists. This is a computed ISO 8601 date, not a completion promise. ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/perception?months=6" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "cm1234567890abcdef", "name": "Northstar Labs", "domain": "northstar.example" }, "current": { "snapshotId": "snap_2026_05", "month": 5, "year": 2026, "scores": { "sentiment": 78, "alignment": 65, "consistency": 82, "factualAlignment": 71 }, "completedAt": "2026-05-03T04:12:00.000Z" }, "previous": { "snapshotId": "snap_2026_04", "month": 4, "year": 2026, "scores": { "sentiment": 74, "alignment": 61, "consistency": 80, "factualAlignment": 69 }, "completedAt": "2026-04-02T04:09:00.000Z" }, "trends": { "sentiment": 4, "alignment": 4, "consistency": 2, "factualAlignment": 2 }, "averages": { "sentiment": 75.5, "alignment": 62.0, "consistency": 80.3, "factualAlignment": 69.8 }, "history": [ { "snapshotId": "snap_2026_04", "month": 4, "year": 2026, "label": "04/2026", "scores": { "sentiment": 74, "alignment": 61, "consistency": 80, "factualAlignment": 69 }, "completedAt": "2026-04-02T04:09:00.000Z" }, { "snapshotId": "snap_2026_05", "month": 5, "year": 2026, "label": "05/2026", "scores": { "sentiment": 78, "alignment": 65, "consistency": 82, "factualAlignment": 71 }, "completedAt": "2026-05-03T04:12:00.000Z" } ], "meta": { "months": 6, "dataPoints": 2 }, "nextAnalysisDate": "2026-06-01T00:00:00.000Z" } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ---------------------------------------- | | `400` | `INVALID_PARAMETER` | Invalid `months` value | | `401` | gateway error | Authentication failed | | `404` | `BRAND_NOT_FOUND` | Brand not found or not accessible | | `429` | gateway error | Rate limit exceeded; honor `Retry-After` | # Get performance snapshot Source: https://docs.qwairy.co/developers/endpoints/performance GET /api/v1/brands/{brandId}/performance Read response-level mention, citation, sentiment, and Share of Voice metrics for a brand. Returns response-level metrics for the selected answer scope, plus topic, tag, competitor, and source breakdowns. This endpoint returns aggregated metrics. See [Competitor](/developers/entities#competitor) and [Source](/developers/entities#source) for entity structures used in `topCompetitors` and `topSources`. ## Metric formulas Understanding how metrics are calculated is essential for reproducing results: | Metric | Formula | Details | | ------------------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | **Mention Rate** | `mentionCount / mentionTotal × 100` | `mentionCount` is the number of answers with a SELF mention. `mentionTotal` is the number of answers with at least one SELF or DIRECT mention. | | **Coverage** | `mentionCount / responsesTotal × 100` | Answer-level coverage: answers with a SELF mention divided by every answer in the selected scope. This is not prompt-level Coverage. | | **Source Rate** | `sourceCount / sourceTotal × 100` | `sourceCount` is the number of answers with a SELF citation. `sourceTotal` is the number of answers with at least one SELF or DIRECT citation. | | **Share of Voice** | `sovSelf / sovTotal × 100` | SELF mention occurrences divided by SELF and DIRECT mention occurrences. INDIRECT mentions are excluded. | Mention Rate and Coverage have different denominators. The response exposes `mentionCount`, `mentionTotal`, and `responsesTotal`; it does not expose `sovSelf` or `sovTotal`. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/performance ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include in the snapshot. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Use one date mode per request. Combining `period` with a complete custom date pair can make the applied filter diverge from the returned `period` metadata. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID (keywordId). Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by prompt type: `TOFU`, `MOFU`, `BOFU` ## Response Indicates if the request was successful Brand ID Brand name Primary domain Start date (ISO 8601) End date (ISO 8601) Total prompts analyzed Number of AI providers List of provider IDs (e.g., `openai`, `perplexity`) Stored answers in the selected scope Answers containing at least one SELF or DIRECT mention Answers containing at least one SELF or DIRECT citation Brand mention rate (%) Number of responses where your brand (SELF) is mentioned Number of responses containing **at least one SELF or DIRECT competitor mention**. This is the denominator for `mentionRate`: it is NOT the total number of responses. INDIRECT mentions are excluded. Brand coverage (%). Percentage of all monitored responses where your brand is mentioned. Formula: `mentionCount / responsesTotal × 100`. Source citation rate (%) Number of distinct answers containing a SELF citation Number of responses containing **at least one SELF or DIRECT source citation**. This is the denominator for `sourceRate`: it is NOT the total number of responses. INDIRECT sources are excluded. Number of unique URLs from your domain (SELF) cited as sources across all AI responses. Average SELF mention sentiment on a 0 to 100 scale; `0` is also returned when no scored SELF mention exists Share of voice (%). Formula: SELF mentions / (SELF + DIRECT mentions) × 100. INDIRECT mentions are excluded. Top competitors by mention count Top sources by citation count Topic ID Topic name Composite score (0-100). Formula: `mentionRate × 0.5 + sourceRate × 0.3 + shareOfVoice × 0.2`. Mention rate for this topic (%). Formula: `selfMentionAnswers / responsesWithMentions × 100` where `responsesWithMentions` counts answers in this topic with at least one SELF or DIRECT competitor mention. Same denominator semantic as the top-level `scores.mentionRate`. Source citation rate for this topic (%). Formula: `selfSourceAnswers / responsesWithSources × 100` where `responsesWithSources` counts answers in this topic with at least one SELF or DIRECT source citation. Same denominator semantic as the top-level `scores.sourceRate` (INDIRECT excluded). Share of voice for this topic (%). Formula: SELF mentions in topic / (SELF + DIRECT mentions in topic) × 100. Average sentiment score for brand mentions in this topic (null if no mentions) Number of distinct prompts in this topic Tag ID Tag name Composite score (0-100). Formula: `mentionRate × 0.5 + sourceRate × 0.3 + shareOfVoice × 0.2`. Mention rate for this tag (%). Formula: `selfMentionAnswers / responsesWithMentions × 100` where `responsesWithMentions` counts answers in this tag with at least one SELF or DIRECT competitor mention. Same denominator semantic as the top-level `scores.mentionRate`. Source citation rate for this tag (%). Formula: `selfSourceAnswers / responsesWithSources × 100` where `responsesWithSources` counts answers in this tag with at least one SELF or DIRECT source citation. Same denominator semantic as the top-level `scores.sourceRate` (INDIRECT excluded). Share of voice for this tag (%). Formula: SELF mentions in tag / (SELF + DIRECT mentions in tag) × 100. Average sentiment score for brand mentions in this tag (null if no mentions) Number of distinct prompts in this tag ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/performance?period=30" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "brand_01", "name": "Acme Analytics", "domain": "acme.example" }, "period": { "start": "2026-07-01", "end": "2026-07-31" }, "methodology": { "promptsCount": 156, "providersCount": 2, "providers": ["openai", "perplexity"], "responsesTotal": 312, "responsesWithMentions": 230, "responsesWithSources": 184 }, "scores": { "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 }, "topCompetitors": [ { "id": "competitor_01", "name": "Rival Labs", "relationship": "DIRECT", "mentions": 111, "avgPosition": 2.3, "avgSentiment": 75.2 } ], "topSources": [ { "id": "source_01", "domain": "publisher.example", "mentions": 102, "avgPosition": 3.1, "isSelf": false } ], "byTopic": [ { "id": "topic_01", "topic": "Data exports", "score": 68, "mentionRate": 67.50, "sourceRate": 28.00, "shareOfVoice": 9.44, "avgSentiment": 76.3, "promptsCount": 12 } ], "byTag": [ { "id": "tag_01", "name": "comparison", "score": 72, "mentionRate": 55.00, "sourceRate": 30.00, "shareOfVoice": 10.25, "avgSentiment": 81.5, "promptsCount": 8 } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ---------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid query parameter | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get Pitch Audit Source: https://docs.qwairy.co/developers/endpoints/pitch-audits/get GET /api/v1/pitch-audits/{auditId} Read the original prospect report for a Pitch Audit. Returns the original prospect's read-only Pitch Audit report: profile, prompts, scores, competitors, sources, insights, recommendations, and co-competitors. Bearer API token. Example: `Bearer qw-api-xxx` ## Path parameters Pitch Audit ID returned by [List Pitch Audits](/developers/endpoints/pitch-audits/list). ## Query parameters Include full AI answer text. Keep the default for a bounded report payload; request answers explicitly when evidence-level analysis is required. Attach the complete `scores` summary to every `coCompetitors` row. Use this expansion to compare all co-competitors in one request. ## Response `true` when the report was returned. Agency workspace ID and name. The original prospect or competitor represented by this report. Every subject that can be requested through `subjectId`. Scores, providers, competitor leaderboard, sources, insights, technical readiness, claims, and recommendations. Whether full answer evidence was requested. Whether every co-competitor row includes its full score summary. Number of answers available in the report. Full AI answer evidence. Present only when `includeAnswers=true`. Brands appearing in the same answers as the current subject. Every row includes a resolvable `subjectId`, `reportUrl`, co-occurrence metrics, and optionally `scores`. ## Synthetic request for the original prospect ```bash theme={null} curl "https://www.qwairy.co/api/v1/pitch-audits/8a218180-6dd7-43c7-a481-f6a1cc72dfd8" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Follow a co-competitor link Take `subjectId` and the canonical `reportUrl` directly from a `coCompetitors` row: ```json theme={null} { "id": "orbit.example", "subjectId": "orbit.example", "name": "Orbit Systems", "sharedAnswers": 12, "subjectAvgPosition": 2.1, "competitorAvgPosition": 1.7, "sharedSourcesCount": 9, "reportUrl": "/api/v1/pitch-audits/8a218180-6dd7-43c7-a481-f6a1cc72dfd8/reports/orbit.example" } ``` Follow `reportUrl`, or see [Get Pitch Audit Subject Report](/developers/endpoints/pitch-audits/get-report). A co-competitor is a derived view of the same audit, not a separate audit record. ## Include every co-competitor score ```bash theme={null} curl "https://www.qwairy.co/api/v1/pitch-audits/8a218180-6dd7-43c7-a481-f6a1cc72dfd8?includeCoCompetitorScores=true" \ -H "Authorization: Bearer qw-api-your-token-here" ``` Read `audit.coCompetitors`. Each row includes the relationship metrics plus `scores.geoScore`, `scores.mentionRate`, `scores.avgPosition`, `scores.sentimentScore`, `scores.shareOfVoice`, and citation metrics. Use `subjectId` only when you need the full derived report. ## Errors | Status | Code | Description | | ------ | ----------------------- | -------------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid query parameter | | 401 | gateway error | Authentication failed | | 404 | `PITCH_AUDIT_NOT_FOUND` | Audit or requested subject is not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # Get Pitch Audit subject report Source: https://docs.qwairy.co/developers/endpoints/pitch-audits/get-report GET /api/v1/pitch-audits/{auditId}/reports/{subjectId} Read a competitor or co-competitor view derived from one Pitch Audit. A subject report pivots the same Pitch Audit evidence around one detected competitor or co-competitor. It does not create a second audit or regenerate any data. Bearer API token. Example: `Bearer qw-api-xxx` ## Path parameters Parent Pitch Audit ID. Value returned by `competitors[].subjectId`, `availableSubjects[].id`, or `coCompetitors[].subjectId`. ## Query parameters Include full AI answer text. The bounded report summary is returned by default. Attach full score summaries to all co-competitors of the selected subject. ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/pitch-audits/8a218180-6dd7-43c7-a481-f6a1cc72dfd8/reports/orbit.example" \ -H "Authorization: Bearer qw-api-your-token-here" ``` To include answer evidence: ```bash theme={null} curl "https://www.qwairy.co/api/v1/pitch-audits/8a218180-6dd7-43c7-a481-f6a1cc72dfd8/reports/orbit.example?includeAnswers=true" \ -H "Authorization: Bearer qw-api-your-token-here" ``` The response uses the same report schema as [Get Pitch Audit](/developers/endpoints/pitch-audits/get). Its `reportSubject` identifies the selected competitor, and its `coCompetitors` rows link to other resolvable subjects. Follow the original prospect row's `reportUrl` to return to the base report. ## Errors | Status | Code | Description | | ------ | ----------------------- | -------------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid query or subject parameter | | 401 | gateway error | Authentication failed | | 404 | `PITCH_AUDIT_NOT_FOUND` | Audit or requested subject is not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # List Pitch Audits Source: https://docs.qwairy.co/developers/endpoints/pitch-audits/list GET /api/v1/pitch-audits List the read-only Pitch Audit snapshots available to your agency workspace. Returns Pitch Audits created by the agency workspace attached to the API token. This endpoint is read-only and never starts, retries, or modifies an audit. Only Pitch Audits owned by an agency workspace associated with the token's team are returned. General API access still follows the plan rules in [Introduction](/developers/introduction). Bearer API token. Example: `Bearer qw-api-xxx` ## Query parameters Filter by `PENDING`, `RUNNING`, `RECOVERING`, `AWAITING_PROMPTS`, `COMPLETED`, or `FAILED`. Number of audits to return. Maximum: 100. Number of audits to skip. ## Response Total, count, limit, and offset for the current page. Pitch Audit ID. Prospect name. Prospect domain. Current audit status. GEO score, mention rate, position, sentiment, share of voice, citations, and totals when available. Detected competitors. Each row includes a `subjectId` accepted by the detail endpoint. Whether the completed report is ready to read. Agency workspace ID and name. ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/pitch-audits?status=COMPLETED&limit=20" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 8, "count": 1, "limit": 20, "offset": 0 }, "audits": [ { "id": "8a218180-6dd7-43c7-a481-f6a1cc72dfd8", "brandName": "Northstar Labs", "domain": "northstar.example", "status": "COMPLETED", "promptCount": 20, "competitorCount": 4, "scores": { "geoScore": 71, "mentionRate": 60 }, "competitors": [ { "subjectId": "orbit.example", "name": "Orbit Systems", "domain": "orbit.example", "score": 64 } ], "reportReady": true, "team": { "id": "team-123", "name": "Agency workspace" } } ] } ``` ## Next step Use the returned audit ID with [Get Pitch Audit](/developers/endpoints/pitch-audits/get). Use a competitor `subjectId` with [Get Pitch Audit Subject Report](/developers/endpoints/pitch-audits/get-report). ## Errors | Status | Code | Description | | ------ | ------------------- | ---------------------------------------- | | `400` | `INVALID_PARAMETER` | Invalid status or pagination value | | `401` | gateway error | Authentication failed | | `429` | gateway error | Rate limit exceeded; honor `Retry-After` | # Prompt answers Source: https://docs.qwairy.co/developers/endpoints/prompts/answers GET /api/v1/brands/{brandId}/prompts/{promptId}/answers Retrieve all AI-generated responses for a specific prompt. Returns all stored answers for one prompt, optionally filtered by provider. This endpoint is not paginated. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the prompt ## Query parameters Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/prompts/PROMPT_ID/answers" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "prompt": { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?" }, "total": 2, "answers": [ { "id": "answer_01", "provider": "ChatGPT", "model": "GPT-5.4", "text": "Acme Analytics and Rival Labs document warehouse export integrations.", "hasSelfMention": true, "selfMentionPosition": 3, "hasSelfSource": false, "competitorsMentioned": ["Acme Analytics", "Rival Labs"], "sourcesCited": ["publisher.example", "reviews.example"], "sentiment": 82, "createdAt": "2026-07-19T10:00:00.000Z" }, { "id": "answer_02", "provider": "Perplexity", "model": "Perplexity Sonar", "text": "The cited documentation lists supported export destinations.", "hasSelfMention": true, "selfMentionPosition": 2, "hasSelfSource": true, "competitorsMentioned": ["Acme Analytics", "Northstar Data"], "sourcesCited": ["acme.example", "docs.example"], "sentiment": 78, "createdAt": "2026-07-19T10:05:00.000Z" } ] } ``` `sentiment` is the SELF mention sentiment on a 0 to 100 scale, or `null`. ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `401` | Missing or invalid API token | | `404` | Brand or prompt not found | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Prompt evolution Source: https://docs.qwairy.co/developers/endpoints/prompts/evolution GET /api/v1/brands/{brandId}/prompts/{promptId}/evolution Track how a prompt's metrics change over time. Returns daily response-level metrics for one prompt. Mention Rate and Source Rate use eligible SELF-or-DIRECT answer denominators. ## Path parameters The unique identifier of the brand The unique identifier of the prompt ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/prompts/PROMPT_ID/evolution?period=7" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "prompt": { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?" }, "evolution": [ { "date": "2026-07-01", "answers": 5, "mentionRate": 80.0, "sourceRate": 60.0, "avgSentiment": 78.5 }, { "date": "2026-07-02", "answers": 8, "mentionRate": 75.0, "sourceRate": 62.5, "avgSentiment": 80.1 } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ---------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid query parameter | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 404 | `PROMPT_NOT_FOUND` | Prompt doesn't exist | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | `mentionRate` is answers with a SELF mention divided by answers with a SELF or DIRECT mention. `sourceRate` applies the same response-level denominator rule to citations. `avgSentiment` is on the 0 to 100 scale and can be `null`. See [Rate limits](/developers/rate-limits) for retry behavior. # Get prompt Source: https://docs.qwairy.co/developers/endpoints/prompts/get GET /api/v1/brands/{brandId}/prompts/{promptId} Retrieve detailed information about a specific prompt. Returns one prompt with response-level metrics and up to 100 aggregated query fan-outs. Use `include=details` for mention and citation aggregates. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the prompt ## Query parameters Set to `details` to enrich the prompt with aggregated competitors, sources, brand mentions, and source stability metrics. When omitted, returns the base fields only. ## Response The base object includes `answersCount`, Mention Rate, Source Rate, SELF `avgSentiment`, and these query-fan-out fields: * `queryFanOutsTotal`: total distinct normalized queries * `queryFanOutsLimit`: maximum rows returned, currently `100` * `queryFanOuts`: `query`, `occurrences`, `uniqueAnswers`, raw `models`, `firstSeen`, and `lastSeen` Mention Rate divides answers with a SELF mention by answers with a SELF or DIRECT mention. Source Rate applies the equivalent answer-level formula to citations. ## Synthetic response ```json theme={null} { "success": true, "prompt": { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?", "topic": { "id": "topic_01", "name": "Data exports" }, "type": "TOFU", "tags": [ { "id": "tag1", "name": "reviews" }, { "id": "tag2", "name": "comparison" } ], "answersCount": 2, "mentionRate": 50.0, "sourceRate": 25.0, "avgSentiment": 78.5, "queryFanOutsTotal": 1, "queryFanOutsLimit": 100, "queryFanOuts": [ { "query": "analytics warehouse export integrations", "occurrences": 2, "uniqueAnswers": 2, "models": ["openai-gpt-5.4"], "firstSeen": "2026-07-18T10:00:00.000Z", "lastSeen": "2026-07-19T10:00:00.000Z" } ], "createdAt": "2026-07-01T10:00:00.000Z", "lastGeneratedAt": "2026-07-19T10:00:00.000Z" } } ``` ## Synthetic response with `include=details` ```json theme={null} { "success": true, "prompt": { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?", "topic": { "id": "topic_01", "name": "Data exports" }, "type": "TOFU", "tags": [ { "id": "tag1", "name": "reviews" }, { "id": "tag2", "name": "comparison" } ], "answersCount": 2, "mentionRate": 50.0, "sourceRate": 25.0, "avgSentiment": 78.5, "queryFanOutsTotal": 1, "queryFanOutsLimit": 100, "queryFanOuts": [], "createdAt": "2026-07-01T10:00:00.000Z", "lastGeneratedAt": "2026-07-19T10:00:00.000Z", "competitors": [ { "name": "Acme Analytics", "mentions": 3, "relationship": "SELF" }, { "name": "Rival Labs", "mentions": 8, "relationship": "DIRECT" } ], "competitorsCount": 2, "competitorMentionsTotal": 11, "brandMentioned": true, "brandMentionCount": 3, "sources": [ { "domain": "acme.example", "citations": 2, "isSelf": true }, { "domain": "reviews.example", "citations": 4, "isSelf": false } ], "sourcesCount": 2, "sourceCitationsTotal": 6, "sourceStability": 50.0 } } ``` ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `401` | Missing or invalid API token | | `404` | Brand or prompt not found | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List prompts Source: https://docs.qwairy.co/developers/endpoints/prompts/list GET /api/v1/brands/{brandId}/prompts Get paginated prompts with search and filtering options. Lists monitored prompts with answer counts and response-level SELF metrics. A prompt can have a funnel stage, topic, and tags. See [Entities](/developers/entities#prompt) for the complete Prompt object structure, and [Topic](/developers/entities#topic) / [Tag](/developers/entities#tag) for classification objects. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. Returns prompts that received at least one answer within the window. If not specified, returns all data. Start date in `YYYY-MM-DD` (for example, `2026-03-05`). Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD` (for example, `2026-03-31`). Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. When a date range is provided, both the prompt list and the per-prompt `include=details` aggregations (`answersCount`, `competitors[].mentions`, `brandMentionCount`, `sources[].citations`) are scoped to that window. Prompts that ran multiple times still appear if at least one of their runs falls inside the range. Filter by topic ID (keywordId). Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by prompt type: `TOFU`, `MOFU`, `BOFU` Maximum number of prompts to return (max: 100) Pagination offset Field to sort by: `lastGeneratedAt`, `answersCount`, `text`, `createdAt` Sort order: `asc` or `desc` Set to `details` to enrich each prompt with aggregated competitors, sources, brand mentions, and source stability metrics. When omitted, the response includes only the base fields listed below. ## Response Indicates if the request was successful Total number of prompts matching filters Number of prompts in this response Maximum items per page Number of items skipped Prompt ID Prompt text Associated topic name Prompt type (TOFU/MOFU/BOFU) List of tag names Number of generated answers Answers with a SELF mention divided by answers with a SELF or DIRECT mention Answers with a SELF citation divided by answers with a SELF or DIRECT citation Last generation timestamp (ISO 8601) List of competitors mentioned across all answers for this prompt. Each object contains `name` (string), `mentions` (number), and `relationship` (`SELF`, `DIRECT`, `INDIRECT`). Number of unique competitors mentioned Total competitor mentions across all answers Whether your brand was mentioned in any answer Number of times your brand was mentioned List of source domains cited across all answers for this prompt. Each object contains `domain` (string), `citations` (number), and `isSelf` (boolean). Number of unique source domains cited Total source citations across all answers Percentage of distinct source domains present in every scoped answer when there is more than one answer; otherwise `0` Prompt creation timestamp (ISO 8601) ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/prompts?type=TOFU&limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 1, "limit": 10, "offset": 0 }, "prompts": [ { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?", "topic": "Data exports", "type": "TOFU", "tags": ["reviews", "comparison"], "answersCount": 2, "mentionRate": 50.0, "sourceRate": 25.0, "lastGeneratedAt": "2026-07-19T10:00:00.000Z" } ] } ``` ## Synthetic response with `include=details` ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/prompts?include=details&limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 1, "limit": 10, "offset": 0 }, "prompts": [ { "id": "prompt_01", "text": "Which analytics platforms support warehouse exports?", "topic": "Data exports", "type": "TOFU", "tags": ["reviews", "comparison"], "answersCount": 2, "mentionRate": 50.0, "sourceRate": 25.0, "lastGeneratedAt": "2024-12-19T10:00:00.000Z", "competitors": [ { "name": "Acme Analytics", "mentions": 3, "relationship": "SELF" }, { "name": "Rival Labs", "mentions": 8, "relationship": "DIRECT" }, { "name": "Northstar Data", "mentions": 5, "relationship": "DIRECT" } ], "competitorsCount": 3, "competitorMentionsTotal": 16, "brandMentioned": true, "brandMentionCount": 3, "sources": [ { "domain": "acme.example", "citations": 2, "isSelf": true }, { "domain": "reviews.example", "citations": 4, "isSelf": false }, { "domain": "publisher.example", "citations": 3, "isSelf": false } ], "sourcesCount": 3, "sourceCitationsTotal": 9, "sourceStability": 33.33, "createdAt": "2026-07-01T10:00:00.000Z" } ] } ``` ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `400` | Invalid query parameter | | `401` | Missing or invalid API token | | `404` | Brand not found or inaccessible | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List query fan-out records Source: https://docs.qwairy.co/developers/endpoints/search GET /api/v1/brands/{brandId}/search List aggregated web-search queries recorded while providers generated answers. Returns retained web-search queries associated with stored answers. Results are grouped by normalized query text and include presence counts plus an API-defined priority label. Results are aggregated by query (case-insensitive, trimmed). Each row represents a unique search query across all responses and providers. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/search ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by one or more provider aliases or model IDs, separated by commas. Maximum number of queries to return (max: 100) Number of results to skip for pagination Field to sort by: `occurrences`, `priority`, `brandPresence`, `query`, `createdAt` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of unique queries matching filters Number of queries in this response Maximum items per page Number of items skipped The search query used by the AI Number of times this query appeared across all responses Priority level: `very-high` (competitors present, brand absent), `high` (both present), `medium` (brand only), `low` (neither) Number of responses where the brand's domain was cited as source Number of responses where direct competitors were mentioned Distinct direct competitor names Number of distinct prompts associated with this query Associated prompt objects with `id` and `text` Number of distinct AI responses containing this query Number of distinct AI models that generated this query Model IDs associated with the query Associated topic names Associated tag names First occurrence (ISO 8601 timestamp) Most recent occurrence (ISO 8601 timestamp) ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/search?limit=10&sort=priority" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 142, "count": 2, "limit": 10, "offset": 0 }, "searches": [ { "query": "enterprise analytics platforms comparison", "occurrences": 12, "priority": "very-high", "brandPresence": 0, "competitorPresence": 8, "competitors": ["Orbit Systems"], "uniquePrompts": 5, "prompts": [{ "id": "prompt_analytics", "text": "Which analytics platforms are commonly compared?" }], "uniqueAnswers": 12, "uniqueModels": 3, "models": ["openai-gpt-5.4", "anthropic-claude-4.6-sonnet", "google-gemini-3-pro"], "topics": ["Analytics"], "tags": ["Comparison"], "firstSeen": "2026-07-01T10:30:00.000Z", "lastSeen": "2026-07-15T10:30:00.000Z" }, { "query": "northstar analytics pricing", "occurrences": 5, "priority": "medium", "brandPresence": 3, "competitorPresence": 0, "competitors": [], "uniquePrompts": 2, "prompts": [{ "id": "prompt_pricing", "text": "What pricing information is available?" }], "uniqueAnswers": 5, "uniqueModels": 2, "models": ["openai-gpt-5.4", "google-gemini-3-pro"], "topics": ["Pricing"], "tags": [], "firstSeen": "2026-07-05T09:15:00.000Z", "lastSeen": "2026-07-14T09:15:00.000Z" } ] } ``` `priority` is a deterministic classification from `brandPresence` and `competitorPresence`. Treat it as a triage input, not as a business-impact forecast. ## Errors | Status | Code | Description | | ------ | ------------------- | ----------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid filter, pagination, or sort value | | 401 | gateway error | Authentication failed | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # List shopping results Source: https://docs.qwairy.co/developers/endpoints/shopping GET /api/v1/brands/{brandId}/shopping List shopping observations extracted from stored answers. Returns shopping observations extracted from stored answers. Product details, ratings, reviews, and purchase options can be `null` or empty when they were not captured. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/shopping ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by one or more provider aliases or model IDs, separated by commas. Maximum number of products to return (max: 100) Number of results to skip for pagination Field to sort by: `createdAt`, `rating`, `reviews`, `productName` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of products matching filters Number of products in this response Maximum items per page Number of items skipped Shopping insight ID Product name Product description Product image URL Product rating (0-5) Number of reviews Purchasing options with prices and sellers Prompt associated with the stored observation ISO 8601 timestamp ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/shopping?limit=10" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 85, "count": 2, "limit": 10, "offset": 0 }, "shopping": [ { "id": "shop1", "productName": "Northstar Analytics Pro", "description": "Synthetic product description", "image": "https://assets.northstar.example/product-image.jpg", "rating": 4.5, "reviews": 1250, "options": [ { "price": "$99/month", "currency": "USD", "websiteName": "Northstar Labs", "websiteUrl": "https://northstar.example/pricing" } ], "prompt": "What is the best CRM software for enterprise?", "createdAt": "2026-07-15T10:30:00Z" }, { "id": "shop2", "productName": "Orbit Insights", "description": "Synthetic analytics product", "image": null, "rating": 4.2, "reviews": 890, "options": [], "prompt": "Compare top analytics platforms", "createdAt": "2026-07-14T15:45:00Z" } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ----------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid filter, pagination, or sort value | | 401 | gateway error | Authentication failed | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # List social signals Source: https://docs.qwairy.co/developers/endpoints/social GET /api/v1/brands/{brandId}/social List citations classified as social-platform sources in stored answers. Returns citations classified as social or forum sources in stored answers. The stable filter values are `reddit`, `youtube`, `twitter`, `facebook`, `linkedin`, and `other`. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/social ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by one or more provider aliases or model IDs, separated by commas. Filter by platform: `reddit`, `youtube`, `twitter`, `facebook`, `linkedin`, `other` Maximum number of citations to return (max: 100) Number of results to skip for pagination Field to sort by: `createdAt`, `position`, `upvotes`, `comments`, `url` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of citations matching filters Number of citations in this response Maximum items per page Number of items skipped Social insight ID Platform name (reddit, youtube, twitter, etc.) Community identifier (subreddit, channel, etc.) Community display name Full URL of the cited content Content title (if available) Domain name Position in source list Upvotes/likes (Reddit, when available) Comment count (when available) ISO 8601 timestamp ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/social?platform=reddit&limit=10" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 2, "limit": 10, "offset": 0 }, "social": [ { "id": "soc1", "platform": "reddit", "communityId": "SaaS", "communityName": "r/SaaS", "url": "https://reddit.com/r/SaaS/comments/abc123/best_crm_tools", "title": "Analytics tools discussed by a sample community", "domain": "reddit.com", "position": 2, "upvotes": 245, "commentCount": 89, "createdAt": "2026-07-15T10:30:00Z" }, { "id": "soc2", "platform": "youtube", "communityId": "TechReviewer", "communityName": "TechReviewer", "url": "https://youtube.com/@TechReviewer/crm-comparison", "title": "Synthetic analytics comparison", "domain": "youtube.com", "position": 3, "upvotes": 0, "commentCount": 0, "createdAt": "2026-07-14T15:45:00Z" } ] } ``` Missing engagement metadata is returned as `0`. Do not interpret `0` as a verified platform engagement count without checking the underlying source. ## Errors | Status | Code | Description | | ------ | ------------------- | ----------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid filter, pagination, or sort value | | 401 | gateway error | Authentication failed | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # Source domain evolution Source: https://docs.qwairy.co/developers/endpoints/source-domains/evolution GET /api/v1/brands/{brandId}/source-domains/{sourceId}/evolution Track how a source's citation metrics change over time. Returns daily citation occurrences and average citation position for one source domain. ## Path parameters The unique identifier of the brand The unique identifier of the source ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/source-domains/SOURCE_ID/evolution?period=7" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "source": { "id": "source_01", "domain": "publisher.example", "isSelf": false }, "evolution": [ { "date": "2026-07-01", "mentions": 5, "avgPosition": 3.1 }, { "date": "2026-07-02", "mentions": 8, "avgPosition": 2.9 } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------ | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 404 | `SOURCE_NOT_FOUND` | Source doesn't exist | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get source domain Source: https://docs.qwairy.co/developers/endpoints/source-domains/get GET /api/v1/brands/{brandId}/source-domains/{sourceId} Retrieve detailed source info with breakdowns by provider and topic. Returns one cited domain with date-scoped metrics, provider and topic breakdowns, and the latest public AI Source Authority score. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/source-domains/SOURCE_ID?period=30" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` The unique identifier of the source ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. ## Synthetic response ```json theme={null} { "success": true, "source": { "id": "source_01", "domain": "publisher.example", "type": "MEDIA", "status": "ACTIVE", "isSelf": false, "totalMentions": 102, "rate": 5.10, "avgPosition": 3.2, "aiSourceAuthorityScore": 61, "byProvider": [ { "provider": "ChatGPT", "mentions": 60, "avgPosition": 3.1 }, { "provider": "Perplexity", "mentions": 42, "avgPosition": 3.4 } ], "byTopic": [ { "topic": "Product Reviews", "mentions": 55, "avgPosition": 2.8 }, { "topic": "Comparisons", "mentions": 30, "avgPosition": 3.5 } ] } } ``` `rate` is this domain's citation occurrences divided by every citation occurrence in the selected date scope. `aiSourceAuthorityScore` can be `null` independently of that date filter because it comes from the latest authority snapshot. ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `401` | Missing or invalid API token | | `404` | Brand or source domain not found | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List source domains Source: https://docs.qwairy.co/developers/endpoints/source-domains/list GET /api/v1/brands/{brandId}/source-domains Monitor which domains AI platforms cite. Lists cited domains and their citation-occurrence share in the selected answer scope. See [Entities](/developers/entities#source) for the complete Source object structure. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by source type: `INSTITUTIONAL`, `COMMERCIAL`, `MEDIA`, `BLOG`, etc. Filter by topic ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by tag ID. Supports comma-separated multi-select (e.g., `id1,id2`). Maximum number of sources to return (max: 100) Number of results to skip for pagination Field to sort by: `mentions`, `position`, `rate`, `domain` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of sources matching filters Number of sources in this response Maximum items per page Number of items skipped Source ID Domain name Source type Whether this is your own domain Total citation count This domain's citation occurrences divided by all citation occurrences in the selected scope Average position in source lists Latest public AI Source Authority score, or `null` when evidence is insufficient or no snapshot exists ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/source-domains?limit=10&offset=0" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 45, "count": 2, "limit": 10, "offset": 0 }, "sources": [ { "id": "source_01", "domain": "publisher.example", "type": "MEDIA", "isSelf": false, "totalMentions": 102, "rate": 5.10, "avgPosition": 3.2, "aiSourceAuthorityScore": 61 }, { "id": "source_02", "domain": "acme.example", "type": "INSTITUTIONAL", "isSelf": true, "totalMentions": 45, "rate": 2.25, "avgPosition": 2.1, "aiSourceAuthorityScore": null } ] } ``` ## Errors | Status | Meaning | | ------ | ---------------------------------------- | | `400` | Invalid query parameter | | `401` | Missing or invalid API token | | `404` | Brand not found or inaccessible | | `429` | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List source URLs Source: https://docs.qwairy.co/developers/endpoints/source-urls GET /api/v1/brands/{brandId}/source-urls Track individual page citations. Lists cited URLs with occurrence counts, average position, taxonomy, and relationship flags for the selected answer scope. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/source-urls ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by specific domain Filter by self domains: `true` or `false` Filter by competitor domains: `true` or `false` Maximum number of URLs to return (max: 100) Number of results to skip for pagination Field to sort by: `mentions`, `position`, `shareOfVoice`, `url` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of URLs matching filters Number of URLs in this response Maximum items per page Number of items skipped Full URL of the cited page Page title (if available) Domain name Whether this is your domain Whether this is a direct competitor's domain Names of direct competitors associated with this URL Total citation count Average position in source lists Share of all citations (0-100) Associated topic objects with `id` and `text` Associated tag objects with `id` and `name` ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/source-urls?isSelf=true&limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 2, "limit": 10, "offset": 0 }, "sourceUrls": [ { "url": "https://acme.example/guides/warehouse-exports", "title": "Warehouse export guide", "domain": "acme.example", "isSelf": true, "isCompetitor": false, "competitors": [], "totalMentions": 45, "avgPosition": 2.1, "shareOfVoice": 3.5, "topics": [{ "id": "topic_01", "text": "Data exports" }], "tags": [{ "id": "tag_01", "name": "integrations" }] }, { "url": "https://rival.example/docs/export-review", "title": "Export integration overview", "domain": "rival.example", "isSelf": false, "isCompetitor": true, "competitors": ["Rival Labs"], "totalMentions": 32, "avgPosition": 3.4, "shareOfVoice": 2.1, "topics": [{ "id": "topic_01", "text": "Data exports" }], "tags": [] } ] } ``` ## Errors | Status | Code | Description | | ------ | ----------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | `shareOfVoice` is the URL's citation occurrences divided by all citation occurrences in the selected scope. See [Rate limits](/developers/rate-limits) for retry behavior. # List sponsored content Source: https://docs.qwairy.co/developers/endpoints/sponsored GET /api/v1/brands/{brandId}/sponsored List sponsored placements detected in stored answers. Returns sponsored placements retained from stored answers. A record reflects the captured answer at that time; it does not confirm an active campaign or advertiser relationship. Bearer token. Example: `Bearer qw-api-xxx` ```http theme={null} GET /api/v1/brands/{brandId}/sponsored ``` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by one or more provider aliases or model IDs, separated by commas. Filter by advertiser name (case-insensitive partial match) Maximum number of results to return (max: 100) Number of results to skip for pagination Field to sort by: `createdAt`, `advertiserName`, `productName`, `position` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of sponsored placements matching filters Number of items in this response Maximum items per page Number of items skipped Sponsored content record ID Name of the advertiser URL of the advertiser's logo Name of the advertised product Product description text Product image URL Position of the ad within the response (1 = first) AI provider model ID Relationship to your brand if advertiser is a known competitor: `SELF`, `DIRECT`, `INDIRECT`, or `null` Prompt associated with the stored placement ISO 8601 timestamp ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/sponsored?period=30&limit=10" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 24, "count": 2, "limit": 10, "offset": 0 }, "sponsored": [ { "id": "sp1", "advertiserName": "Orbit Travel", "advertiserLogo": "https://assets.orbit.example/logo.png", "productName": "Sample flight offer", "description": "Synthetic sponsored placement description.", "imageUrl": null, "position": 1, "provider": "openai-gpt-5.4", "competitorRelationship": "DIRECT", "prompt": "What are the best flight deals from Chicago?", "createdAt": "2026-04-10T14:30:00Z" }, { "id": "sp2", "advertiserName": "Northstar Retail", "advertiserLogo": "https://assets.northstar.example/logo.png", "productName": "Sample seasonal offer", "description": "Synthetic sponsored placement description.", "imageUrl": null, "position": 1, "provider": "openai-gpt-5.4", "competitorRelationship": null, "prompt": "Best spring fashion deals 2026", "createdAt": "2026-04-09T09:15:00Z" } ] } ``` ## Errors | Status | Code | Description | | ------ | ------------------- | ----------------------------------------- | | 400 | `INVALID_PARAMETER` | Invalid filter, pagination, or sort value | | 401 | gateway error | Authentication failed | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | # Tag evolution Source: https://docs.qwairy.co/developers/endpoints/tags/evolution GET /api/v1/brands/{brandId}/tags/{tagId}/evolution Daily time-series metrics for a specific tag. Returns daily response-level metrics for one tag. Mention Rate and Source Rate use eligible SELF-or-DIRECT answer denominators; Share of Voice uses mention occurrences. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand The unique identifier of the tag ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic (keyword) ID. Supports comma-separated multi-select (e.g., `id1,id2`). Filter by prompt type: `TOFU`, `MOFU`, `BOFU` ## Response Indicates if the request was successful Tag ID Tag name Date (YYYY-MM-DD format) Stored answers on this date Answers with a SELF mention divided by answers with a SELF or DIRECT mention SELF mention occurrences divided by SELF and DIRECT mention occurrences Average SELF mention sentiment on a 0 to 100 scale, or `null` Answers with a SELF citation divided by answers with a SELF or DIRECT citation ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/tags/TAG_ID/evolution?period=30" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "tag": { "id": "tag1", "name": "Product Comparisons" }, "evolution": [ { "date": "2026-02-15", "answers": 42, "mentionRate": 65.2, "shareOfVoice": 48.3, "avgSentiment": 72.1, "sourceRate": 55.0 }, { "date": "2026-02-16", "answers": 38, "mentionRate": 60.5, "shareOfVoice": 45.1, "avgSentiment": 74.2, "sourceRate": 52.6 } ] } ``` ## Errors | Status | Code | Description | | ------ | ----------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 404 | `TAG_NOT_FOUND` | Tag doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # List tags Source: https://docs.qwairy.co/developers/endpoints/tags/list GET /api/v1/brands/{brandId}/tags Analyze performance by tag. Lists tags with answer counts and response-level SELF metrics for the selected scope. See [Entities](/developers/entities#tag) for the complete Tag object structure. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Query parameters Number of days to include. If not specified, returns all data. Start date in `YYYY-MM-DD`. Provide it with `endDate`; a lone boundary is currently ignored. Choose either `period` or the complete date pair. End date in `YYYY-MM-DD`. Provide it with `startDate`; a lone boundary is currently ignored. Do not combine the complete date pair with `period`. Filter by AI provider. Supports comma-separated multi-select (e.g., `chatgpt,claude`). Filter by topic (keyword) ID. Supports comma-separated multi-select (e.g., `id1,id2`). Maximum number of tags to return (max: 100) Number of results to skip for pagination Field to sort by: `prompts`, `answers`, `mentionRate`, `shareOfVoice`, `sentiment`, `name` Sort order: `asc` or `desc` ## Response Indicates if the request was successful Total number of tags matching filters Number of tags in this response Maximum items per page Number of items skipped Tag ID Tag name Number of prompts with this tag Stored answers in the selected scope Answers with a SELF mention divided by answers with a SELF or DIRECT mention SELF mention occurrences divided by SELF and DIRECT mention occurrences Average SELF mention sentiment on a 0 to 100 scale; `0` is returned when no scored SELF mention exists ## Synthetic request ```bash theme={null} curl "https://www.qwairy.co/api/v1/brands/YOUR_BRAND_ID/tags?limit=10" \ -H "Authorization: Bearer $QWAIRY_API_TOKEN" ``` ## Synthetic response ```json theme={null} { "success": true, "pagination": { "total": 12, "count": 2, "limit": 10, "offset": 0 }, "tags": [ { "id": "tag1", "name": "Product Comparisons", "totalPrompts": 25, "totalAnswers": 200, "brandMentionRate": 52.0, "shareOfVoice": 38.5, "avgSentiment": 81.2 }, { "id": "tag2", "name": "Pricing Questions", "totalPrompts": 18, "totalAnswers": 144, "brandMentionRate": 35.4, "shareOfVoice": 22.1, "avgSentiment": 68.5 } ] } ``` ## Errors | Status | Code | Description | | ------ | ----------------- | ---------------------------------------- | | 401 | gateway error | Missing or invalid API token | | 404 | `BRAND_NOT_FOUND` | Brand doesn't exist or not accessible | | 429 | gateway error | Rate limit exceeded; honor `Retry-After` | See [Rate limits](/developers/rate-limits) for retry behavior. # Get Site Readiness analysis Source: https://docs.qwairy.co/developers/endpoints/technical-analysis GET /api/v1/brands/{brandId}/technical-analysis Read the stored Site Readiness analysis for robots.txt, llms.txt, and sitemap.xml. Returns the latest stored Site Readiness analysis for `robots.txt`, `llms.txt`, and `sitemap.xml`. The response field remains `aiReadiness` for API compatibility. This read-only request does not start a crawl. When no stored analysis exists, `analyzed` is `false`, `aiReadiness` and the analysis fields are `null`, and the response includes a message explaining how the product initializes the analysis. Bearer token. Example: `Bearer qw-api-xxx` ## Path parameters The unique identifier of the brand ## Site Readiness Score formula Out of 100: * robots.txt present: **+20** * accessible crawlers: **+round((Allowed or Partial entries / returned crawler entries) × 40)**. No crawler entries means 0 points for this component. * llms.txt present: **+20** * sitemap.xml present: **+20** A missing or invalid robots.txt therefore caps the score at 40. ## Response Indicates if the request was successful `id`, `name`, `domain` `false` when no analysis has been generated yet (all analysis fields are then `null`) Site Readiness Score (`0-100`) Number of detected issues Number of suggested optimizations URL checked for robots.txt URL checked for llms.txt URL checked for sitemap.xml `present`, `missing`, `error` or `invalid` Final URL after redirects Error message when status is `error` Fetched file content Per-crawler `{ status: "Allowed" | "Disallowed" | "Partial", reason }` map (robots.txt only) Same shape as `robotsAnalysis` (no `crawlers`) Same shape as `robotsAnalysis` (no `crawlers`) Last analysis timestamp (ISO 8601) or `null` Generated llms.txt template, or `null` if never generated ## Synthetic request ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands/cm1234567890abcdef/technical-analysis" \ -H "Authorization: Bearer qw-api-your-token-here" ``` ## Synthetic response ```json theme={null} { "success": true, "brand": { "id": "cm1234567890abcdef", "name": "Northstar Labs", "domain": "northstar.example" }, "analyzed": true, "aiReadiness": { "score": 80, "issuesCount": 0, "optimizationsCount": 1 }, "robotsUrl": "https://northstar.example/robots.txt", "llmsUrl": "https://northstar.example/llms.txt", "sitemapUrl": "https://northstar.example/sitemap.xml", "robotsAnalysis": { "status": "present", "finalUrl": "https://northstar.example/robots.txt", "crawlers": { "GPTBot": { "status": "Allowed", "reason": "No matching disallow rule" }, "ClaudeBot": { "status": "Allowed", "reason": "No matching disallow rule" } } }, "llmsAnalysis": { "status": "missing" }, "sitemapAnalysis": { "status": "present", "finalUrl": "https://northstar.example/sitemap.xml" }, "lastAnalyzedAt": "2026-05-12T08:30:00.000Z", "generatedLlmsTxt": null } ``` ## Errors | Status | Code | Description | | ------ | ----------------- | ---------------------------------------- | | `401` | gateway error | Authentication failed | | `404` | `BRAND_NOT_FOUND` | Brand not found or not accessible | | `429` | gateway error | Rate limit exceeded; honor `Retry-After` | # Entities Source: https://docs.qwairy.co/developers/entities Reference the shared objects and metric fields returned by API v1. These shared structures appear across API v1 responses. Endpoint pages remain the source of truth for fields returned by a specific route. ## Pagination Most list endpoints return a `pagination` object. The brands endpoint returns `total` without pagination. ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 10, "limit": 10, "offset": 0 }, "data": [ ... ] } ``` Total number of items matching the query (across all pages) Number of items returned in this response Maximum items per page (as requested or default) Number of items skipped (for pagination) ### Synthetic pagination example To fetch page 2 with 20 items per page: ```bash theme={null} GET /api/v1/brands/{brandId}/prompts?limit=20&offset=20 ``` Synthetic response: ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 20, "limit": 20, "offset": 20 }, "prompts": [ ... ] } ``` *** ## Brand A brand represents the company or product monitored by a workspace. List Brands, Get Brand Details Synthetic example: ```json theme={null} { "id": "brand_01", "name": "Acme Analytics", "domain": "acme.example", "description": "Analytics software for operations teams.", "createdAt": "2026-07-15T10:30:00.000Z", "stats": { "promptsCount": 156, "answersCount": 312, "competitorsCount": 8 } } ``` Opaque brand identifier Brand display name Primary domain associated with the brand Optional brand description (can be null) ISO 8601 timestamp of brand creation Total prompts tracked for this brand Total stored answers for this brand Number of competitors (SELF + DIRECT) *** ## Pitch Audit A Pitch Audit is a read-only report resource generated for an agency prospect. Once completed, its evidence is a snapshot. Competitor and co-competitor reports are subject views derived from the same audit, not separate audit records. List Pitch Audits, Get Pitch Audit, Get Pitch Audit Subject Report Synthetic example: ```json theme={null} { "id": "8a218180-6dd7-43c7-a481-f6a1cc72dfd8", "status": "COMPLETED", "reportSubject": { "id": "self", "name": "Prospect Brand", "isOriginalBrand": true }, "availableSubjects": [ { "id": "self", "name": "Prospect Brand", "isOriginalBrand": true }, { "id": "rival.example", "name": "Rival Labs", "isOriginalBrand": false } ], "answersIncluded": false, "answerCount": 24, "coCompetitors": [ { "subjectId": "rival.example", "name": "Rival Labs", "scores": { "geoScore": 64, "mentionRate": 50 }, "reportUrl": "/api/v1/pitch-audits/8a218180-6dd7-43c7-a481-f6a1cc72dfd8/reports/rival.example" } ] } ``` Subject represented by the current report. The original prospect uses `self`; derived reports use a stable competitor `subjectId`. All subjects that can be resolved within this audit. Brands that appear in the same answers as the current subject. Use each row's `subjectId` or canonical `reportUrl` to open its derived report. When `includeCoCompetitorScores=true`, each row also includes its full `scores` summary. Whether the response contains full AI answer text. Answers are excluded by default. Number of answers available, whether or not answer text was requested. *** ## Competitor A competitor is a brand or domain detected in stored answers and classified relative to the monitored brand. List Competitors, Get Competitor Details, Competitor Evolution Synthetic example: ```json theme={null} { "id": "cmp_abc123", "name": "Rival Labs", "domain": "rival.example", "relationship": "DIRECT", "totalMentions": 111, "shareOfVoice": 8.73, "avgPosition": 2.3, "avgSentiment": 75.2 } ``` Unique competitor identifier Competitor display name Competitor's primary domain Relationship type: * `SELF`: Your own brand * `DIRECT`: Direct competitor (used in Share of Voice calculation) * `INDIRECT`: Indirect competitor (mentioned by AI but not a direct rival) The list endpoint returns `SELF` and `DIRECT` by default and can include `INDIRECT` through the `relationship` filter. Detail and evolution Share of Voice use SELF and DIRECT mentions. On the list endpoint, Share of Voice uses the relationships selected by the filter. Total number of times mentioned in AI responses Share of Voice (0-100). This entity's share of all brand mentions in AI responses: its mentions divided by the total of all SELF and DIRECT mentions. INDIRECT mentions are excluded. Average position when mentioned in lists (1 = first, lower is better) Average mention sentiment on a 0 to 100 scale, or `null` *** ## Source A source is a domain cited by AI platforms when generating responses. Track which websites influence AI answers in your industry. Source Domains, Source URLs, Source Evolution Synthetic example: ```json theme={null} { "id": "src_xyz789", "domain": "publisher.example", "type": "MEDIA", "isSelf": false, "totalMentions": 102, "rate": 5.10, "avgPosition": 3.2, "aiSourceAuthorityScore": 61 } ``` Unique source identifier Source domain name Source category: * `INSTITUTIONAL`: Official/government sites * `COMMERCIAL`: E-commerce, business sites * `MEDIA`: News, magazines, publications * `BLOG`: Personal or company blogs * `SOCIAL`: Social media platforms * `FORUM`: Discussion forums, Q\&A sites * `EDUCATIONAL`: Universities, courses * `OTHER`: Uncategorized Whether this source belongs to your brand Total citations in AI responses **Share of Citations** - percentage of all AI citations from this source (0-100). Example: `rate: 5.10` means 5.10% of all citations reference this domain. Average position in source lists (1 = first) Latest public AI Source Authority score, or `null` when no sufficient snapshot is available *** ## Prompt A prompt is a question monitored across AI providers. Each prompt can have a funnel stage, topic, and tags. List Prompts, Get Prompt Details, Prompt Answers Synthetic example: ```json theme={null} { "id": "q_abc123", "text": "Which analytics platforms support warehouse exports?", "topic": "Data exports", "type": "TOFU", "tags": ["reviews", "comparison"], "answersCount": 2, "mentionRate": 50.0, "sourceRate": 25.0, "lastGeneratedAt": "2026-07-19T10:00:00.000Z" } ``` Unique prompt identifier The prompt/question text Associated topic/keyword name (can be null) Marketing funnel stage: * `TOFU`: Top of Funnel (awareness) * `MOFU`: Middle of Funnel (consideration) * `BOFU`: Bottom of Funnel (decision) List of tag names for organization Number of AI responses generated for this prompt Answers with a SELF mention divided by answers with a SELF or DIRECT mention, from 0 to 100 Answers with a SELF citation divided by answers with a SELF or DIRECT citation, from 0 to 100 ISO 8601 timestamp of last answer generation *** ## Answer An answer is stored output from one provider run for a prompt. It contains text, detected mentions, and citations. List Answers, Get Answer Details Synthetic example: ```json theme={null} { "id": "ans_123abc", "promptId": "q_abc123", "promptText": "Which analytics platforms support warehouse exports?", "provider": "Example provider", "model": "example-model", "text": "Acme Analytics and Rival Labs both document warehouse exports...", "hasSelfMention": true, "selfMentionPosition": 3, "hasSelfSource": false, "competitorsCount": 5, "sourcesCount": 3, "sentiment": 82, "createdAt": "2026-07-19T10:00:00.000Z" } ``` Unique answer identifier ID of the associated prompt Text of the associated prompt Provider display name Model display name Full response text (or preview in list endpoints) Whether your brand is mentioned Position in competitor list when mentioned (null if not mentioned) Whether your domain is cited as a source Number of competitors mentioned in this answer Number of sources cited in this answer Sentiment score for the SELF mention on a 0 to 100 scale, or `null` ISO 8601 timestamp of answer generation *** ## Competitor mention Detailed information about a competitor mention within an answer (returned in answer details). Synthetic example: ```json theme={null} { "name": "Acme Analytics", "position": 3, "relationship": "SELF", "sentiment": 85 } ``` Competitor name as mentioned Position in the response (1 = mentioned first) `SELF`, `DIRECT`, or `INDIRECT`. Answer details return mentions for **all** relationship types. If you're computing Share of Voice, filter to only `SELF` and `DIRECT` mentions. Sentiment score for this mention on a 0 to 100 scale, or `null` *** ## Source citation Detailed information about a source citation within an answer (returned in answer details). Synthetic example: ```json theme={null} { "url": "https://publisher.example/article", "domain": "publisher.example", "position": 1, "isSelf": false } ``` Full URL cited (can be null) Domain name of the source Position in source list (1 = cited first) Whether this is your own domain *** ## Topic A topic (also called keyword) groups related prompts together. Synthetic example: ```json theme={null} { "id": "topic_abc123", "name": "Product Reviews" } ``` Unique topic identifier Topic display name *** ## Tag Tags allow custom categorization of prompts. Synthetic example: ```json theme={null} { "id": "tag_xyz789", "name": "comparison" } ``` Unique tag identifier Tag display name *** ## Evolution data point Used in evolution endpoints to track metrics over time. Synthetic example: ```json theme={null} { "date": "2026-07-01", "mentions": 5, "shareOfVoice": 7.2, "avgPosition": 2.3, "avgSentiment": 76.5 } ``` Date in YYYY-MM-DD format Number of mentions on this date Share of voice percentage on this date Average position on this date Average SELF mention sentiment on this date, or `null` # Error codes Source: https://docs.qwairy.co/developers/errors Handle gateway and resource error shapes returned by API v1. API v1 returns two error shapes depending on where the failure occurs: * **Resource errors** (validation and not-found errors raised inside an endpoint) return a **nested** object: `{ "error": { "code", "message" } }`. Use the `code` for programmatic handling. * **Gateway errors** raised before an endpoint runs return a **flat** object: `{ "error", "message" }`. This includes `401`, `429`, and an authentication-layer `500`. Here `error` is a status label, not a machine-readable code. Always branch on the HTTP status first, then read the body in the shape matching that status. ## HTTP status codes | Code | Status | Description | | ---- | --------------------- | --------------------------------------- | | 200 | OK | Request succeeded | | 400 | Bad Request | Invalid request parameters | | 401 | Unauthorized | Missing or invalid authentication | | 403 | Forbidden | Valid auth but insufficient permissions | | 404 | Not Found | Resource doesn't exist | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Server-side error | ## Error response formats ### Resource errors (nested) Validation and not-found errors raised inside an endpoint return a structured `error` object: ```json theme={null} { "error": { "code": "BRAND_NOT_FOUND", "message": "Brand not found or not accessible" } } ``` Machine-readable error code for programmatic handling Human-readable error description ### Gateway errors (flat) Gateway responses use a flat shape. `error` is a status label, not a code: ```json theme={null} { "error": "Unauthorized", "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qw-api-xxx" } ``` Short status label (e.g. `Unauthorized`, `Too Many Requests`). Human-readable description of the failure. 429 responses additionally include `limit` (`"burst"` or `"daily"`) and `retryAfter` (seconds). ## Error code reference ### Authorization | Code | HTTP Status | Description | | ------------------- | ----------- | ------------------------------ | | `INSUFFICIENT_PLAN` | 403 | Growth plan or higher required | | `FORBIDDEN` | 403 | Access denied | Authentication `401` responses are flat and do not contain these resource codes. ### Not found | Code | HTTP Status | Description | | ----------------------- | ----------- | ------------------------------------- | | `BRAND_NOT_FOUND` | 404 | Brand not found or not accessible | | `COMPETITOR_NOT_FOUND` | 404 | Competitor not found | | `SOURCE_NOT_FOUND` | 404 | Source not found | | `PROMPT_NOT_FOUND` | 404 | Prompt not found | | `ANSWER_NOT_FOUND` | 404 | Answer not found | | `KEYWORD_NOT_FOUND` | 404 | Topic not found | | `TAG_NOT_FOUND` | 404 | Tag not found | | `PITCH_AUDIT_NOT_FOUND` | 404 | Pitch Audit not found or inaccessible | | `RESOURCE_NOT_FOUND` | 404 | Generic resource not found | | `ENDPOINT_NOT_FOUND` | 404 | API endpoint does not exist | ### Bad request | Code | HTTP Status | Description | | -------------------- | ----------- | -------------------------- | | `INVALID_REQUEST` | 400 | Invalid request format | | `INVALID_PARAMETER` | 400 | Invalid parameter value | | `MISSING_PARAMETER` | 400 | Missing required parameter | | `INVALID_DATE_RANGE` | 400 | Invalid date range | | `INVALID_SORT_FIELD` | 400 | Invalid sort field | ### Rate limiting Gateway `429` responses are flat. Use the HTTP status, `limit`, `retryAfter`, and `Retry-After` header instead of expecting a nested error code. ### Server errors | Code | HTTP Status | Description | | ---------------- | ----------- | --------------------- | | `INTERNAL_ERROR` | 500 | Internal server error | ## Common errors ### Authentication errors (401) Authentication failures use the **flat** shape: ```json theme={null} // Missing or malformed Authorization header { "error": "Unauthorized", "message": "Missing or invalid Authorization header. Use: Authorization: Bearer qw-api-xxx" } // Invalid token, revoked token, or plan below Growth { "error": "Unauthorized", "message": "Invalid API token or insufficient subscription plan (Growth+ required)" } ``` Check the `Bearer` prefix, token status, team status, and active plan. ### Permission errors (403) ```json theme={null} // Team subscription doesn't include API access { "error": { "code": "INSUFFICIENT_PLAN", "message": "Growth plan or higher required" } } ``` Verify the resource-specific permission or plan requirement. ### Not-found errors (404) ```json theme={null} // Brand doesn't exist { "error": { "code": "BRAND_NOT_FOUND", "message": "Brand not found or not accessible" } } // Competitor doesn't exist { "error": { "code": "COMPETITOR_NOT_FOUND", "message": "Competitor not found" } } ``` Check that the resource ID belongs to the team associated with the token. ### Rate-limit errors (429) Rate-limit responses use the **flat** shape and add `limit` and `retryAfter`: ```json theme={null} { "error": "Too Many Requests", "limit": "burst", "message": "Per-minute rate limit exceeded. Please try again in 45 seconds.", "retryAfter": 45 } ``` `limit` is `"burst"` (per-minute) or `"daily"`. Honor the `Retry-After` header before retrying. See [Rate limits](/developers/rate-limits). ### Server errors (500) ```json theme={null} { "error": { "code": "INTERNAL_ERROR", "message": "An unexpected error occurred" } } ``` Retry transient failures with bounded backoff. If the error persists, contact support with the endpoint and request timestamp. ## Handle errors ### JavaScript/TypeScript example ```typescript theme={null} async function callQwairyAPI(endpoint: string) { const response = await fetch(`https://www.qwairy.co/api/v1${endpoint}`, { headers: { 'Authorization': `Bearer ${process.env.QWAIRY_API_TOKEN}`, }, }); if (!response.ok) { const data = await response.json().catch(() => ({})); // Gateway errors use the flat shape: { error, message } if (response.status === 401) { throw new Error(`Authentication failed: ${data.message ?? data.error}`); } if (response.status === 429) { const retryAfter = response.headers.get('Retry-After') ?? data.retryAfter; throw new Error(`Rate limited. Retry after ${retryAfter} second(s).`); } // Resource errors use the nested shape: { error: { code, message } } const nestedError = typeof data.error === 'object' ? data.error : null; const code = nestedError?.code ?? `HTTP_${response.status}`; const message = nestedError?.message ?? data.message ?? data.error ?? `HTTP ${response.status}`; switch (code) { case 'INSUFFICIENT_PLAN': case 'FORBIDDEN': throw new Error(`Permission denied: ${message}`); case 'BRAND_NOT_FOUND': case 'COMPETITOR_NOT_FOUND': case 'SOURCE_NOT_FOUND': case 'PROMPT_NOT_FOUND': case 'ANSWER_NOT_FOUND': throw new Error(`Resource not found: ${message}`); default: throw new Error(`API error [${code}]: ${message}`); } } return response.json(); } ``` ### Python example ```python theme={null} import requests import os def call_qwairy_api(endpoint): response = requests.get( f"https://www.qwairy.co/api/v1{endpoint}", headers={"Authorization": f"Bearer {os.environ['QWAIRY_API_TOKEN']}"} ) if not response.ok: body = response.json() if response.content else {} # Gateway errors use the flat shape: { error, message } if response.status_code == 401: raise Exception(f"Authentication failed: {body.get('message', body.get('error'))}") if response.status_code == 429: retry_after = response.headers.get('Retry-After', body.get('retryAfter')) raise Exception(f"Rate limited. Retry after {retry_after} second(s).") # Resource errors use the nested shape: { error: { code, message } } error = body.get('error', {}) if isinstance(body.get('error'), dict) else {} code = error.get('code', 'UNKNOWN') message = error.get('message', body.get('message', 'Unknown error')) if code in ['INSUFFICIENT_PLAN', 'FORBIDDEN']: raise Exception(f"Permission denied: {message}") elif code.endswith('_NOT_FOUND'): raise Exception(f"Not found: {message}") else: raise Exception(f"API error [{code}]: {message}") return response.json() ``` ## Get help If you encounter persistent errors or unexpected behavior, contact us at [team@qwairy.co](mailto:team@qwairy.co) with: * The endpoint you're calling * The full error response * Your request headers (without the token) * The timestamp of the request # Competitive analysis Source: https://docs.qwairy.co/developers/guides/competitive-analysis Compare SELF and DIRECT observations, then measure one competitor across a consistent API scope. Use the competitor endpoints to compare observed mentions and Share of Voice within one monitored answer set. This guide uses the reusable client from the [Quick start](/developers/guides/index#api-client). ## Build the comparison Request SELF and DIRECT relationships for the same period. Do not treat other relationship types as direct competitors unless your analysis explicitly requires them. ```javascript JavaScript theme={null} async function buildCompetitiveLandscape(client, brandId, period = 30) { const data = await client.getCompetitors(brandId, { period, relationship: 'SELF,DIRECT', limit: 100, sort: 'shareOfVoice', order: 'desc', }); const self = data.competitors.find( (item) => item.relationship === 'SELF' ); if (!self) throw new Error('SELF brand is absent from the selected scope'); const competitors = data.competitors .filter((item) => item.relationship === 'DIRECT') .map((item) => ({ id: item.id, name: item.name, mentions: item.totalMentions, shareOfVoice: item.shareOfVoice, shareOfVoiceGap: Number( (item.shareOfVoice - self.shareOfVoice).toFixed(2) ), averagePosition: item.avgPosition, sentiment: item.avgSentiment, })); return { period, self: { id: self.id, name: self.name, mentions: self.totalMentions, shareOfVoice: self.shareOfVoice, }, competitors, }; } ``` ```python Python theme={null} def build_competitive_landscape(client, brand_id: str, period: int = 30) -> dict: data = client.get_competitors( brand_id, period=period, limit=100, sort='shareOfVoice', order='desc', ) self_brand = next( (item for item in data['competitors'] if item['relationship'] == 'SELF'), None, ) if self_brand is None: raise ValueError('SELF brand is absent from the selected scope') competitors = [ { 'id': item['id'], 'name': item['name'], 'mentions': item['totalMentions'], 'share_of_voice': item['shareOfVoice'], 'share_of_voice_gap': round( item['shareOfVoice'] - self_brand['shareOfVoice'], 2 ), 'average_position': item['avgPosition'], 'sentiment': item['avgSentiment'], } for item in data['competitors'] if item['relationship'] == 'DIRECT' ] return { 'period': period, 'self': { 'id': self_brand['id'], 'name': self_brand['name'], 'mentions': self_brand['totalMentions'], 'share_of_voice': self_brand['shareOfVoice'], }, 'competitors': competitors, } ``` The Python wrapper does not expose the `relationship` query parameter. It therefore filters the response after retrieval. ## Track one competitor over time Report the observed difference without assigning a universal significance threshold. If your organization uses a materiality threshold, pass it as an explicit business rule and document its owner. ```javascript JavaScript theme={null} async function compareCompetitorEndpoints( client, brandId, competitorId, period = 30 ) { const result = await client.getCompetitorEvolution( brandId, competitorId, { period } ); const points = result.evolution; if (points.length < 2) { return { status: 'insufficient_data', points }; } const first = points[0]; const last = points.at(-1); const shareOfVoiceChange = Number( (last.shareOfVoice - first.shareOfVoice).toFixed(2) ); return { status: 'observed', competitor: result.competitor, window: { start: first.date, end: last.date }, shareOfVoice: { start: first.shareOfVoice, end: last.shareOfVoice, changePoints: shareOfVoiceChange, direction: shareOfVoiceChange > 0 ? 'increased' : shareOfVoiceChange < 0 ? 'decreased' : 'unchanged', }, mentionChange: last.mentions - first.mentions, points, }; } ``` ```python Python theme={null} def compare_competitor_endpoints( client, brand_id: str, competitor_id: str, period: int = 30 ) -> dict: result = client.get_competitor_evolution( brand_id, competitor_id, period=period ) points = result['evolution'] if len(points) < 2: return {'status': 'insufficient_data', 'points': points} first = points[0] last = points[-1] share_of_voice_change = round( last['shareOfVoice'] - first['shareOfVoice'], 2 ) direction = ( 'increased' if share_of_voice_change > 0 else 'decreased' if share_of_voice_change < 0 else 'unchanged' ) return { 'status': 'observed', 'competitor': result['competitor'], 'window': {'start': first['date'], 'end': last['date']}, 'share_of_voice': { 'start': first['shareOfVoice'], 'end': last['shareOfVoice'], 'change_points': share_of_voice_change, 'direction': direction, }, 'mention_change': last['mentions'] - first['mentions'], 'points': points, } ``` ## Interpret the result * A positive gap means the competitor has a higher Share of Voice in the selected API scope. It is not market share. * Mention counts, average position, sentiment, and citations answer different questions. Do not collapse them into a single causal explanation. * Compare evolution only when prompt, provider, topic, tag, and date scopes are equivalent. * A launch, campaign, or content change near the same date is a hypothesis to investigate, not a cause established by this endpoint. * Inspect stored answers and sources before choosing an action. ## Related pages * [List competitors](/developers/endpoints/competitors/list) * [Get competitor](/developers/endpoints/competitors/get) * [Competitor evolution](/developers/endpoints/competitors/evolution) * [Get performance snapshot](/developers/endpoints/performance) * [Custom dashboard](/developers/guides/custom-dashboard) # Custom dashboard Source: https://docs.qwairy.co/developers/guides/custom-dashboard Build a dashboard from performance, competitor, source, answer, and query fan-out data. Build a dashboard that keeps each API surface's metric scope visible. This guide uses the reusable client from the [Quick start](/developers/guides/index#api-client). ## Fetch the overview Request performance, competitors, and source domains with the same period. Keep the returned methodology with the displayed scores so readers can see the response and provider scope. ```javascript JavaScript theme={null} async function fetchDashboardData(client, brandId, period = 30) { const [performance, competitorData, sourceData] = await Promise.all([ client.getPerformance(brandId, { period }), client.getCompetitors(brandId, { period, relationship: 'SELF,DIRECT', limit: 100, sort: 'shareOfVoice', order: 'desc', }), client.getSourceDomains(brandId, { period, limit: 20, sort: 'mentions', order: 'desc', }), ]); const self = competitorData.competitors.find( (item) => item.relationship === 'SELF' ); const direct = competitorData.competitors.filter( (item) => item.relationship === 'DIRECT' ); return { scores: performance.scores, methodology: performance.methodology, self: self ?? null, competitors: direct.map((item) => ({ id: item.id, name: item.name, mentions: item.totalMentions, shareOfVoice: item.shareOfVoice, shareOfVoiceGap: self ? Number((item.shareOfVoice - self.shareOfVoice).toFixed(2)) : null, })), sources: sourceData.sources.map((item) => ({ domain: item.domain, citations: item.totalMentions, citationShare: item.rate, isSelf: item.isSelf, })), }; } ``` ```python Python theme={null} def fetch_dashboard_data(client, brand_id: str, period: int = 30) -> dict: performance = client.get_performance(brand_id, period=period) competitor_data = client.get_competitors( brand_id, period=period, limit=100, sort='shareOfVoice', order='desc', ) source_data = client.get_source_domains( brand_id, period=period, limit=20, sort='mentions', order='desc', ) self_brand = next( (item for item in competitor_data['competitors'] if item['relationship'] == 'SELF'), None, ) direct = [ item for item in competitor_data['competitors'] if item['relationship'] == 'DIRECT' ] return { 'scores': performance['scores'], 'methodology': performance['methodology'], 'self': self_brand, 'competitors': [ { 'id': item['id'], 'name': item['name'], 'mentions': item['totalMentions'], 'share_of_voice': item['shareOfVoice'], 'share_of_voice_gap': round( item['shareOfVoice'] - self_brand['shareOfVoice'], 2 ) if self_brand else None, } for item in direct ], 'sources': [ { 'domain': item['domain'], 'citations': item['totalMentions'], 'citation_share': item['rate'], 'is_self': item['isSelf'], } for item in source_data['sources'] ], } ``` The Python client in the Quick start does not expose the `relationship` argument. Filter the returned list as shown, or extend the wrapper to pass that query parameter. ## Label the metrics by surface Do not replace these definitions with similarly named dashboard, export, MCP, or Looker Studio formulas. | Field | REST performance scope | | --------------------- | ----------------------------------------------------------------------- | | `scores.mentionRate` | SELF-mentioned answers divided by answers with a SELF or DIRECT mention | | `scores.sourceRate` | SELF-citing answers divided by answers with a SELF or DIRECT citation | | `scores.coverage` | SELF-mentioned answers divided by all answers in the selected API scope | | `scores.shareOfVoice` | SELF mention occurrences divided by SELF plus DIRECT occurrences | | `scores.sentiment` | Average SELF sentiment on the `0` to `100` scale | Show `methodology.responsesTotal`, `methodology.promptsCount`, and `methodology.providers` near the score cards. A change in those values can make two periods non-comparable. ## Add response diagnostics The Answers endpoint applies `hasSelfSource=true`, but currently ignores the inverse value `false`. Request SELF-mentioned answers and filter the returned `hasSelfSource` flag in your client. ```javascript JavaScript theme={null} async function fetchDiagnostics(client, brandId, period = 30) { const [answerData, searchData] = await Promise.all([ client.getAnswers(brandId, { period, hasSelfMention: true, limit: 100, sort: 'createdAt', order: 'desc', }), client.getSearch(brandId, { period, limit: 20, sort: 'occurrences', order: 'desc', }), ]); return { sampledMentionWithoutCitation: answerData.answers .filter((answer) => !answer.hasSelfSource) .map((answer) => ({ prompt: answer.promptText, provider: answer.provider, position: answer.selfMentionPosition, })), queryFanOut: searchData.searches.map((record) => ({ query: record.query, occurrences: record.occurrences, prompts: record.prompts.map((prompt) => prompt.text), models: record.models, })), }; } ``` The answer diagnostic is limited to the requested page. Its length is not the total number of matching answers. Paginate before calculating a complete count. ## Verify the dashboard 1. Display the selected period and API methodology. 2. Confirm that SELF and DIRECT relationships are not mixed with other competitor types. 3. Treat a Share of Voice gap as a difference in the selected answer set, not market share. 4. Keep answer citations, query fan-out, and competitor mentions as separate observations. 5. Compare equivalent filters and prompt sets before labeling a trend. ## Related pages * [Get performance snapshot](/developers/endpoints/performance) * [List competitors](/developers/endpoints/competitors/list) * [List source domains](/developers/endpoints/source-domains/list) * [List answers](/developers/endpoints/answers/list) * [List query fan-out records](/developers/endpoints/search) # Data export Source: https://docs.qwairy.co/developers/guides/data-export Export GEO data to CSV, JSON, and BI tools like BigQuery or Snowflake. Export your Qwairy data for analysis in spreadsheets, databases, or BI platforms. This guide uses the API client from the [Guides index](/developers/guides/index#api-client). Copy it to your project first. ## What you'll build Export pipelines for: * **CSV files**: For Excel, Google Sheets * **JSON files**: For data pipelines * **BigQuery**: For Google Cloud analytics * **Snowflake**: For enterprise data warehousing *** ## Fetch all data First, gather data from all endpoints. ```javascript JavaScript theme={null} async function fetchAllData(client, brandId, period = 30) { const [performance, competitors, sources] = await Promise.all([ client.getPerformance(brandId, { period }), client.getCompetitors(brandId, { period, limit: 100 }), client.getSourceDomains(brandId, { period, limit: 100 }), ]); return { exportedAt: new Date().toISOString(), brandId, period, performance: { scores: performance.scores, methodology: performance.methodology, }, competitors: competitors.competitors.map(c => ({ id: c.id, name: c.name, relationship: c.relationship, shareOfVoice: c.shareOfVoice, totalMentions: c.totalMentions, avgPosition: c.avgPosition, avgSentiment: c.avgSentiment, })), sources: sources.sources.map(s => ({ id: s.id, domain: s.domain, type: s.type, isSelf: s.isSelf, totalMentions: s.totalMentions, rate: s.rate, avgPosition: s.avgPosition, })), }; } ``` ```python Python theme={null} def fetch_all_data(client, brand_id: str, period: int = 30) -> dict: """Fetch all data for export.""" from concurrent.futures import ThreadPoolExecutor from datetime import datetime with ThreadPoolExecutor(max_workers=3) as executor: perf_future = executor.submit(client.get_performance, brand_id, period=period) comp_future = executor.submit(client.get_competitors, brand_id, period=period, limit=100) src_future = executor.submit(client.get_source_domains, brand_id, period=period, limit=100) performance = perf_future.result() competitors = comp_future.result() sources = src_future.result() return { 'exported_at': datetime.now().isoformat(), 'brand_id': brand_id, 'period': period, 'performance': { 'scores': performance['scores'], 'methodology': performance['methodology'], }, 'competitors': [ { 'id': c['id'], 'name': c['name'], 'relationship': c['relationship'], 'share_of_voice': c['shareOfVoice'], 'total_mentions': c['totalMentions'], 'avg_position': c['avgPosition'], 'avg_sentiment': c['avgSentiment'], } for c in competitors['competitors'] ], 'sources': [ { 'id': s['id'], 'domain': s['domain'], 'type': s['type'], 'is_self': s['isSelf'], 'total_mentions': s['totalMentions'], 'rate': s['rate'], 'avg_position': s['avgPosition'], } for s in sources['sources'] ], } ``` *** ## Export to CSV ```javascript JavaScript theme={null} const fs = require('fs'); function exportToCSV(data, outputDir = './exports') { if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const timestamp = data.exportedAt.split('T')[0]; // Competitors CSV const competitorHeaders = ['id', 'name', 'relationship', 'shareOfVoice', 'totalMentions', 'avgPosition', 'avgSentiment']; const competitorRows = data.competitors.map(c => competitorHeaders.map(h => c[h]).join(',')); fs.writeFileSync( `${outputDir}/competitors_${timestamp}.csv`, [competitorHeaders.join(','), ...competitorRows].join('\n') ); // Sources CSV const sourceHeaders = ['id', 'domain', 'type', 'isSelf', 'totalMentions', 'rate', 'avgPosition']; const sourceRows = data.sources.map(s => sourceHeaders.map(h => s[h]).join(',')); fs.writeFileSync( `${outputDir}/sources_${timestamp}.csv`, [sourceHeaders.join(','), ...sourceRows].join('\n') ); // Performance CSV (single row) const perfHeaders = ['mentionRate', 'sourceRate', 'coverage', 'shareOfVoice', 'sentiment']; const perfValues = [ data.performance.scores.mentionRate, data.performance.scores.sourceRate, data.performance.scores.coverage, data.performance.scores.shareOfVoice, data.performance.scores.sentiment, ]; fs.writeFileSync( `${outputDir}/performance_${timestamp}.csv`, [perfHeaders.join(','), perfValues.join(',')].join('\n') ); return { files: [ `${outputDir}/competitors_${timestamp}.csv`, `${outputDir}/sources_${timestamp}.csv`, `${outputDir}/performance_${timestamp}.csv`, ], }; } ``` ```python Python theme={null} import csv import os def export_to_csv(data: dict, output_dir: str = './exports') -> dict: """Export data to CSV files.""" os.makedirs(output_dir, exist_ok=True) timestamp = data['exported_at'].split('T')[0] files = [] # Competitors CSV comp_file = f'{output_dir}/competitors_{timestamp}.csv' with open(comp_file, 'w', newline='') as f: if data['competitors']: writer = csv.DictWriter(f, fieldnames=data['competitors'][0].keys()) writer.writeheader() writer.writerows(data['competitors']) files.append(comp_file) # Sources CSV src_file = f'{output_dir}/sources_{timestamp}.csv' with open(src_file, 'w', newline='') as f: if data['sources']: writer = csv.DictWriter(f, fieldnames=data['sources'][0].keys()) writer.writeheader() writer.writerows(data['sources']) files.append(src_file) # Performance CSV perf_file = f'{output_dir}/performance_{timestamp}.csv' scores = data['performance']['scores'] with open(perf_file, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=['mention_rate', 'source_rate', 'coverage', 'share_of_voice', 'sentiment']) writer.writeheader() writer.writerow({ 'mention_rate': scores['mentionRate'], 'source_rate': scores['sourceRate'], 'coverage': scores['coverage'], 'share_of_voice': scores['shareOfVoice'], 'sentiment': scores['sentiment'], }) files.append(perf_file) return {'files': files} ``` *** ## Export to JSON ```javascript JavaScript theme={null} const fs = require('fs'); function exportToJSON(data, outputDir = './exports') { if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const timestamp = data.exportedAt.split('T')[0]; const filepath = `${outputDir}/qwairy_export_${timestamp}.json`; fs.writeFileSync(filepath, JSON.stringify(data, null, 2)); return { file: filepath, size: fs.statSync(filepath).size }; } ``` ```python Python theme={null} import json import os def export_to_json(data: dict, output_dir: str = './exports') -> dict: """Export data to JSON file.""" os.makedirs(output_dir, exist_ok=True) timestamp = data['exported_at'].split('T')[0] filepath = f'{output_dir}/qwairy_export_{timestamp}.json' with open(filepath, 'w') as f: json.dump(data, f, indent=2) return {'file': filepath, 'size': os.path.getsize(filepath)} ``` *** ## Database schemas Create these tables before exporting to BigQuery or Snowflake. ```sql BigQuery theme={null} -- Create dataset CREATE SCHEMA IF NOT EXISTS qwairy; -- Competitors table CREATE TABLE IF NOT EXISTS qwairy.competitors ( id STRING NOT NULL, name STRING NOT NULL, relationship STRING, share_of_voice FLOAT64, total_mentions INT64, avg_position FLOAT64, avg_sentiment FLOAT64, exported_at TIMESTAMP NOT NULL, brand_id STRING NOT NULL ); -- Sources table CREATE TABLE IF NOT EXISTS qwairy.sources ( id STRING NOT NULL, domain STRING NOT NULL, type STRING, is_self BOOL, total_mentions INT64, rate FLOAT64, avg_position FLOAT64, exported_at TIMESTAMP NOT NULL, brand_id STRING NOT NULL ); -- Performance table CREATE TABLE IF NOT EXISTS qwairy.performance ( mentionRate FLOAT64, sourceRate FLOAT64, coverage FLOAT64, shareOfVoice FLOAT64, sentiment FLOAT64, methodology STRING, exported_at TIMESTAMP NOT NULL, brand_id STRING NOT NULL ); ``` ```sql Snowflake theme={null} -- Create schema CREATE SCHEMA IF NOT EXISTS QWAIRY; -- Competitors table CREATE TABLE IF NOT EXISTS QWAIRY.COMPETITORS ( ID VARCHAR(50) NOT NULL, NAME VARCHAR(255) NOT NULL, RELATIONSHIP VARCHAR(50), SHARE_OF_VOICE FLOAT, TOTAL_MENTIONS INTEGER, AVG_POSITION FLOAT, AVG_SENTIMENT FLOAT, EXPORTED_AT TIMESTAMP_NTZ NOT NULL, BRAND_ID VARCHAR(50) NOT NULL ); -- Sources table CREATE TABLE IF NOT EXISTS QWAIRY.SOURCES ( ID VARCHAR(50) NOT NULL, DOMAIN VARCHAR(255) NOT NULL, TYPE VARCHAR(50), IS_SELF BOOLEAN, TOTAL_MENTIONS INTEGER, RATE FLOAT, AVG_POSITION FLOAT, EXPORTED_AT TIMESTAMP_NTZ NOT NULL, BRAND_ID VARCHAR(50) NOT NULL ); -- Performance table CREATE TABLE IF NOT EXISTS QWAIRY.PERFORMANCE ( MENTION_RATE FLOAT, SOURCE_RATE FLOAT, COVERAGE FLOAT, SHARE_OF_VOICE FLOAT, SENTIMENT FLOAT, METHODOLOGY VARCHAR(5000), EXPORTED_AT TIMESTAMP_NTZ NOT NULL, BRAND_ID VARCHAR(50) NOT NULL ); ``` *** ## Export to BigQuery ```javascript JavaScript theme={null} const { BigQuery } = require('@google-cloud/bigquery'); async function exportToBigQuery(data, datasetId = 'qwairy', projectId = process.env.GCP_PROJECT_ID) { const bigquery = new BigQuery({ projectId }); // Create dataset if not exists const [datasets] = await bigquery.getDatasets(); if (!datasets.find(d => d.id === datasetId)) { await bigquery.createDataset(datasetId); } const dataset = bigquery.dataset(datasetId); // Insert competitors await dataset.table('competitors').insert( data.competitors.map(c => ({ ...c, exportedAt: data.exportedAt, brandId: data.brandId, })) ); // Insert sources await dataset.table('sources').insert( data.sources.map(s => ({ ...s, exportedAt: data.exportedAt, brandId: data.brandId, })) ); // Insert performance await dataset.table('performance').insert([{ ...data.performance.scores, methodology: JSON.stringify(data.performance.methodology), exportedAt: data.exportedAt, brandId: data.brandId, }]); return { dataset: datasetId, tables: ['competitors', 'sources', 'performance'] }; } ``` ```python Python theme={null} from google.cloud import bigquery def export_to_bigquery(data: dict, dataset_id: str = 'qwairy', project_id: str = None) -> dict: """Export data to BigQuery.""" import os import json project_id = project_id or os.environ.get('GCP_PROJECT_ID') client = bigquery.Client(project=project_id) # Create dataset if not exists dataset_ref = client.dataset(dataset_id) try: client.get_dataset(dataset_ref) except Exception: client.create_dataset(dataset_ref) # Insert competitors competitors_table = f'{project_id}.{dataset_id}.competitors' rows = [ {**c, 'exported_at': data['exported_at'], 'brand_id': data['brand_id']} for c in data['competitors'] ] if rows: client.insert_rows_json(competitors_table, rows) # Insert sources sources_table = f'{project_id}.{dataset_id}.sources' rows = [ {**s, 'exported_at': data['exported_at'], 'brand_id': data['brand_id']} for s in data['sources'] ] if rows: client.insert_rows_json(sources_table, rows) # Insert performance perf_table = f'{project_id}.{dataset_id}.performance' perf_row = { **data['performance']['scores'], 'methodology': json.dumps(data['performance']['methodology']), 'exported_at': data['exported_at'], 'brand_id': data['brand_id'], } client.insert_rows_json(perf_table, [perf_row]) return {'dataset': dataset_id, 'tables': ['competitors', 'sources', 'performance']} ``` *** ## Export to Snowflake ```javascript JavaScript theme={null} const snowflake = require('snowflake-sdk'); async function exportToSnowflake(data, config) { const connection = snowflake.createConnection({ account: config.account, username: config.username, password: config.password, warehouse: config.warehouse, database: config.database, schema: config.schema || 'QWAIRY', }); await new Promise((resolve, reject) => { connection.connect((err) => err ? reject(err) : resolve()); }); const execute = (sql, binds = []) => new Promise((resolve, reject) => { connection.execute({ sqlText: sql, binds, complete: (err, stmt, rows) => err ? reject(err) : resolve(rows) }); }); // Insert competitors for (const c of data.competitors) { await execute( `INSERT INTO competitors (id, name, relationship, share_of_voice, total_mentions, avg_position, avg_sentiment, exported_at, brand_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [c.id, c.name, c.relationship, c.shareOfVoice, c.totalMentions, c.avgPosition, c.avgSentiment, data.exportedAt, data.brandId] ); } // Insert sources for (const s of data.sources) { await execute( `INSERT INTO sources (id, domain, type, is_self, total_mentions, rate, avg_position, exported_at, brand_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [s.id, s.domain, s.type, s.isSelf, s.totalMentions, s.rate, s.avgPosition, data.exportedAt, data.brandId] ); } connection.destroy(); return { schema: config.schema || 'QWAIRY', tables: ['competitors', 'sources'] }; } ``` ```python Python theme={null} import snowflake.connector def export_to_snowflake(data: dict, config: dict) -> dict: """Export data to Snowflake.""" conn = snowflake.connector.connect( account=config['account'], user=config['username'], password=config['password'], warehouse=config['warehouse'], database=config['database'], schema=config.get('schema', 'QWAIRY'), ) cursor = conn.cursor() # Insert competitors for c in data['competitors']: cursor.execute( '''INSERT INTO competitors (id, name, relationship, share_of_voice, total_mentions, avg_position, avg_sentiment, exported_at, brand_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)''', (c['id'], c['name'], c['relationship'], c['share_of_voice'], c['total_mentions'], c['avg_position'], c['avg_sentiment'], data['exported_at'], data['brand_id']) ) # Insert sources for s in data['sources']: cursor.execute( '''INSERT INTO sources (id, domain, type, is_self, total_mentions, rate, avg_position, exported_at, brand_id) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)''', (s['id'], s['domain'], s['type'], s['is_self'], s['total_mentions'], s['rate'], s['avg_position'], data['exported_at'], data['brand_id']) ) conn.commit() cursor.close() conn.close() return {'schema': config.get('schema', 'QWAIRY'), 'tables': ['competitors', 'sources']} ``` *** ## Usage ```javascript JavaScript theme={null} const client = new QwairyClient(process.env.QWAIRY_API_TOKEN); // Fetch data const data = await fetchAllData(client, 'your-brand-id', 30); // Export to files const csvResult = exportToCSV(data); console.log('CSV files:', csvResult.files); const jsonResult = exportToJSON(data); console.log('JSON file:', jsonResult.file, `(${jsonResult.size} bytes)`); // Export to BigQuery (requires @google-cloud/bigquery) // const bqResult = await exportToBigQuery(data); // console.log('BigQuery tables:', bqResult.tables); ``` ```python Python theme={null} client = QwairyClient() # Fetch data data = fetch_all_data(client, 'your-brand-id', period=30) # Export to files csv_result = export_to_csv(data) print(f"CSV files: {csv_result['files']}") json_result = export_to_json(data) print(f"JSON file: {json_result['file']} ({json_result['size']} bytes)") # Export to BigQuery (requires google-cloud-bigquery) # bq_result = export_to_bigquery(data) # print(f"BigQuery tables: {bq_result['tables']}") ``` *** ## Schedule exports Automate daily or weekly exports: | Platform | Configuration | | ------------------ | --------------------------------- | | **Cron** | `0 1 * * *` (daily at 1am) | | **GitHub Actions** | `schedule: cron: '0 1 * * *'` | | **AWS Lambda** | EventBridge scheduled rule | | **Google Cloud** | Cloud Scheduler + Cloud Functions | *** ## Next steps * Create a [custom dashboard](/developers/guides/custom-dashboard) for on-demand monitoring * Set up [weekly reports](/developers/guides/weekly-reports) for stakeholders * Add [competitive analysis](/developers/guides/competitive-analysis) to your exports # Quick start Source: https://docs.qwairy.co/developers/guides/index 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. 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. ## Make your first request Get your brand's performance scores in one command: ```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']}%") ``` **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. Build a branded GEO dashboard with KPIs and competitor rankings Automate week-over-week performance reports Track your position against competitors Export data for BI tools (BigQuery, Snowflake, CSV) *** ## API client Reusable client with error handling for all guides on this site. ```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() ``` 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. ```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; issuesCount: number; optimizationsCount: number } | null; robotsAnalysis: Record | null; llmsAnalysis: Record | null; sitemapAnalysis: Record | null; lastAnalyzedAt: string | null; } ``` *** ## Pagination Fetch all results when you have more than 100 items. ```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)}") ``` *** ## Field reference Mapping between business metrics and API fields: | Business metric | Endpoint | Field | Description | | ------------------------------- | --------------------- | ---------------------------------- | --------------------------------------------------------------------------------------- | | Mention Rate | `/performance` | `scores.mentionRate` | Responses with a SELF mention divided by responses with a SELF or DIRECT mention | | Citation Rate | `/performance` | `scores.sourceRate` | Responses with a SELF citation divided by responses with a SELF or DIRECT citation | | Coverage | `/performance` | `scores.coverage` | Responses with a SELF mention divided by all responses in the selected scope | | Share of Voice | `/performance` | `scores.shareOfVoice` | SELF mention occurrences divided by SELF and DIRECT occurrences | | Share of Voice (per competitor) | `/competitors` | `shareOfVoice` | Competitor occurrence share in the endpoint's selected relationship set | | Share of Citations | `/source-domains` | `rate` | Domain citation occurrences divided by all citation occurrences | | Sentiment | `/performance` | `scores.sentiment` | Average SELF sentiment score on the `0` to `100` scale | | Average Position | `/competitors` | `avgPosition` | Average rank in AI responses (1 = first) | | Mentioned but not cited | `/answers` | `hasSelfMention` + `hasSelfSource` | Request `hasSelfMention=true`, then client-filter rows where `hasSelfSource` is `false` | | Query fan-out | `/search` | `query` | Retained web queries associated with stored answers | | Perception scores | `/perception` | `current.scores` | Sentiment, alignment, consistency, factual alignment (0-100) | | Content articles | `/content` | `articles` | Content Studio articles (Markdown via `/content/{id}`) | | Site Readiness Score | `/technical-analysis` | `aiReadiness.score` | Stored robots.txt, llms.txt, and sitemap.xml 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. # Weekly reports Source: https://docs.qwairy.co/developers/guides/weekly-reports Compare two complete, non-overlapping seven-day API windows with explicit methodology. Build a repeatable week-over-week report from two complete date windows that this guide defines in UTC. This guide uses the reusable client from the [Quick start](/developers/guides/index#api-client). ## Define comparable windows For this report, construct inclusive UTC calendar-day windows. End the current window yesterday, then subtract six days. End the previous window one day before the current window begins. ```javascript JavaScript theme={null} const DAY_MS = 24 * 60 * 60 * 1000; function isoDate(timestamp) { return new Date(timestamp).toISOString().slice(0, 10); } function completeSevenDayWindows(now = new Date()) { const todayUtc = Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() ); const currentEnd = todayUtc - DAY_MS; const currentStart = currentEnd - 6 * DAY_MS; const previousEnd = currentStart - DAY_MS; const previousStart = previousEnd - 6 * DAY_MS; return { current: { startDate: isoDate(currentStart), endDate: isoDate(currentEnd) }, previous: { startDate: isoDate(previousStart), endDate: isoDate(previousEnd) }, }; } ``` ```python Python theme={null} from datetime import datetime, timedelta, timezone def complete_seven_day_windows() -> dict: today_utc = datetime.now(timezone.utc).date() current_end = today_utc - timedelta(days=1) current_start = current_end - timedelta(days=6) previous_end = current_start - timedelta(days=1) previous_start = previous_end - timedelta(days=6) return { 'current': { 'start_date': current_start.isoformat(), 'end_date': current_end.isoformat(), }, 'previous': { 'start_date': previous_start.isoformat(), 'end_date': previous_end.isoformat(), }, } ``` This avoids a one-day overlap and excludes the partial current UTC day. ## Compare score points Mention Rate, Source Rate, Coverage, and Share of Voice are percentage values. Sentiment is a score from `0` to `100`. Report an absolute point difference for all five rather than a relative percentage change, which becomes misleading near zero and is not meaningful for sentiment. ```javascript JavaScript theme={null} async function generateWeeklyReport(client, brandId, now = new Date()) { const windows = completeSevenDayWindows(now); const [current, previous] = await Promise.all([ client.getPerformance(brandId, windows.current), client.getPerformance(brandId, windows.previous), ]); const metricKeys = [ ['Mention Rate', 'mentionRate'], ['Source Rate', 'sourceRate'], ['Coverage', 'coverage'], ['Share of Voice', 'shareOfVoice'], ['Sentiment', 'sentiment'], ]; const metrics = metricKeys.map(([name, key]) => { const currentValue = current.scores[key]; const previousValue = previous.scores[key]; const changePoints = Number((currentValue - previousValue).toFixed(2)); return { name, current: currentValue, previous: previousValue, changePoints, direction: changePoints > 0 ? 'increased' : changePoints < 0 ? 'decreased' : 'unchanged', }; }); return { generatedAt: now.toISOString(), windows, metrics, methodology: { current: current.methodology, previous: previous.methodology, }, }; } ``` ```python Python theme={null} def generate_weekly_report(client, brand_id: str) -> dict: windows = complete_seven_day_windows() current = client.get_performance( brand_id, start_date=windows['current']['start_date'], end_date=windows['current']['end_date'], ) previous = client.get_performance( brand_id, start_date=windows['previous']['start_date'], end_date=windows['previous']['end_date'], ) metric_keys = [ ('Mention Rate', 'mentionRate'), ('Source Rate', 'sourceRate'), ('Coverage', 'coverage'), ('Share of Voice', 'shareOfVoice'), ('Sentiment', 'sentiment'), ] metrics = [] for name, key in metric_keys: change_points = round( current['scores'][key] - previous['scores'][key], 2 ) direction = ( 'increased' if change_points > 0 else 'decreased' if change_points < 0 else 'unchanged' ) metrics.append({ 'name': name, 'current': current['scores'][key], 'previous': previous['scores'][key], 'change_points': change_points, 'direction': direction, }) return { 'generated_at': datetime.now(timezone.utc).isoformat(), 'windows': windows, 'metrics': metrics, 'methodology': { 'current': current['methodology'], 'previous': previous['methodology'], }, } ``` ## Synthetic output ```json theme={null} { "windows": { "current": { "startDate": "2026-07-14", "endDate": "2026-07-20" }, "previous": { "startDate": "2026-07-07", "endDate": "2026-07-13" } }, "metrics": [ { "name": "Mention Rate", "current": 45.2, "previous": 42.1, "changePoints": 3.1, "direction": "increased" }, { "name": "Sentiment", "current": 12.4, "previous": 10.1, "changePoints": 2.3, "direction": "increased" } ] } ``` These labels describe the selected API windows only. They do not establish business impact or cause. ## Add diagnostics carefully * Use [List answers](/developers/endpoints/answers/list) to inspect response-level changes. The endpoint does not apply `hasSelfSource=false`; filter the returned flag in your client and paginate before calculating a total. * Use [List query fan-out records](/developers/endpoints/search) for aggregated query text. Each record exposes `prompts[]` and `models[]`, not singular `prompt` or `provider` fields. * Keep citation, mention, query fan-out, and competitor movements as separate observations. ## Validate the comparison 1. Confirm the windows contain seven days each and do not overlap. 2. Compare `methodology.promptsCount`, `responsesTotal`, and `providers` between periods. 3. Explain prompt, provider, topic, or tag changes before interpreting a score delta. 4. Label every value with its REST performance formula. 5. Send the report only to destinations approved for the data it contains. ## Related pages * [Get performance snapshot](/developers/endpoints/performance) * [Custom dashboard](/developers/guides/custom-dashboard) * [Competitive analysis](/developers/guides/competitive-analysis) * [Data export](/developers/guides/data-export) # Introduction Source: https://docs.qwairy.co/developers/introduction Start with Qwairy API v1 authentication, response shapes, pagination, and core endpoints. Use API v1 to read the monitoring data available to your team, including answers, citations, competitors, and aggregate metrics. API v1 requires an active **Growth** plan or above. [Review your subscription](https://www.qwairy.co/dashboard/team/billing) if authentication reports an insufficient plan. ## Base URL All API endpoints are relative to: ``` https://www.qwairy.co/api/v1 ``` ## Available endpoints The API exposes these monitoring resources: | Resource | Description | | ----------------------------------------------------------- | ---------------------------------------------------------- | | [Brands](/developers/endpoints/brands/list) | List all brands in your account | | [Performance](/developers/endpoints/performance) | Read response-level visibility metrics | | [Competitors](/developers/endpoints/competitors/list) | Track competitor mentions and share of voice | | [Source Domains](/developers/endpoints/source-domains/list) | Monitor domains cited by AI platforms | | [Source URLs](/developers/endpoints/source-urls) | Track individual pages cited in AI responses | | [Prompts](/developers/endpoints/prompts/list) | Access monitored prompts | | [Answers](/developers/endpoints/answers/list) | Retrieve stored AI answers | | [Search](/developers/endpoints/search) | List retained query fan-out associated with stored answers | ## Quick start Go to **Team Management** → **API Access** and create a new API token. ```bash theme={null} curl -X GET "https://www.qwairy.co/api/v1/brands" \ -H "Authorization: Bearer qw-api-your-token-here" ``` Use the brand ID to access performance metrics, competitors, sources, and more. ## Response format Responses are JSON. List endpoints generally return a resource array and a `pagination` object: ```json theme={null} { "success": true, "pagination": { "total": 156, "count": 10, "limit": 10, "offset": 0 }, "prompts": [ ... ] } ``` Most list endpoints include a `pagination` object with: * `total`: Total items matching the query * `count`: Items in this response * `limit`: Max items per page * `offset`: Items skipped The `/brands` endpoint is the exception: it returns the full list with a top-level `total` count and no `pagination` object. See [Entities](/developers/entities) for detailed data structures. Validation and resource errors include machine-readable codes: ```json theme={null} { "error": { "code": "BRAND_NOT_FOUND", "message": "Brand not found or not accessible" } } ``` Authentication and rate-limit errors use a flat shape. See [Error codes](/developers/errors) before implementing error handling. ## Get help * Check the [Authentication](/developers/authentication) guide * Review [Rate Limits](/developers/rate-limits) * See [Error codes](/developers/errors) * Contact us at [team@qwairy.co](mailto:team@qwairy.co) # Rate limits Source: https://docs.qwairy.co/developers/rate-limits Understand team-level API limits, rate-limit headers, and Retry-After handling. Limits are applied **per team** across all API tokens belonging to that team. ## Current limits Two windows are enforced simultaneously on every request: | Window | Limit | | ------------------- | ----------------------- | | Per-minute (burst) | 1,000 requests / minute | | Per-day (daily cap) | 20,000 requests / day | A request is accepted only if **both** windows have budget remaining. If either window is exhausted, the API returns `429 Too Many Requests`. To discuss a different limit for a planned workload, contact [team@qwairy.co](mailto:team@qwairy.co) with the expected request volume. ## Rate-limit headers After successful authentication, API responses include headers for both windows. A `429` response also includes `Retry-After`. | Header | Description | | ----------------------------- | ------------------------------------------------------------------------------------ | | `X-RateLimit-Limit` | Per-minute request cap | | `X-RateLimit-Remaining` | Requests remaining in the current minute window | | `X-RateLimit-Reset` | ISO 8601 timestamp when the minute window resets | | `X-RateLimit-Daily-Limit` | Per-day request cap | | `X-RateLimit-Daily-Remaining` | Requests remaining in the current day window | | `X-RateLimit-Daily-Reset` | ISO 8601 timestamp when the day window resets | | `Retry-After` | Seconds to wait before retrying, based on the limit actually hit (only set on `429`) | ### Synthetic headers ```http theme={null} X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 873 X-RateLimit-Reset: 2026-04-07T09:13:00.000Z X-RateLimit-Daily-Limit: 20000 X-RateLimit-Daily-Remaining: 4211 X-RateLimit-Daily-Reset: 2026-04-08T00:00:00.000Z ``` Check both windows. A request can be rejected when the daily cap is exhausted even if the per-minute budget remains, and vice versa. ## Handle rate limits When either window is exceeded, the API returns `429 Too Many Requests` with a body indicating which window was hit: ```json theme={null} { "error": "Too Many Requests", "limit": "daily", "message": "Daily rate limit (20000/day) exceeded. Please try again in 32451 seconds.", "retryAfter": 32451 } ``` The `limit` field is either `"burst"` or `"daily"`. Honor `Retry-After`, which is calculated from the window that blocked the request. ### Retry practices Use the `Retry-After` header as-is. It already reflects whichever window (minute or day) is the real blocker. Monitor `X-RateLimit-Remaining` and `X-RateLimit-Daily-Remaining` to throttle proactively. Store and reuse data that doesn't change frequently (brands, competitors, topics). Use query filters (`provider`, `period`, `limit`) to fetch only the data you need. ### Retry logic ```javascript theme={null} async function fetchWithRetry(url, options, maxRetries = 10) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status !== 429) return response; // Retry-After points at the blocking window // (minute or day window), not just the minute reset. const retryAfterSec = Number(response.headers.get('Retry-After') ?? '60'); const waitMs = Math.max(retryAfterSec * 1000, 1000); const body = await response.clone().json().catch(() => ({})); console.warn( `Rate limited on ${body.limit ?? 'unknown'} window. Retrying in ${waitMs}ms...` ); await new Promise((r) => setTimeout(r, waitMs)); } throw new Error('Max retries exceeded'); } ``` If you see `"limit": "daily"` on retry, waiting within the same process rarely helps: the daily window can be hours away. Persist the `X-RateLimit-Daily-Reset` timestamp and reschedule the job instead of blocking a worker. ## Reduce API usage ### Use filtering parameters Instead of fetching all data and filtering client-side, use query parameters: ```bash theme={null} # Fetch all answers, then filter in code GET /api/v1/brands/{brandId}/answers # Filter at the API level GET /api/v1/brands/{brandId}/answers?provider=chatgpt&hasSelfMention=true&limit=50 ``` ### Cache stable data Cache data only for as long as your integration can tolerate staleness: * **Brands list**: changes when brand access changes * **Competitors list**: changes with configuration and observed data * **Topics/Tags**: changes when workspace configuration changes ### Use date ranges Limit data retrieval to relevant time periods: ```bash theme={null} # Bound an otherwise unbounded request to the last 7 days GET /api/v1/brands/{brandId}/performance?period=7 ``` ## Discuss higher limits Contact [team@qwairy.co](mailto:team@qwairy.co) with your use case and expected volume. Any change depends on the workload and account configuration. # Outbound webhooks Source: https://docs.qwairy.co/developers/webhooks Receive weekly-report and export-ready events as signed JSON POST requests to your endpoint. An Enterprise team can send supported notifications to its own endpoint as signed JSON events. When webhook delivery is enabled, Qwairy does not send the matching email. Outbound webhooks are part of [Agencies](/agencies/introduction), an **Enterprise** capability. The controls are **OWNER**-only. ## Events A workspace's weekly report. A data export has finished and is ready to download. A `webhook.test` event is also sent by the **Send test event** button so you can validate your endpoint before turning delivery on. ## Configure delivery Go to **Team Management > Notifications** and find **Outbound webhooks**. Enter a public `https` URL and save it. Private addresses and `http` URLs are rejected. A **signing secret** is generated and shown once; copy and store it. Click **Send test event** to confirm Qwairy can reach your endpoint and your signature check passes. Toggle **Deliver via webhook** on. From now on, the events above go to your endpoint instead of being emailed. You can rotate the secret at any time with **Regenerate secret** (the new value is shown once), and remove the webhook to fall back to email. ## Payload Every request is a `POST` with a JSON envelope: ```json theme={null} { "event": "export.ready", "id": "8b40e94f-79f4-40cc-8d38-bde11ef0d8d9", "createdAt": "2026-06-18T09:00:00.000Z", "team": { "id": "team_abc123", "name": "Acme" }, "data": { "exportId": "cmg1x0c9r0001l204qk1nv8dz", "exportType": "answers", "rowCount": 842 } } ``` The `data` object is event-specific. Treat it as forward-compatible: new fields may be added, so ignore the ones you don't use rather than rejecting the payload. ## Headers | Header | Value | | -------------------- | ----------------------------------------------- | | `X-Qwairy-Event` | Event name, for example `export.ready` | | `X-Qwairy-Timestamp` | Unix time (seconds) when the request was signed | | `X-Qwairy-Signature` | `sha256=` HMAC of the request | ## Verify the signature The signature is an HMAC-SHA256 keyed with your signing secret. Its input is `.` using the raw request bytes before JSON parsing. ```js theme={null} import crypto from 'crypto'; function isValidQwairyWebhook(req, secret) { const signature = req.headers['x-qwairy-signature']; // "sha256=" const timestamp = req.headers['x-qwairy-timestamp']; // Reject stale requests to prevent replay (e.g. older than 5 minutes). if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(`${timestamp}.${req.rawBody}`) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } ``` Verify the signature on every request and reject anything that fails. Use a constant-time comparison, and reject timestamps outside a short window to block replays. ## Delivery behavior * Qwairy expects a `2xx` response. Non-`2xx`, timeouts, and redirects count as failures. * A failed weekly-report delivery is retried on the next scheduled run; the matching email is **not** sent in its place. * The export-ready event is one-shot: if delivery fails, the export still appears in your workspace's **Exports** hub. # Backlink Opportunities Source: https://docs.qwairy.co/documentation/act/backlink-opportunities Investigate cited third-party pages where competitor presence and on-page evidence suggest an outreach opportunity. Open **Act > Backlink Opportunities** to review external pages found through monitored sources. Qwairy combines citation activity with on-page analysis to identify pages that may be relevant to outreach. A candidate page is not an endorsement of its quality, authority, or willingness to link. ## How candidates are identified A candidate can be based on signals such as: * the page or domain appears in monitored AI answers; * direct competitors are present on the analyzed page; * the SELF brand is absent or has limited on-page presence; * the source is classified as a third party rather than owned or ignored. Source analysis may fetch the public URL to inspect page content. Access can fail when a site blocks automated requests, requires authentication, or changes after the monitored answer was captured. ## Review before outreach 1. Open the source page and confirm it is current. 2. Check why it appeared in the monitored answer. 3. Verify competitor and source classifications. 4. Assess editorial relevance and contact ownership outside Qwairy. 5. Record outreach and publication status in the system your team uses for delivery. Do not treat a detected gap as evidence that a backlink will be granted or that it will change search or AI visibility. ## Related pages * [Source Explorer](/documentation/monitor/content-sources) * [Content Opportunities](/documentation/act/content-opportunities) # Content Opportunities Source: https://docs.qwairy.co/documentation/act/content-opportunities Review prompts where monitored answers show a content or brand-presence gap, with the evidence used for prioritization. Open **Act > Content Opportunities** to review prompts that may warrant new or revised content. An opportunity is derived from the monitored dataset. It is a research lead, not a prediction that publishing content will change a provider's answer. ## What the page combines The page can bring together: * the prompt and its topic or funnel stage; * providers that returned relevant answers; * SELF and competitor presence; * cited sources or related evidence; * a gap or priority value calculated for the current scope. Open an item to understand which records support it. Confirm that the prompt reflects an audience need before creating work from it. ## Review an opportunity 1. Read the monitored answers for the prompt. 2. Check whether direct competitors are classified correctly. 3. Inspect cited pages in **Source Explorer**. 4. Review existing content on your site for overlap. 5. Decide whether to update an existing page, create a new page, or dismiss the item. Use filters and sorting to focus on the market, provider, topic, or stage relevant to the current plan. ## Continue in Content Studio Where the action is available, send an opportunity to [Content Studio](/documentation/act/content-studio) to prepare a brief. Review the generated brief against your editorial standards and source material. ## Related pages * [Query Fan-Out](/documentation/monitor/insights/query-fan-out) * [Source Explorer](/documentation/monitor/content-sources) * [Response Analysis](/documentation/monitor/analyzing-answers) # Content Studio Source: https://docs.qwairy.co/documentation/act/content-studio Create and review content briefs using selected Qwairy insights, brand context, and editorial inputs. Open **Act > Content Studio** to turn a selected opportunity or topic into a structured content brief. Content Studio can use context from Qwairy features such as Query Fan-Out, Shopping Results, Content Opportunities, personas, and brand data. Generated material is a draft for human review. ## Start a brief Choose the available inputs for the assignment, which can include: * target prompt or topic; * audience persona; * language and market; * supporting insights and sources; * editorial instructions. Review the selected inputs before generating. Creating an article or generated brief costs **50 credits**. Duplicating an existing article creates a new article and costs another **50 credits**. ## Review the output Check the brief for: * a clear search or audience intent; * accurate use of brand facts; * traceable sources for factual claims; * overlap with existing pages; * an appropriate structure and call to action; * unsupported competitor, legal, financial, or performance claims. Edit the result to match your editorial voice and publishing requirements. Qwairy does not publish the brief to an external content system from this page unless a separate integration explicitly provides that action. ## Use monitoring as evidence Return to the source prompt and answers when the brief makes an assumption about provider behavior. After publication, compare equivalent monitoring periods without claiming that a single content change caused a metric movement. ## Related pages * [Content Opportunities](/documentation/act/content-opportunities) * [Personas](/documentation/workspace/personas) * [Providers & Credits](/documentation/get-started/credits) # Perception Source: https://docs.qwairy.co/documentation/analyze/brand-perception Compare how selected AI providers describe your brand with the attributes you have configured. Open **Analyze > Perception** to review generated perception snapshots for the selected brand. Perception is separate from the sentiment attached to routine monitoring mentions. It runs a dedicated analysis and compares provider answers with your configured brand attributes. ## Prepare the analysis Review the brand attributes before starting a snapshot. These attributes define the intended positioning used for alignment analysis. Keep them specific and verifiable. The run uses credits per provider. See [Providers & Credits](/documentation/get-started/credits) for the current cost. ## History and status The history table records each snapshot and its state, including pending, generating, analyzing, completed, or failed. Open a completed snapshot to inspect its detail. Do not compare two snapshots without checking their providers, attributes, and time period. A configuration change can affect the result independently of provider output. ## Snapshot detail The detail view can include: * sentiment and alignment scores; * an Overview across providers and tabs for individual providers; * areas where provider descriptions converge or diverge; * themes and brand-attribute alignment; * a SWOT-style summary; * the prompts, answers, and sources used in the snapshot. Scores are generated assessments of the selected responses. Read the supporting summaries and answers before using them in an external statement. ## Review workflow 1. Confirm the brand attributes and provider set. 2. Review the aggregate view for recurring themes. 3. Switch to provider tabs to locate disagreement. 4. Open the source answers for context. 5. Record any attribute or provider change before the next comparison. ## Related pages * [Sentiment](/documentation/analyze/sentiment-analysis) * [Response Analysis](/documentation/monitor/analyzing-answers) # Fact Check Source: https://docs.qwairy.co/documentation/analyze/fact-check Extract claims from monitored answers, compare them with a source of truth, and record human verification decisions. Fact Check is in private beta. Access must be granted for the workspace. Open **Analyze > Fact Check** to review factual claims extracted from answers that mention the monitored brand. Fact Check does not consume customer credits. There is no customer-facing active or paused toggle. Processing is controlled by beta access and the available Fact Check workflows. ## How claims are processed 1. Qwairy selects monitored answers with a SELF brand mention. 2. It extracts factual claims from those answers. 3. It compares claims with entries in the **Source of Truth** where available. 4. Authorized users review claims that need a decision. 5. The audit log records claim and verification activity. Automated matching helps organize review. It does not replace verification of material facts. ## Tabs ### Overview The Overview summarizes claim accuracy, status distribution, changes over time, and available category or provider breakdowns. ### Claims Search, filter, and sort extracted claims. Available statuses include Unverified, Correct, Incorrect, Outdated, Auto-verified, and Ignored. Open a claim to review its occurrences, providers, category, impact, and relationship to the Source of Truth before assigning a verdict. ### Source of Truth Maintain the canonical statements used for comparison. A source-of-truth entry should be narrow, current, and supported by an approved internal or public reference. Access to this tab depends on the user's role. ### Audit log Review the chronological record of extraction and verification activity. Access depends on the user's role. ## Export Where available for the user's role, use **Export CSV** to download claims and their current metadata. An export is a point-in-time snapshot; later verdicts are not applied to an existing file. A claim marked Correct means it was verified in the configured review context. Confirm legal, financial, security, and other time-sensitive statements with the responsible source before external use. # Sentiment Source: https://docs.qwairy.co/documentation/analyze/sentiment-analysis Review the sentiment assigned to brand mentions across providers, topics, competitors, and time periods. Open **Analyze > Sentiment** to review how detected brand mentions are described in monitored answers. Each analyzed mention receives a score on a 0 to 100 scale. Higher values represent more positive language in the analyzed context. The page also groups mentions into positive, neutral, and negative categories. ## Choose the scope Use the available controls to select: * the monitored brand or a competitor; * provider; * topic; * time range; * result order. Global filters can further affect the dataset. Confirm the selected brand before comparing sentiment with another page. ## Review individual mentions The mention list connects each score to its context, including the provider, topic, prompt, and generated summary where available. Read the full answer when wording is ambiguous. A score summarizes detected tone; it does not establish customer opinion, brand reputation in the wider market, or the intent of the provider. ## Compare periods Keep the prompt set, providers, market, and brand relationship stable. A change in the mix of prompts can move the average even when similar answers receive similar scores. ## Export Use the available export control for the current sentiment dataset. Record the filters and date range with any external report. ## Related pages * [Perception](/documentation/analyze/brand-perception) * [Response Analysis](/documentation/monitor/analyzing-answers) * [Core Concepts](/documentation/get-started/core-concepts) # Supported countries and territories Source: https://docs.qwairy.co/documentation/availability/countries Compare market and AI engine availability for continuous workspaces and one-off Pitch Audits. Country scope works differently for continuous workspaces and one-off Pitch Audits. Use the tab for the surface you are configuring. | Surface | Market choices | Scope | | ------------ | -------------: | ---------------------------------------------------------------------------- | | Workspaces | 248 | One country or territory per workspace. Available AI engines vary by market. | | Pitch Audits | 248 | One country per audit. Available AI engines vary by market. | Country availability does not guarantee a particular answer. AI engines can return different results by market, prompt, language, and collection time. A workspace can be configured for any of the 248 markets below. The matrix shows which core AI engines can be selected for that workspace country. | Country or territory | Code | ChatGPT | Gemini | Perplexity | Copilot | Grok | AI Mode | AI Overview | | -------------------------------- | ---- | ------- | ------ | ---------- | ------- | ---- | ------- | ----------- | | Afghanistan | `AF` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Aland Islands | `AX` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Albania | `AL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Algeria | `DZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | American Samoa | `AS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Andorra | `AD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Angola | `AO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Anguilla | `AI` | Yes | Yes | Yes | No | Yes | Yes | Yes | | Antarctica | `AQ` | No | No | No | No | No | No | Yes | | Antigua and Barbuda | `AG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Argentina | `AR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Armenia | `AM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Aruba | `AW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Australia | `AU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Austria | `AT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Azerbaijan | `AZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bahamas | `BS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bahrain | `BH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bailiwick of Jersey | `JE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bangladesh | `BD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Barbados | `BB` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Belarus | `BY` | No | No | No | No | No | No | Yes | | Belgium | `BE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Belize | `BZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Benin | `BJ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bermuda | `BM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bhutan | `BT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bolivia | `BO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bonaire (caribbean Netherlands) | `BQ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bosnia and Herzegovina | `BA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Botswana | `BW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bouvet Island | `BV` | No | No | No | No | No | Yes | Yes | | Brazil | `BR` | Yes | Yes | Yes | No | Yes | Yes | Yes | | British Indian Ocean Territory | `IO` | No | Yes | No | No | No | Yes | Yes | | Brunei | `BN` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Bulgaria | `BG` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Burkina Faso | `BF` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Burundi | `BI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cambodia | `KH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cameroon | `CM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Canada | `CA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cape Verde | `CV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cayman Islands | `KY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Central African Republic | `CF` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Chad | `TD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Chile | `CL` | Yes | Yes | Yes | Yes | No | Yes | Yes | | China | `CN` | No | No | No | No | No | Yes | Yes | | Christmas Island | `CX` | No | No | No | No | No | Yes | Yes | | Cocos (keeling) Islands | `CC` | No | No | No | No | Yes | Yes | Yes | | Colombia | `CO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Comoros | `KM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Congo | `CG` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Cook Islands | `CK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Costa Rica | `CR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Côte d'Ivoire | `CI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Croatia (hrvatska) | `HR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Curacao | `CW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cyprus | `CY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Czech Republic | `CZ` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Denmark | `DK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Djibouti | `DJ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Dominica | `DM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Dominican Republic | `DO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | DR Congo | `CD` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Ecuador | `EC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Egypt | `EG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | El Salvador | `SV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Equatorial Guinea | `GQ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Eritrea | `ER` | No | No | No | No | No | Yes | Yes | | Estonia | `EE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Ethiopia | `ET` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Falkland Islands | `FK` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Faroe Islands | `FO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Fiji | `FJ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Finland | `FI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | France | `FR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | French Guiana | `GF` | Yes | Yes | Yes | Yes | Yes | Yes | No | | French Polynesia | `PF` | Yes | Yes | Yes | Yes | Yes | No | Yes | | Gabon | `GA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Gambia | `GM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Georgia | `GE` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Germany | `DE` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Ghana | `GH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Gibraltar | `GI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Greece | `GR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Greenland | `GL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Grenada | `GD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guadeloupe | `GP` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Guam | `GU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guatemala | `GT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guernsey | `GG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guinea | `GN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guinea-Bissau | `GW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guyana | `GY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Haiti | `HT` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Heard & McDonald Islands | `HM` | No | No | No | No | No | Yes | Yes | | Honduras | `HN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Hong Kong | `HK` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Hungary | `HU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Iceland | `IS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | India | `IN` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Indonesia | `ID` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Iran | `IR` | No | No | No | No | No | No | No | | Iraq | `IQ` | No | No | No | No | No | No | Yes | | Ireland | `IE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Isle of Man | `IM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Israel | `IL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Italy | `IT` | Yes | Yes | Yes | No | Yes | Yes | Yes | | Jamaica | `JM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Japan | `JP` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Jordan | `JO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kazakhstan | `KZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kenya | `KE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kiribati | `KI` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Kosovo | `XK` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Kuwait | `KW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kyrgyzstan | `KG` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Laos | `LA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Latvia | `LV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Lebanon | `LB` | No | No | No | No | No | No | Yes | | Lesotho | `LS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Liberia | `LR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Libya | `LY` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Liechtenstein | `LI` | Yes | Yes | Yes | No | No | Yes | Yes | | Lithuania | `LT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Luxembourg | `LU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Macau | `MO` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Madagascar | `MG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Malawi | `MW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Malaysia | `MY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Maldives | `MV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mali | `ML` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Malta | `MT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Marshall Islands | `MH` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Martinique | `MQ` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Mauritania | `MR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mauritius | `MU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mayotte | `YT` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Mexico | `MX` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Micronesia | `FM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Moldova | `MD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Monaco | `MC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mongolia | `MN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Montenegro | `ME` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Montserrat | `MS` | No | No | No | No | No | Yes | Yes | | Morocco | `MA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mozambique | `MZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Myanmar (burma) | `MM` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Namibia | `NA` | No | Yes | Yes | No | No | Yes | Yes | | Nauru | `NR` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Nepal | `NP` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Netherlands | `NL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | New Caledonia | `NC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | New Zealand | `NZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Nicaragua | `NI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Niger | `NE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Nigeria | `NG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Niue | `NU` | No | No | Yes | Yes | No | Yes | Yes | | Norfolk Island | `NF` | No | No | No | No | No | Yes | Yes | | North Korea | `KP` | No | No | No | No | No | No | No | | North Macedonia | `MK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Northern Mariana Islands | `MP` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Norway | `NO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Oman | `OM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Pakistan | `PK` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Palau | `PW` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Palestine | `PS` | No | No | No | No | No | No | Yes | | Panama | `PA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Papua New Guinea | `PG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Paraguay | `PY` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Peru | `PE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Philippines | `PH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Pitcairn Islands | `PN` | No | No | No | No | No | Yes | Yes | | Poland | `PL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Portugal | `PT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Puerto Rico | `PR` | Yes | Yes | Yes | Yes | Yes | No | Yes | | Qatar | `QA` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Réunion | `RE` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Romania | `RO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Russia | `RU` | No | No | No | No | No | No | Yes | | Rwanda | `RW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Saint Barthélemy | `BL` | No | Yes | Yes | No | No | Yes | No | | Saint Helena | `SH` | No | Yes | Yes | Yes | No | Yes | No | | Saint Kitts and Nevis | `KN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Saint Lucia | `LC` | Yes | Yes | Yes | Yes | Yes | No | Yes | | Saint Martin | `MF` | Yes | Yes | Yes | Yes | No | Yes | No | | Saint Pierre and Miquelon | `PM` | No | Yes | Yes | Yes | No | Yes | No | | Saint Vincent and the Grenadines | `VC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | San Marino | `SM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | São Tomé and Príncipe | `ST` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Saudi Arabia | `SA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Senegal | `SN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Serbia | `RS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Seychelles | `SC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sierra Leone | `SL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Singapore | `SG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sint Maarten | `SX` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Slovak Republic | `SK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Slovenia | `SI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Solomon Islands | `SB` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Somalia | `SO` | Yes | Yes | Yes | Yes | No | Yes | Yes | | South Africa | `ZA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | South Georgia | `GS` | No | No | No | No | No | Yes | No | | South Korea | `KR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | South Sudan | `SS` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Spain | `ES` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sri Lanka | `LK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sudan | `SD` | No | No | No | No | No | No | Yes | | Suriname | `SR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Svalbard & Jan Mayen | `SJ` | No | No | No | No | No | Yes | Yes | | Swaziland | `SZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sweden | `SE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Switzerland | `CH` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Syria | `SY` | No | No | No | No | No | No | Yes | | Taiwan | `TW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tajikistan | `TJ` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Tanzania | `TZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Thailand | `TH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Timor-Leste | `TL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Togo | `TG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tokelau | `TK` | No | No | No | No | No | Yes | Yes | | Tonga | `TO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Trinidad and Tobago | `TT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tunisia | `TN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Turkey | `TR` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Turkmenistan | `TM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Turks and Caicos Islands | `TC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tuvalu | `TV` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Uganda | `UG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Ukraine | `UA` | Yes | Yes | Yes | Yes | No | Yes | Yes | | United Arab Emirates | `AE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | United Kingdom | `GB` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | United States | `US` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Uruguay | `UY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | US Minor Outlying Islands | `UM` | No | No | No | No | No | Yes | Yes | | Uzbekistan | `UZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Vanuatu | `VU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Vatican City | `VA` | Yes | No | Yes | No | No | Yes | Yes | | Venezuela | `VE` | No | Yes | Yes | No | No | Yes | Yes | | Vietnam | `VN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Virgin Islands (british) | `VG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Virgin Islands (us) | `VI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Wallis and Futuna | `WF` | Yes | Yes | Yes | Yes | No | Yes | No | | Western Sahara | `EH` | No | No | No | No | No | Yes | Yes | | Western Samoa | `WS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Yemen | `YE` | Yes | Yes | Yes | Yes | No | Yes | Yes | | Zambia | `ZM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Zimbabwe | `ZW` | Yes | Yes | Yes | Yes | No | Yes | Yes | Pitch Audits accept the 248 markets below. Each audit is a one-off snapshot and has its own engine selection. | Country or territory | Code | ChatGPT | Gemini | Perplexity | Copilot | Grok | AI Mode | AI Overview | | -------------------------------- | ---- | ------- | ------ | ---------- | ------- | ---- | ------- | ----------- | | Afghanistan | `AF` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Aland Islands | `AX` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Albania | `AL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Algeria | `DZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | American Samoa | `AS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Andorra | `AD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Angola | `AO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Anguilla | `AI` | Yes | Yes | Yes | No | Yes | Yes | Yes | | Antarctica | `AQ` | No | No | No | No | Yes | No | Yes | | Antigua and Barbuda | `AG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Argentina | `AR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Armenia | `AM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Aruba | `AW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Australia | `AU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Austria | `AT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Azerbaijan | `AZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bahamas | `BS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bahrain | `BH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bailiwick of Jersey | `JE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bangladesh | `BD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Barbados | `BB` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Belarus | `BY` | No | No | No | No | Yes | No | Yes | | Belgium | `BE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Belize | `BZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Benin | `BJ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bermuda | `BM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bhutan | `BT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bolivia | `BO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bonaire (caribbean Netherlands) | `BQ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bosnia and Herzegovina | `BA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Botswana | `BW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bouvet Island | `BV` | No | No | No | No | Yes | Yes | Yes | | Brazil | `BR` | Yes | Yes | Yes | No | Yes | Yes | Yes | | British Indian Ocean Territory | `IO` | No | Yes | No | No | Yes | Yes | Yes | | Brunei | `BN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Bulgaria | `BG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Burkina Faso | `BF` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Burundi | `BI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cambodia | `KH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cameroon | `CM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Canada | `CA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cape Verde | `CV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cayman Islands | `KY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Central African Republic | `CF` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Chad | `TD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Chile | `CL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | China | `CN` | No | No | No | No | Yes | Yes | Yes | | Christmas Island | `CX` | No | No | No | No | Yes | Yes | Yes | | Cocos (keeling) Islands | `CC` | No | No | No | No | Yes | Yes | Yes | | Colombia | `CO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Comoros | `KM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Congo | `CG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cook Islands | `CK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Costa Rica | `CR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Côte d'Ivoire | `CI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Croatia (hrvatska) | `HR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Curacao | `CW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Cyprus | `CY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Czech Republic | `CZ` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Denmark | `DK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Djibouti | `DJ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Dominica | `DM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Dominican Republic | `DO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | DR Congo | `CD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Ecuador | `EC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Egypt | `EG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | El Salvador | `SV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Equatorial Guinea | `GQ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Eritrea | `ER` | No | No | No | No | Yes | Yes | Yes | | Estonia | `EE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Ethiopia | `ET` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Falkland Islands | `FK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Faroe Islands | `FO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Fiji | `FJ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Finland | `FI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | France | `FR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | French Guiana | `GF` | Yes | Yes | Yes | Yes | Yes | Yes | No | | French Polynesia | `PF` | Yes | Yes | Yes | Yes | Yes | No | Yes | | Gabon | `GA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Gambia | `GM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Georgia | `GE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Germany | `DE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Ghana | `GH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Gibraltar | `GI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Greece | `GR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Greenland | `GL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Grenada | `GD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guadeloupe | `GP` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Guam | `GU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guatemala | `GT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guernsey | `GG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guinea | `GN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guinea-Bissau | `GW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Guyana | `GY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Haiti | `HT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Heard & McDonald Islands | `HM` | No | No | No | No | Yes | Yes | Yes | | Honduras | `HN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Hong Kong | `HK` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Hungary | `HU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Iceland | `IS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | India | `IN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Indonesia | `ID` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Iran | `IR` | No | No | No | No | Yes | No | No | | Iraq | `IQ` | No | No | No | No | Yes | No | Yes | | Ireland | `IE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Isle of Man | `IM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Israel | `IL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Italy | `IT` | Yes | Yes | Yes | No | Yes | Yes | Yes | | Jamaica | `JM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Japan | `JP` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Jordan | `JO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kazakhstan | `KZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kenya | `KE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kiribati | `KI` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Kosovo | `XK` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Kuwait | `KW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Kyrgyzstan | `KG` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Laos | `LA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Latvia | `LV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Lebanon | `LB` | No | No | No | No | Yes | No | Yes | | Lesotho | `LS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Liberia | `LR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Libya | `LY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Liechtenstein | `LI` | Yes | Yes | Yes | No | Yes | Yes | Yes | | Lithuania | `LT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Luxembourg | `LU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Macau | `MO` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Madagascar | `MG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Malawi | `MW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Malaysia | `MY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Maldives | `MV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mali | `ML` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Malta | `MT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Marshall Islands | `MH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Martinique | `MQ` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Mauritania | `MR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mauritius | `MU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mayotte | `YT` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Mexico | `MX` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Micronesia | `FM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Moldova | `MD` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Monaco | `MC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mongolia | `MN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Montenegro | `ME` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Montserrat | `MS` | No | No | No | No | Yes | Yes | Yes | | Morocco | `MA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Mozambique | `MZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Myanmar (burma) | `MM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Namibia | `NA` | No | Yes | Yes | No | Yes | Yes | Yes | | Nauru | `NR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Nepal | `NP` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Netherlands | `NL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | New Caledonia | `NC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | New Zealand | `NZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Nicaragua | `NI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Niger | `NE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Nigeria | `NG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Niue | `NU` | No | No | Yes | Yes | Yes | Yes | Yes | | Norfolk Island | `NF` | No | No | No | No | Yes | Yes | Yes | | North Korea | `KP` | No | No | No | No | Yes | No | No | | North Macedonia | `MK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Northern Mariana Islands | `MP` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Norway | `NO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Oman | `OM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Pakistan | `PK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Palau | `PW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Palestine | `PS` | No | No | No | No | Yes | No | Yes | | Panama | `PA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Papua New Guinea | `PG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Paraguay | `PY` | No | Yes | Yes | Yes | Yes | Yes | Yes | | Peru | `PE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Philippines | `PH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Pitcairn Islands | `PN` | No | No | No | No | Yes | Yes | Yes | | Poland | `PL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Portugal | `PT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Puerto Rico | `PR` | Yes | Yes | Yes | Yes | Yes | No | Yes | | Qatar | `QA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Réunion | `RE` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Romania | `RO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Russia | `RU` | No | No | No | No | Yes | No | Yes | | Rwanda | `RW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Saint Barthélemy | `BL` | No | Yes | Yes | No | Yes | Yes | No | | Saint Helena | `SH` | No | Yes | Yes | Yes | Yes | Yes | No | | Saint Kitts and Nevis | `KN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Saint Lucia | `LC` | Yes | Yes | Yes | Yes | Yes | No | Yes | | Saint Martin | `MF` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Saint Pierre and Miquelon | `PM` | No | Yes | Yes | Yes | Yes | Yes | No | | Saint Vincent and the Grenadines | `VC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | San Marino | `SM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | São Tomé and Príncipe | `ST` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Saudi Arabia | `SA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Senegal | `SN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Serbia | `RS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Seychelles | `SC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sierra Leone | `SL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Singapore | `SG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sint Maarten | `SX` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Slovak Republic | `SK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Slovenia | `SI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Solomon Islands | `SB` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Somalia | `SO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | South Africa | `ZA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | South Georgia | `GS` | No | No | No | No | Yes | Yes | No | | South Korea | `KR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | South Sudan | `SS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Spain | `ES` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sri Lanka | `LK` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sudan | `SD` | No | No | No | No | Yes | No | Yes | | Suriname | `SR` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Svalbard & Jan Mayen | `SJ` | No | No | No | No | Yes | Yes | Yes | | Swaziland | `SZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Sweden | `SE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Switzerland | `CH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Syria | `SY` | No | No | No | No | Yes | No | Yes | | Taiwan | `TW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tajikistan | `TJ` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Tanzania | `TZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Thailand | `TH` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Timor-Leste | `TL` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Togo | `TG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tokelau | `TK` | No | No | No | No | Yes | Yes | Yes | | Tonga | `TO` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Trinidad and Tobago | `TT` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tunisia | `TN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Turkey | `TR` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Turkmenistan | `TM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Turks and Caicos Islands | `TC` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Tuvalu | `TV` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Uganda | `UG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Ukraine | `UA` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | United Arab Emirates | `AE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | United Kingdom | `GB` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | United States | `US` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Uruguay | `UY` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | US Minor Outlying Islands | `UM` | No | No | No | No | Yes | Yes | Yes | | Uzbekistan | `UZ` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Vanuatu | `VU` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Vatican City | `VA` | Yes | No | Yes | No | Yes | Yes | Yes | | Venezuela | `VE` | No | Yes | Yes | No | Yes | Yes | Yes | | Vietnam | `VN` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Virgin Islands (british) | `VG` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Virgin Islands (us) | `VI` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Wallis and Futuna | `WF` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Western Sahara | `EH` | No | No | No | No | Yes | Yes | Yes | | Western Samoa | `WS` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Yemen | `YE` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Zambia | `ZM` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Zimbabwe | `ZW` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | ## How to use this registry * Choose the country or territory that represents the audience you want to observe. * Compare only answers collected with the same country, language, prompt set, and time window. * Recheck this page before expanding to a new market because provider availability can change. This registry reflects the Qwairy product configuration synchronized on September 15, 2026. ## Related pages * [Supported languages](/documentation/availability/languages) * [Workspace monitoring](/documentation/workspace/monitoring) * [Pitch Audits](/agencies/pitch-audits) # Supported languages Source: https://docs.qwairy.co/documentation/availability/languages Compare language configuration for continuous workspaces and one-off Pitch Audits. Language configuration has a different grain in workspaces and Pitch Audits. Both surfaces select language independently from country, but they expose different language catalogues. | Surface | Language choices | Selection method | | ------------ | -----------------------------------: | ------------------------------------------------- | | Workspaces | 45 languages, 46 selectable variants | Selected independently from the workspace country | | Pitch Audits | 2 languages | Selected independently from the audit country | The following 46 selectable variants represent 45 supported languages. English has separate US and UK variants. Qwairy codes are product configuration values. Do not assume that every code is interchangeable with a generic ISO language code. | Language | Qwairy code | | ------------ | ----------- | | Albanian | `SQ` | | Arabic | `AR` | | Armenian | `HY` | | Azerbaijani | `AZ` | | Bengali | `BN` | | Bosnian | `BS` | | Bulgarian | `BG` | | Chinese | `ZH` | | Croatian | `HR` | | Czech | `CS` | | Danish | `DA` | | Dutch | `NL` | | English (UK) | `EN_GB` | | English (US) | `EN_US` | | Estonian | `ET` | | Finnish | `FI` | | French | `FR` | | German | `DE` | | Greek | `EL` | | Hebrew | `HE` | | Hindi | `HI` | | Hungarian | `HU` | | Indonesian | `ID` | | Italian | `IT` | | Japanese | `JA` | | Korean | `KO` | | Latvian | `LV` | | Lithuanian | `LT` | | Macedonian | `MK` | | Malay | `MS` | | Norwegian | `NO` | | Polish | `PL` | | Portuguese | `PT` | | Romanian | `RO` | | Russian | `RU` | | Serbian | `SR` | | Slovak | `SK` | | Slovenian | `SL` | | Spanish | `ES` | | Swedish | `SE` | | Tagalog | `TL` | | Thai | `TH` | | Turkish | `TR` | | Ukrainian | `UK` | | Urdu | `UR` | | Vietnamese | `VI` | Pitch Audits support an explicit choice between English and French, independently from the selected country. The selected language is used for generated questions and the report. | Language | Code | | -------- | ---- | | English | `en` | | French | `fr` | ## Country and language are separate dimensions In a workspace, country controls the market context and AI engine availability. Language controls prompt generation and generated content. Changing either dimension changes the measured scope and can break trend comparability. This registry reflects the Qwairy product configuration on September 15, 2026. ## Related pages * [Supported countries and territories](/documentation/availability/countries) * [Workspace settings](/documentation/workspace/brand-settings) * [Content Studio](/documentation/act/content-studio) * [Pitch Audits](/agencies/pitch-audits) # Action Center Source: https://docs.qwairy.co/documentation/cockpit/action-center Review suggestions derived from Qwairy data, organize accepted work, and adjust the strategy used for prioritization. Action Center is available on paid plans. Viewer access is restricted. Open **Cockpit > Action Center** to review suggested work derived from the current workspace data. Suggestions are decision support. Validate their evidence, scope, ownership, and expected effort before committing resources. ## Suggestions The **Suggestions** tab lists detected opportunities and issues. Open a suggestion to review its supporting signals and proposed steps. You can: * accept a suggestion and add it to the action plan; * dismiss a suggestion that is not relevant; * use the available filters to narrow the queue. Accepting a suggestion records an intention to work on it. It does not change external content or guarantee a metric change. ## Action plan The **Action plan** tab tracks accepted work. Switch between the available list and board views to review status and priority. A separate **Getting started** area may appear for setup tasks. These items help complete the workspace configuration and are distinct from ongoing suggestions. ## History The **History** tab keeps completed or otherwise resolved items available for review. Use it to understand what the team recorded, not as proof that an external change was published. ## Strategy The **Strategy** tab lets authorized users adjust the context used to order suggestions. Revisit it when business priorities, market, or reporting scope changes. ## Review workflow 1. Read the supporting evidence. 2. Confirm that the affected prompt, provider, source, or page is still in scope. 3. Assign an owner and status in the action plan. 4. Record the external implementation separately when needed. 5. Compare later monitoring periods using the same scope. # Compare Source: https://docs.qwairy.co/documentation/cockpit/compare Compare the monitored brand with selected competitors across metrics, positioning, overlap, trends, and breakdowns. Open **Cockpit > Compare** to place the monitored brand and selected competitors in the same analytical scope. The monitored brand is fixed in the comparison. Add at least one competitor to populate comparative views. Favorited competitors may be preselected when the page opens. ## Comparison views ### Metrics The metric table presents each selected brand using the product metrics available for the current period. Check the definition of each metric before comparing it with a value from another Qwairy surface. ### Brand Positioning The positioning view places selected brands on configurable axes. Read the axis labels and metric selectors before interpreting distance or quadrant placement. ### Co-Mention Overlap The overlap view shows how often brands appear together in the monitored answer set. Co-mention is an observed association in those answers, not evidence of a business relationship or user preference. ### Evolution Over Time The trend view compares selected brands across the current date range. Keep the prompt set, providers, market, and brand relationships stable when evaluating movement. ### Breakdowns Use the breakdown tabs to compare results by: * provider; * topic; * tag; * funnel stage; * prompt. ## Check an unexpected comparison 1. Confirm that each competitor is classified as DIRECT. 2. Verify the current filters and date range. 3. Check whether every selected provider returned answers. 4. Open **Monitor > Response Analysis** to inspect the underlying text. See [Core Concepts](/documentation/get-started/core-concepts) for the shared metric definitions. # GEO Matrix Source: https://docs.qwairy.co/documentation/cockpit/geo-matrix Compare one visibility metric across AI providers and topics, tags, funnel stages, or individual prompts. Open **Cockpit > GEO Matrix** to compare a selected metric across two dimensions. ## Choose the rows The row selector can group data by: * **Topics**; * **Tags**; * **Funnel**; * **Prompts**. Provider columns show the same metric for each AI provider in the current scope. Use the metric selector above the matrix to change what each cell represents. ## Read a cell A cell combines the selected row group with one provider. Its value and comparative position are calculated only from answers that match that intersection and the active filters. An empty cell means that the intersection has no usable data for the selected metric. It does not by itself indicate poor performance. ## Inspect details Select a cell to inspect the records available for that combination. The detail view can show the leading competitors and sources for the selected scope. Use this sequence when a cell looks unexpected: 1. confirm the metric and row grouping; 2. check the date range and saved list; 3. verify that prompts generated answers for the provider; 4. inspect competitor classifications and cited sources; 5. compare the underlying answers in **Monitor**. ## Export the matrix Use the image export control to download the current matrix as a PNG. The export reflects the visible configuration, so include the metric, grouping, date range, and filters when sharing it. ## Related pages * [Overview](/documentation/cockpit/performance-dashboard) * [Compare](/documentation/cockpit/compare) * [Prompts](/documentation/workspace/prompts) # Overview Source: https://docs.qwairy.co/documentation/cockpit/performance-dashboard Read the five dashboard metrics, trends, competitors, sources, and breakdowns for the current monitoring scope. Open **Cockpit > Overview** for a summary of the current brand and filter scope. ## Metric cards The top row contains five metrics: | Metric | What the card calculates | | --------------------- | ------------------------------------------------------------------------------------------------------------ | | **Mention Rate** | Distinct answers with a SELF mention divided by distinct answers with at least one SELF or DIRECT mention. | | **Citation Rate** | Distinct answers with a SELF citation divided by distinct answers with at least one SELF or DIRECT citation. | | **Share of Voice** | SELF mention occurrences divided by SELF and DIRECT mention occurrences. | | **Average Sentiment** | Average sentiment for the selected brand's scored mentions. | | **Coverage** | Distinct answered prompts with at least one SELF mention divided by all distinct answered prompts. | These definitions are specific to the dashboard. In particular, the REST performance endpoint calculates Coverage at the answer level. See [Core Concepts](/documentation/get-started/core-concepts). ## Filters Use the available provider, topic, tag, funnel, prompt, list, and date controls to change the scope. A card, chart, and table should be compared only when they use the same filters. ## Dashboard sections The Overview can include: * performance evolution over time; * a provider comparison; * competitors detected in monitored answers; * cited source domains and their contribution to the selected scope; * breakdowns by provider, topic, tag, and funnel stage. Select a chart element or breakdown row to narrow the view where that interaction is available. ## Investigate a result 1. Confirm the filters and date range. 2. Check the denominator for the metric. 3. Open [Response Analysis](/documentation/monitor/analyzing-answers) to read the underlying answers. 4. Review brand relationships in [Competitor Mentions](/documentation/monitor/competitor-mentions). 5. Review ownership and page detail in [Source Explorer](/documentation/monitor/content-sources). A percentage can move because answers changed, because the prompt or provider mix changed, or because a brand relationship was reclassified. Record configuration changes when reporting a trend. # Core Concepts Source: https://docs.qwairy.co/documentation/get-started/core-concepts Definitions for prompts, topics, providers, answers, mentions, citations, and Qwairy's dashboard metrics. Use these definitions when reading Qwairy dashboards and this documentation. ## Monitoring vocabulary * **Prompt**: a natural-language question tracked across one or more AI models. * **Topic**: a thematic group assigned to a prompt. A prompt can have one topic or be unassigned. * **Tag**: a reusable label. A prompt can have multiple tags. * **Funnel stage**: the prompt's position in the buyer journey: TOFU, MOFU, or BOFU. * **Provider**: the company or product family that serves an AI experience, such as OpenAI or Google. * **Model**: the specific AI experience or model selected for generation, such as ChatGPT or GPT-5. * **Answer**: the output captured for one prompt and model generation. * **Monitoring frequency**: how often a prompt is scheduled. A prompt may inherit the workspace default or override it with Daily, Weekly, Monthly, or No monitoring. * **Boost**: additional answers generated for a prompt, model, and run. A boost can be set from 2 to 20 answers. The workspace can have a limited number of boosted prompts. * **Credits**: the unit consumed by answer generation and selected product operations. Creating or importing prompt definitions is free, but automatic answers generated afterward consume the sum of the selected model costs. See [Providers & Credits](/documentation/get-started/credits). ## Brand relationships Qwairy classifies detected brands by their relationship to the monitored brand: * **SELF**: the monitored brand. * **DIRECT**: a direct competitor included in competitive metrics. * **INDIRECT**: a related brand that is not currently treated as a direct competitor. * **IGNORED**: a brand excluded from the competitive analysis. Review these classifications when a metric looks unexpected. ## Mentions and citations A **mention** is an occurrence of a classified brand in an answer. A **citation** is a detected source associated with an answer. A link in the answer and a citation used for analysis are related signals, but they are not interchangeable on every page. ## Overview metrics All formulas below use the active date range and filters. | Metric | Product definition | | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | | **Mention Rate** | Distinct answers that mention the SELF brand divided by distinct answers that mention at least one SELF or DIRECT brand. | | **Citation Rate** | Distinct answers that cite a SELF source divided by distinct answers that cite at least one SELF or DIRECT source. | | **Share of Voice** | SELF mention occurrences divided by SELF and DIRECT mention occurrences. | | **Average Sentiment** | Average 0 to 100 sentiment score for mentions of the selected brand in scope. | | **Coverage** | Distinct answered prompts with at least one SELF mention divided by all distinct answered prompts in scope. | If a denominator is zero, the interface displays the empty or zero state defined by that view. The REST performance endpoint has its own Coverage definition: answers with a SELF mention divided by all answers. Do not use that REST formula to reproduce the prompt-level Coverage card on the dashboard. ## Scope and comparability Metrics change with provider, topic, tag, funnel, prompt, list, and date filters. Compare periods only after checking that the scope, models, market, and prompt set are equivalent. For the source data behind a metric, use [Response Analysis](/documentation/monitor/analyzing-answers), [Competitor Mentions](/documentation/monitor/competitor-mentions), and [Source Explorer](/documentation/monitor/content-sources). # Providers & Credits Source: https://docs.qwairy.co/documentation/get-started/credits Review the supported AI experiences and models, their collection method, and the credit cost of each operation. Credit use depends on the operation, the selected model, the number of prompts, and any configured boost. ## Collection methods Qwairy collects answers through two methods: * **Core models** use the provider's public AI experience. They cost **1 credit per answer** and support country-level localization where the provider is available. * **Premium models** use an official provider API for a named model. They cost **2 to 20 credits per answer**, depending on the model. Country hints and native web search or grounding are enabled only where the provider supports them. Mistral and DeepSeek currently run without web search. Some Premium identifiers are fixed versions, while others follow a moving provider alias. These methods observe different product surfaces. Keep the collection method and model selection stable when comparing periods. Core-model availability depends on the selected country and the provider's current service status. Unavailable models are excluded from generation. ## Core models | Model | Provider | Credits | | ---------------------- | ------------- | ------: | | **ChatGPT** | OpenAI | 1 | | **Perplexity** | Perplexity AI | 1 | | **Copilot** | Microsoft | 1 | | **Grok** | xAI | 1 | | **Gemini** | Google | 1 | | **Google AI Overview** | Google | 1 | | **Google AI Mode** | Google | 1 | ## Premium models ### OpenAI | Model | Credits | | ------------ | ------: | | GPT-5 mini | 2 | | GPT-5 | 4 | | GPT-5.1 | 4 | | GPT-5.2 | 5 | | GPT-5.4 mini | 3 | | GPT-5.4 | 6 | | GPT-5.5 | 11 | ### Anthropic (Claude) | Model | Credits | | ----------------- | ------: | | Claude Haiku 4.5 | 4 | | Claude Sonnet 4.5 | 12 | | Claude Sonnet 4.6 | 14 | | Claude Sonnet 5 | 14 | | Claude Opus 4.6 | 20 | | Claude Opus 4.7 | 20 | | Claude Opus 4.8 | 20 | ### Google Gemini | Model | Credits | | ---------------------- | ------: | | Gemini 2.5 Flash-Lite | 3 | | Gemini 2.5 Flash | 4 | | Gemini 2.5 Pro | 5 | | Gemini 3 Flash Preview | 4 | | Gemini 3.1 Flash-Lite | 2 | | Gemini 3.5 Flash | 3 | | Gemini 3.1 Pro Preview | 5 | ### Perplexity | Model | Credits | | -------------------- | ------: | | Perplexity Sonar | 2 | | Perplexity Sonar Pro | 3 | | Sonar Reasoning Pro | 4 | | Sonar Deep Research | 6 | ### xAI (Grok) | Model | Credits | | -------- | ------: | | Grok 4.3 | 5 | | Grok 4.5 | 3 | ### Mistral | Model | Credits | | -------------- | ------: | | Mistral Small | 3 | | Mistral Medium | 4 | | Mistral Large | 4 | ### DeepSeek | Model | Credits | | ----------------- | ------: | | DeepSeek V4 Flash | 2 | | DeepSeek V4 Pro | 2 | ## Other credit costs | Operation | Cost | Notes | | ---------------------------------------------- | -------------------------------------: | ----------------------------------------------------------------------------------------------------------- | | **Prompt definition creation or import** | 0 credits | Adding prompts is free when no answers run immediately. | | **Automatic answers after creation or import** | Sum of selected model costs per prompt | With auto-generate enabled, each new prompt runs against the active and available models. | | **Answer generation** | 1 to 20 credits | One prompt sent to one model. Monitoring and manual runs multiply this cost by prompts, models, and boosts. | | **Perception analysis** | 10 credits per provider | A snapshot can run across up to 6 Core providers. | | **Site Diagnostics** | 5 credits per completed page audit | Audits and re-audits are billed per page that completes. | | **Content Studio article or brief creation** | 50 credits | Each new article or generated brief. | | **Content Studio article duplication** | 50 credits | Each duplicate creates a new article. | | **Pitch Audit answer generation** | Sum of selected model costs per prompt | For each prompt, add the credit cost of every selected model. | Fact Check does not consume customer credits. ## Calculate a run Multiply each prompt by the credit cost of every selected model, then include the extra answers requested by any boost. For 10 prompts on ChatGPT Core, GPT-5 mini, and Perplexity Core: ```text theme={null} 10 x (1 + 2 + 1) = 40 credits ``` Creating or importing prompt definitions is free. With auto-generate enabled, 100 prompts using the three default onboarding Core models where all are available cost: ```text theme={null} 100 x (1 + 1 + 1) = 300 credits ``` Ten completed Site Diagnostics page audits cost: ```text theme={null} 10 x 5 = 50 credits ``` Creating one Content Studio article or generated brief costs **50 credits**. Duplicating one existing article costs another **50 credits**. Automatic-answer cost always uses the sum of the models configured and available for that workspace. It is not a fixed per-prompt amount. The Monitoring page shows an estimate based on the current prompt frequencies, models, and boosts. Review that estimate before saving a change. ## Check and replenish the balance Your remaining balance appears in the dashboard sidebar and under **Team Management > Usage**. The Usage page breaks consumption down by category, model, and transaction. If the balance covers only part of a monitoring or import batch, Qwairy may process an affordable subset of prompts or models. At zero credits, scheduled monitoring pauses and manual generation is unavailable. Owners of paid parent teams can purchase a credit pack or upgrade under **Team Management > Billing**. Free teams must upgrade first. Sub-team credits are allocated by the parent organization. ## Related pages * [Monitoring](/documentation/workspace/monitoring) * [Prompts](/documentation/workspace/prompts) * [Usage](/documentation/team/usage) * [Billing](/documentation/team/billing) # Frequently Asked Questions Source: https://docs.qwairy.co/documentation/get-started/faq Answers about monitoring, metrics, topics, prompts, credits, website access, competitors, exports, and integrations. ## Monitoring and data It represents an answer captured for a specific prompt, model, configuration, and time. Provider output can vary between generations, so one answer is an observation rather than a universal result. Yes. A prompt can inherit the workspace default or use Daily, Weekly, Monthly, or No monitoring. You can also pause or resume individual prompts from **Workspace > Prompts**. A boost generates multiple answers for the selected prompt, model, and run. You can set 2 to 20 answers. Boosts increase credit use, and the workspace limits how many prompts can be boosted at once. The answer-monitoring pipeline does not require a crawler or tracking tag on your site. Other workflows may fetch public pages you submit, including onboarding analysis, Site Diagnostics, and source analysis. ## Metrics They use different units and denominators. Mention Rate is answer-level and considers answers with a SELF or DIRECT brand mention. Dashboard Coverage is prompt-level and considers all distinct answered prompts in scope. See [Core Concepts](/documentation/get-started/core-concepts). The REST performance endpoint calculates answers with a SELF mention divided by all answers. The dashboard card calculates distinct answered prompts with a SELF mention divided by all distinct answered prompts. Use the definition for the surface you are reporting. Mention Rate, Citation Rate, and Share of Voice use SELF and DIRECT relationships. Reclassifying a detected brand changes which records enter those calculations for the selected scope. ## Topics and prompts Qwairy keeps the prompts and unassigns them from the deleted topic. It also removes the topic from saved taxonomy references and cleans related topic projections. Assign another topic if you need those prompts in topic-level views. Yes. A prompt can have multiple tags but at most one topic. ## Credits and access Answer generation, Perception analysis, completed Site Diagnostics page audits, Content Studio article or brief creation and duplication, and Pitch Audit answer generation consume credits. Creating or importing prompt definitions is free. If automatic answers run afterward, each prompt costs the sum of the configured and available model costs; any boost increases that answer-generation cost. See [Providers & Credits](/documentation/get-started/credits). Multiply the number of prompts by the sum of the selected model costs, then include every additional answer requested by a boost. The interface shows an estimate before you save the configuration. Owners of paid parent teams can buy credit packs under **Team Management > Billing**. Free teams must upgrade first. Sub-teams receive credits allocated by their parent organization. No. Fact Check is a private-beta workflow and does not debit customer credits. Use **Workspace > Shared Links** for shareable views and **Workspace > Exports** for generated files. Team integrations include the REST API, MCP Server, and Looker Studio where enabled for the account. ## Troubleshooting Check the current brand, date range, saved list, and filters. Confirm that answers exist for the selected prompts and providers. Some insight pages appear only when the corresponding answer metadata was detected. Use **Monitor > Competitor Mentions** to change a brand relationship. Use **Monitor > Source Explorer** to inspect source ownership and type. # Interpreting Your First Results Source: https://docs.qwairy.co/documentation/get-started/first-results Review the first monitoring snapshot, verify its scope, and trace dashboard metrics back to prompts and answers. A first monitoring run is a snapshot of the selected prompts and models. Treat it as an initial observation, not as a trend. ## Check the scope first Before interpreting a percentage, confirm: * the expected prompts generated answers; * the intended providers, country, and language were selected; * topics, tags, and funnel stages are assigned correctly; * direct competitors are classified as DIRECT rather than INDIRECT or IGNORED; * the date range includes the run you want to review. ## Read the Overview Open **Cockpit > Overview**. The five cards summarize different parts of the selected dataset: * Mention Rate and Citation Rate use only answers with a SELF or DIRECT denominator; * Share of Voice counts mention occurrences, not distinct answers; * Average Sentiment summarizes scored mentions; * Coverage works at the distinct-prompt level. See [Core Concepts](/documentation/get-started/core-concepts) before comparing these values with an export or API response. ## Inspect the evidence Open **Monitor > Response Analysis** to read individual answers. Use prompt, answer, funnel, competitor, source, and insight filters to narrow the table. Then use: * **Monitor > Competitor Mentions** to verify brand relationships; * **Monitor > Source Explorer** to inspect cited domains and pages; * **Cockpit > GEO Matrix** to compare a metric across providers and prompt groupings. ## When data is sparse An empty chart can mean that no answer matches the current filters, that a provider did not return data, or that the relevant signal was not detected. Widen the date range and clear filters before changing the prompt set. If answers exist but do not address the intended question, revise ambiguous prompts in **Workspace > Prompts**. Keep the old and new wording separate if you need to preserve historical comparability. ## Build a trend Use repeated runs with a stable prompt set, provider selection, and market to build a comparable time series. Document configuration changes when interpreting a break in the trend. # Quickstart Source: https://docs.qwairy.co/documentation/get-started/quickstart Set up a brand, review generated topics and prompts, choose monitoring settings, and inspect the first answers. The onboarding flow creates the first monitoring configuration for a brand. Review each generated field before starting a run. ## Before you begin Have the following information ready: * the public website URL for the brand; * the country or market you want to observe; * the products, services, and competitors that matter to the analysis. Qwairy may fetch the submitted website and use public web context during onboarding. The generated description, topics, prompts, and competitors are suggestions that you can edit. ## Complete onboarding Submit the public URL for the brand. Qwairy analyzes the page and proposes a brand profile. Check the name, description, domain, language, and market. Correct any incomplete or inaccurate field before continuing. Keep the topics that match your monitoring scope. The onboarding flow can start with up to **5 topics** and **50 prompts**. Edit or remove prompts that are ambiguous, duplicated, or outside the intended market. Confirm that direct competitors are classified correctly. You can add, edit, favorite, ignore, or reclassify competitors later. Select the AI models and a default frequency. With auto-generate enabled, answers run immediately after setup; otherwise they wait for a manual or scheduled run. Credit cost equals the number of prompts multiplied by the sum of the selected model costs, plus any additional answers requested by boosts. The total is displayed before confirmation. See [Providers & Credits](/documentation/get-started/credits). ## After onboarding Open **Workspace > Prompts** to review the resulting prompt list. Each prompt can inherit the workspace frequency or use its own Daily, Weekly, Monthly, or No monitoring setting. You can also pause a prompt or configure a boost for additional answers per model and run. Then open **Cockpit > Overview** and **Monitor > Response Analysis**. The first view summarizes the selected dataset; the second lets you inspect the answers behind it. Deleting a topic does not delete its prompts. Qwairy unassigns those prompts, removes the topic from saved taxonomy references, and cleans the related topic projections. Reassign the prompts if you still want a topic-level breakdown. ## Next steps * [Configure Monitoring](/documentation/workspace/monitoring) * [Manage Prompts](/documentation/workspace/prompts) * [Interpret the First Results](/documentation/get-started/first-results) # What is Qwairy Source: https://docs.qwairy.co/documentation/get-started/what-is-qwairy Learn how Qwairy observes brand visibility in AI answers and turns monitoring data into analysis and actions. Qwairy is a Generative Engine Optimization (GEO) platform. It monitors how selected AI providers answer the prompts that matter to your brand. For each monitored answer, Qwairy identifies signals such as brand mentions, cited sources, competitors, and sentiment. The product then groups those observations by provider, prompt, topic, tag, funnel stage, and time period. ## The monitoring loop 1. Define the prompts that represent your audience's questions. 2. Select the AI providers and models to monitor. 3. Generate answers manually or on a schedule. 4. Review visibility metrics and the underlying answers. 5. Use the analysis to prioritize further investigation or work. Monitoring records what providers returned for a particular prompt, model, location, and time. It does not show every answer that every user may receive. ## What Qwairy measures The Overview combines five product metrics: * **Mention Rate**: how often your brand appears among answers that mention your brand or a direct competitor. * **Citation Rate**: how often your domain is cited among answers that cite your domain or a direct competitor's domain. * **Share of Voice**: your share of mention occurrences across your brand and direct competitors. * **Average Sentiment**: the average sentiment score attached to the selected brand's mentions. * **Coverage**: the share of distinct answered prompts that contain at least one mention of your brand. See [Core Concepts](/documentation/get-started/core-concepts) for the complete definitions and denominators. ## Website access The answer-monitoring pipeline sends prompts to selected AI providers and analyzes their answers. It does not require a tracking tag or a crawler installed on your site. Other Qwairy workflows may fetch public pages you submit. This includes onboarding analysis, Site Diagnostics, and source analysis. Review the relevant workflow before entering a private or restricted URL. ## Where to start * [Quickstart](/documentation/get-started/quickstart) * [Interpreting Your First Results](/documentation/get-started/first-results) * [Providers & Credits](/documentation/get-started/credits) # AI Revenue Source: https://docs.qwairy.co/documentation/measure/ai-revenue Attribute configured business conversions to AI referral or acquisition evidence while keeping measured revenue separate from estimated value. AI Revenue is available on active paid plans and requires a supported analytics connection. Managers and Owners can connect providers and change tracked outcomes. Until a configuration is saved, Qwairy uses Purchases with provider-native value by default. Open **Measure > AI Revenue** to review purchases, leads, sign-ups, or other configured business conversions that the connected analytics provider associates with AI referral or acquisition evidence. This is analytics-source attribution. Depending on the selected mode, it uses referral evidence from the converting session, first-acquisition evidence, or a deduplicated combination of both. It does not prove that Qwairy activity or an AI answer caused a conversion. ## Supported connections | Provider | Available conversion sources | | ------------------ | ----------------------------------------------- | | Google Analytics 4 | Purchases and discovered events | | Piwik PRO | Purchases and discovered events | | Matomo | Purchases and discovered goals | | Piano Analytics | Purchases and the supported Transactions metric | | Adobe Analytics | Purchases and discovered metrics | Amplitude powers Referrer Analytics but not AI Revenue. Connect or change a provider from [Analytics Integrations](/documentation/measure/integrations/overview). The available actions depend on your team role. ## Configure conversions Open the **Conversions** selector to choose the outcomes that matter to your business: 1. Select each conversion source you want to measure. Selected definitions contribute to the totals. 2. For discovered lead conversions, leave the optional value empty to use a native value when available or to count without value. Enter a fixed value when you need an estimate. 3. If a conversion is not discovered, a Manager or Owner can add its provider event, goal, or metric identifier manually. 4. Save the configuration. The default definition is **Purchases** with its provider-native ecommerce value. Other available definitions depend on the connected provider and source. You can track up to 20 selected business outcomes. If several definitions contribute to totals, one visitor can complete more than one conversion. The total is the sum of the included definitions and is not necessarily a count of unique people. ## Read the results | Metric | Meaning | | ---------------- | ---------------------------------------------------------------------------------------------------------- | | AI Conversions | Included conversion occurrences associated with recognized AI sources under the selected attribution rules | | Conversion Rate | Included AI conversions divided by the AI-associated sessions in the selected attribution scope | | Measured Revenue | Provider-native revenue from included definitions configured with native value | | Estimated Value | Included conversions configured with a fixed value, multiplied by that fixed value | The page also breaks results down by conversion definition, AI source, period, and top converting landing page. The site conversion totals and rate provide a same-provider baseline. For GA4, the dashboard defaults to **Combined** attribution. A row is credited once: first to an AI session source when present, otherwise to an AI first-user source. Use **Direct session** to restrict attribution to the converting session or **First acquisition** to restrict it to the user's original acquisition source. The MCP `get_ai_revenue` tool currently uses Direct session attribution for GA4. Measured Revenue and Estimated Value are separate evidence classes. Measured Revenue is provider-native and is not converted by Qwairy. The current response does not identify its currency, so the interface labels the amount as `native`; confirm the property currency in the provider. Estimated Value uses the currency configured in Qwairy. Do not add the two values unless you have confirmed they share a currency, and do not describe an estimate as observed revenue. ## Validate the result 1. Confirm the analytics property, site, or report suite. 2. Check the date range and reporting time zone. 3. Review every selected conversion definition and optional fixed value. 4. Verify that the corresponding purchases, events, goals, or metrics are populated in the source system. 5. Compare the provider total with a matching source-system segment. 6. Review warnings and document attribution, consent, and currency limitations in external reporting. Data can be missing when referrer information is removed, tracking is blocked, the provider uses a different domain, the selected conversion is not populated, or its instrumentation is incomplete. A connected provider with no conversion data for the selected period is different from a disconnected provider. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [Referrer Analytics](/documentation/measure/referrer-analytics) * [Page Performance](/documentation/measure/page-performance) * [MCP Measure tools](/mcp/tools/measure) # Bing Webmaster Tools Source: https://docs.qwairy.co/documentation/measure/bing-webmaster Connect a Bing site, review query and page search data, and export the selected reporting scope. Open **Measure > Bing Webmaster Tools** to connect a Bing site and inspect its search performance data. ## Connect a site 1. Select **Connect**. 2. Enter a Bing Webmaster Tools API key. 3. Choose the site that matches the monitored brand. 4. Confirm the connection and wait for the available data to load. Treat the API key as a credential. Use the permissions and rotation process defined by your organization. ## Queries tab The **Queries** tab shows search queries and the metrics returned for the selected site and date range. Use search, sorting, and filters to locate relevant terms. Where the Track action is available, review the wording before adding a query to the Qwairy prompt set. ## Pages tab The **Pages** tab groups the available search metrics by URL. Confirm redirects, canonicalization, and site-property scope when one page appears under several URLs. ## Interpretation Bing Webmaster Tools data describes Bing Search activity. It can provide context for Microsoft-related discovery, but it does not establish that a page influenced a specific AI answer. ## Export and troubleshooting Export the current filtered view when needed. If data is missing, check the site selection, API-key access, reporting delay, and date range in both Qwairy and Bing Webmaster Tools. # Crawler Analytics Source: https://docs.qwairy.co/documentation/measure/crawler-analytics Configure a supported server-log collector and analyze retained snapshots of requests classified as AI crawlers. Crawler Analytics is available on Business, Enterprise, and Agency Business plans, or through an explicit feature override. Open **Measure > Crawler Analytics** to review crawler requests accepted from a connected log source. The setup flow is under **Settings** on that page. Crawler Analytics stores bounded daily aggregates. It does not retain raw traffic events or human activity, and it does not prove that a requested page was indexed, used for training, or cited. ## Choose one delivery source The setup page exposes these collectors: | Source | Method | Requirement | Ingestion path | | ---------------------------------------------------------------------------------------------- | ------------------------------------------------ | ------------------------ | ------------------------- | | **[Vercel](/documentation/measure/crawler-analytics/connectors/vercel)** | Drains, JSON logs | Vercel Pro or Enterprise | `/api/v1/logs/vercel` | | **[Cloudflare Logpush](/documentation/measure/crawler-analytics/connectors/cloudflare)** | Managed HTTP destination | Cloudflare Enterprise | `/api/v1/logs/cloudflare` | | **[AWS CloudFront](/documentation/measure/crawler-analytics/connectors/aws-cloudfront)** | Standard Logging v2 through Amazon Data Firehose | CloudFront and Firehose | `/api/v1/logs/cloudfront` | | **[Akamai](/documentation/measure/crawler-analytics/connectors/akamai)** | DataStream 2 | Akamai DataStream 2 | `/api/v1/logs/akamai` | | **[Fastly](/documentation/measure/crawler-analytics/connectors/fastly)** | HTTPS Logging | Fastly CDN service | `/api/v1/logs/fastly` | | **[Netlify](/documentation/measure/crawler-analytics/connectors/netlify)** | General HTTP Log Drain | Netlify Enterprise | `/api/v1/logs/netlify` | | **[Generic HTTP API](/documentation/measure/crawler-analytics/connectors/generic-http)** | Server-side HTTP collector | A server runtime | `/api/v1/logs/ingest` | | **[Cloudflare Worker](/documentation/measure/crawler-analytics/connectors/cloudflare-worker)** | Edge collector recipe | Cloudflare Workers | `/api/v1/logs/cloudflare` | | **[WordPress](/documentation/measure/crawler-analytics/connectors/wordpress)** | PHP collector recipe | PHP 7.4 or later | `/api/v1/logs/ingest` | AWS CloudFront is an active connector in the current setup flow. It is not a coming-soon integration. A brand uses one activated delivery source. After activation, changing provider requires an explicit migration. A pending, unactivated connector can be replaced from the setup flow. ## Create and protect a key Create a dedicated key for the selected source. The plaintext secret is shown once; later the interface shows only its prefix. You can keep up to **5 active keys** for an integration. To rotate, create the replacement, update the source, confirm delivery, and then delete the old key. Choose an IANA time zone when creating the first key. It defines daily snapshots and becomes immutable after activation. ## Provider-specific boundaries ### Managed streams * **Vercel** sends Static and Runtime production logs as JSON. Vercel cannot filter the drain by user agent, so forwarded human rows are discarded by Qwairy but can still count toward Vercel delivery volume. Sampling makes totals partial. * **Cloudflare Logpush** should send only the fields listed by the setup page and apply its crawler pre-filter. The job must use `max_upload_bytes: 5000000` and `max_upload_records: 10000`. * **CloudFront** uses the exact Standard Logging v2 field list shown in setup, JSON output, a Firehose HTTP buffer of 1 MiB and 60 seconds, and S3 failure backup. Shorter delivery intervals are unsupported. * **Akamai** uses the listed DataStream 2 fields, JSON output, Log User-Agent Header, and a push frequency of 60 seconds or longer. * **Fastly** uses the provided minimal JSON format. Its setup allows up to 10,000 entries and 2,000,000 bytes per request; JSON arrays, NDJSON, and gzip are supported. * **Netlify** sends its full Traffic Logs stream and has no crawler-only drain filter. User-Agent must remain available for classification. Qwairy ignores the client IP field and does not retain queries, human rows, or raw payloads. For high-volume sources that cannot filter before delivery, evaluate provider delivery cost and the ingestion ceilings before activation. ### Edge and application collectors The Generic HTTP API accepts `POST` requests with `Content-Type: application/json` and `X-API-Key`. Each useful record contains method, path, hostname, user agent, timestamp, and optionally status code. Filter for known AI crawler user agents in your server or edge layer before sending. A generic batch can contain up to **1,000 records** and the normalized batch ceiling is **2,000,000 bytes**. The Cloudflare Worker and WordPress recipes apply this collector pattern at the edge or application layer. Review generated code before deployment and keep the key in a server-side secret. ## Processing rules The setup page lets you exclude paths and user agents before aggregation. Patterns are case-insensitive and support wildcards, one per line. You can configure up to **32 path rules** and **32 user-agent rules**. Each pattern is limited to 256 bytes and each rule set to 4,096 bytes. ## Read the dashboard The available ranges are Last 24 hours, Last 7 days, and Last 30 days. Crawler Analytics uses analytic days in the integration's configured time zone. **Last 24 hours** means the current analytic day, from its local midnight through the current snapshot; it is not a rolling 24-hour window. The other presets include the current analytic day and the preceding 6 or 29 analytic days. Dashboard totals, crawler breakdowns, charts, and page tables use the public set of supported observable AI-specific User-Agent identities. The ingestion registry can recognize additional identities for classification and compatibility, but those identities are excluded from the current public totals and tables. Classification matches a supported token in the HTTP User-Agent. Qwairy does not verify source IP or reverse DNS, so an occurrence is evidence of the received identity string, not proof of the requester's origin. Traditional search crawlers and robots-policy tokens such as `Google-Extended` are excluded from public occurrence totals. The current public identity set is: * `Claude-SearchBot`, `MistralAI-Index`, `OAI-SearchBot`, and `PerplexityBot`; * `ChatGPT-User`, `Google-GeminiNotebook`, `MistralAI-User`, `Perplexity-User`, `Claude-User`, and `Google-CloudVertexBot`; * `ClaudeBot`, `GPTBot`, and `GrokBot`. `Google-NotebookLM` is accepted as an HTTP alias of the canonical `Google-GeminiNotebook` identity. The page shows: * observed occurrences; * pages that returned at least one HTTP 2xx to a classified crawler; * crawler identities observed; * average frequency across observed daily buckets; * trends, crawler distribution, page state, status-code groups, and page details. Page states are computed as follows: * **New**: the first retained page observation falls within the last 7 days, including the boundary (`firstSeenAt >= now - 7 days`). * **Hot**: the page is in the top decile by supported-crawler occurrences over the current 7 analytic days, has activity on at least 2 of those days, and is not neglected. * **Neglected**: the most recent retained page observation is strictly older than the 30-day boundary. `New` and `Hot` can appear together. `Neglected` is exclusive and cannot appear with either state. The hot calculation always uses the current 7-analytic-day window, independently of the dashboard range selected for displayed occurrences. Missing delivery is not inferred as zero. The interface distinguishes observed, not measured, and incomplete-ingestion states. Quota or provider gaps produce `incomplete_ingestion`. The `≥` marker is reserved for a count whose numeric precision is capped; at-least-once delivery is reported separately because repackaged retries can duplicate observations. Page-by-crawler daily rollups are retained for **30 analytic days**. Delivery continuity evidence is retained for **13 months**. Durable page identities keep `firstSeenAt`, `lastSeenAt`, and `lifetimeCount` after the daily rollups expire. A page identity is created only after at least one HTTP 2xx response. ## Path privacy and matching Before aggregation, Qwairy removes query strings and fragments. Sensitive-looking segments, including long numeric identifiers, email addresses, UUIDs, long hexadecimal values, JWTs, and high-entropy tokens, are replaced with an integration-scoped one-way pseudonym. Paths beyond the supported length are also bounded with a pseudonym. Occurrence counts and lifecycle timestamps remain real, but a pseudonymized path is not reconstructible or linkable across integrations. Page Performance does not attach a title to it and excludes it from GA4, Search Console, and Bing matching. Technical ingestion ceilings are **10,000,000 events per integration per day** and **200,000,000 events per billed team per day**. They protect shared infrastructure and do not cap page cardinality. ## Validate the connection 1. Use the endpoint and authentication scheme displayed for the chosen source. 2. Send the provider's test event or a permitted crawler request. 3. Wait for the connector to move from pending to connected after accepted rollup data. 4. Check delivery-continuity warnings before interpreting a zero. 5. Compare equivalent, unsampled source logs when reconciling totals. ## Related pages * [Site Readiness](/documentation/optimize/site-readiness) * [Page Performance](/documentation/measure/page-performance) # Akamai DataStream 2 Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/akamai Configure an Akamai DataStream 2 custom HTTPS destination for Crawler Analytics. Use Akamai DataStream 2 to send a minimal JSON request dataset to Crawler Analytics through a custom HTTPS destination. ## Before you start You need: * Akamai DataStream 2 for the target property; * permission to change Property Manager logging and activate a stream; * access to **Measure > Crawler Analytics > Settings**; * a secure place to retain the one-time connector secret. ## Create and protect the key 1. In Crawler Analytics settings, select **Akamai**. 2. Select **Create Key**. 3. Enter a name and the IANA time zone used for daily snapshots. 4. Copy the secret when it appears. It cannot be displayed again. 5. Store it as the Basic authentication password for the custom HTTPS destination. To rotate the key, create a replacement, update and activate the destination, confirm delivery, and then delete the previous key. An integration can have up to five active keys. ## Configure the stream 1. In Akamai Control Center, create or edit a DataStream 2 stream for the target property. 2. Include exactly these request fields: ```text theme={null} statusCode reqTimeSec reqHost reqMethod reqPath UA ``` 3. Select **JSON** as the log format. 4. In Property Manager, enable **Log Request Details** and **Log User-Agent Header** so `UA` is populated. 5. Set the push frequency to 60 seconds or longer. 6. Choose a **Custom HTTPS** destination and set: | Setting | Value | | -------------- | ------------------------------------------ | | Endpoint URL | `https://www.qwairy.co/api/v1/logs/akamai` | | Content type | `application/json` | | Authentication | Basic | | Username | `qwairy` | | Password | The Qwairy connector secret | 7. If the property can match known AI crawler user agents before streaming, apply that condition. Otherwise keep the dataset minimal and let Qwairy discard unrecognized rows. 8. Review and activate the stream. See [Akamai's custom HTTPS destination guide](https://techdocs.akamai.com/datastream2/v2/docs/stream-custom-https) for the current provider workflow. ## Test delivery Use Akamai's destination validation or a mock stream to verify the HTTPS and Basic authentication settings. A validation payload does not necessarily contain an accepted crawler record. After activation, request a safe path on the monitored hostname with a recognized crawler User-Agent: ```bash theme={null} curl --head --user-agent "GPTBot/1.0" "https://www.example.com/docs/crawler-check" ``` Check the DataStream delivery status. Qwairy changes the connector from pending after a valid crawler row reaches the rollup pipeline. ## Limits, sampling, and cost * Qwairy accepts Akamai wire deliveries up to 4,000,000 bytes. DataStream can split larger provider files into additional deliveries. * Keep the push frequency at 60 seconds or longer for this connector. * A DataStream sample below 100%, or a Property Manager rule that excludes requests, makes Qwairy totals partial for that scope. * The minimal field list excludes client address, query, cookie, and referrer data. * DataStream delivery and destination traffic may carry Akamai charges. Shared Qwairy daily ceilings also apply. ## Troubleshooting * **Destination validation fails**: confirm HTTPS, `application/json`, Basic authentication, username `qwairy`, and the current connector secret. * **Qwairy stays pending**: confirm the stream is active, the push frequency is supported, and `UA`, `reqHost`, `reqMethod`, `reqPath`, and `reqTimeSec` are populated. * **Rows are acknowledged but not counted**: only current `GET` and `HEAD` rows for the configured hostname with a recognized crawler User-Agent are aggregated. * **Counts are lower than Akamai reports**: check the DataStream sampling rate, Property Manager match criteria, upload errors, Qwairy exclusion rules, and equivalent time windows. * **Delivery is intermittent**: review the destination's DataStream upload metrics and alerts before interpreting missing periods as zero. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # AWS CloudFront through Firehose Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/aws-cloudfront Send CloudFront Standard Logging v2 records to Crawler Analytics through Amazon Data Firehose. Use CloudFront Standard Logging v2 with Amazon Data Firehose to deliver a minimal JSON request dataset to Crawler Analytics. ## Before you start You need: * a CloudFront distribution serving the hostname configured for the Qwairy brand; * permission to configure Standard Logging v2, Firehose, IAM, CloudWatch, and an S3 failure-backup bucket; * access to **Measure > Crawler Analytics > Settings**; * a secure place to retain the one-time connector secret. CloudFront requires its Firehose log-delivery destination in `us-east-1`. ## Create and protect the key 1. In Crawler Analytics settings, select **AWS CloudFront**. 2. Select **Create Key**. 3. Enter a name and the IANA time zone used for daily snapshots. 4. Copy the secret when it appears. Qwairy shows only its prefix later. 5. Store it as the Firehose HTTP endpoint access key. Do not add it to a URL, source repository, or CloudWatch log message. To rotate the key, create a replacement, update Firehose, confirm delivery, and then delete the previous key. An integration can have up to five active keys. ## Configure Firehose 1. In `us-east-1`, create an Amazon Data Firehose delivery stream with an HTTP endpoint destination. 2. Set the endpoint URL to: ```text theme={null} https://www.qwairy.co/api/v1/logs/cloudfront ``` 3. Use the Qwairy connector secret as the Firehose **Access key**. Firehose adds the required `X-Amz-Firehose-Access-Key` header. 4. Set the HTTP buffer size to **1 MiB** and the interval to **60 seconds**. Shorter delivery intervals are not supported by this connector. 5. Enable S3 backup for failed HTTP deliveries and enable Firehose error logging in CloudWatch. 6. Grant the Firehose role access to the backup bucket and required log resources. ## Configure Standard Logging v2 1. In CloudFront, create or edit a Standard Logging v2 configuration for the distribution. 2. Select **JSON** output. 3. Select the Firehose stream as the delivery destination. 4. Use exactly this field list: ```text theme={null} timestamp(ms),x-edge-request-id,sc-status,cs-method,cs-uri-stem,cs(Host),cs(User-Agent) ``` This list excludes client address, query, cookie, and referrer fields. 5. Enable the logging configuration. See [CloudFront Standard Logging v2](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/standard-logging.html) and [Firehose HTTP backup settings](https://docs.aws.amazon.com/firehose/latest/dev/create-configure-backup.html) for the current AWS controls. ## Test delivery After enabling both configurations, request a safe path on the distribution hostname with a recognized crawler User-Agent: ```bash theme={null} curl --head --user-agent "GPTBot/1.0" "https://www.example.com/docs/crawler-check" ``` Check the Firehose HTTP delivery metrics and CloudWatch error log. A successful Qwairy response includes the Firehose request ID. The connector changes from pending after an accepted CloudFront row reaches the rollup pipeline. Generic Firehose demo records do not use the CloudFront Standard Logging v2 shape and therefore do not prove that crawler aggregation is configured correctly. ## Limits, delivery, and cost * Keep the HTTP buffer at 1 MiB and 60 seconds. This keeps the base64 Firehose envelope inside Qwairy's 2,000,000-byte wire boundary. * A delivery can contain at most 10,000 Firehose records or parsed occurrences for this route. * Upstream exclusions make the resulting crawler counts partial. Compare only equivalent CloudFront and Qwairy scopes. * CloudFront logging, Firehose delivery, CloudWatch error logs, and S3 backup can each incur AWS charges. * Shared Qwairy daily ingestion ceilings also apply. ## Troubleshooting * **401 from the HTTP endpoint**: confirm that Firehose uses the current Qwairy secret as its Access key. * **400 invalid delivery request**: verify that the source is CloudFront Standard Logging v2, output is JSON, the exact field list is selected, and the Firehose request ID is preserved. * **413 payload too large**: restore the 1 MiB Firehose buffer and check that no transformation expands individual records. * **415 unsupported encoding**: use the Firehose HTTP delivery encoding supported by the setup and keep the destination content type as JSON. * **Firehose retries or writes to S3 backup**: inspect CloudWatch delivery errors, IAM permissions, endpoint settings, and buffering hints. * **Qwairy stays pending**: confirm that logging is enabled on the intended distribution and generate a current `GET` or `HEAD` request with a recognized crawler User-Agent to the configured hostname. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # Cloudflare Logpush Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/cloudflare Configure a bounded Cloudflare Logpush HTTP job for Crawler Analytics. Use Cloudflare Logpush to send a filtered HTTP Requests dataset to Crawler Analytics. ## Before you start You need: * a Cloudflare Enterprise zone with Logpush access; * permission to create the Logpush job; * a Cloudflare API token with `Logs Write` permission to apply the required upload bounds; * access to **Measure > Crawler Analytics > Settings**; * a secure place to retain the one-time connector secret. ## Create and protect the key 1. In Crawler Analytics settings, select **Cloudflare Logpush**. 2. Select **Create Key**. 3. Enter a name and the IANA time zone used for daily snapshots. 4. Copy the secret when it appears. Qwairy shows only its prefix later. 5. Keep it in the Logpush destination header configuration. Do not place it in an ordinary query parameter, source control, or client-side code. To rotate the key, create a replacement, update the Logpush destination, validate delivery, and then delete the previous key. An integration can have up to five active keys. ## Create the Logpush job 1. In Cloudflare, open **Analytics & Logs > Logs > Add Logpush job** for the target zone. 2. Select the HTTP Requests dataset and an **HTTP destination**. 3. Use this destination configuration, replacing the placeholder with the one-time Qwairy secret: ```text theme={null} https://www.qwairy.co/api/v1/logs/cloudflare?header_Authorization=Bearer%20YOUR_CRAWLER_KEY ``` Cloudflare converts `header_Authorization` into an HTTP header. The space after `Bearer` must remain encoded as `%20`. 4. Select only these fields: ```text theme={null} ClientRequestHost ClientRequestMethod ClientRequestPath ClientRequestUserAgent EdgeResponseStatus EdgeStartTimestamp ``` 5. Keep the timestamp format as RFC 3339. 6. Before enabling the job, use the Cloudflare Logpush API to set: ```json theme={null} { "max_upload_bytes": 5000000, "max_upload_records": 10000 } ``` The default upload size is outside Qwairy's bounded route contract. 7. Add this job filter. It deliberately over-matches several crawler families; Qwairy's registry performs the final classification. ```json theme={null} {"where":{"or":[{"key":"ClientRequestUserAgent","operator":"contains","value":"Bot"},{"key":"ClientRequestUserAgent","operator":"contains","value":"bot"},{"key":"ClientRequestUserAgent","operator":"contains","value":"User"},{"key":"ClientRequestUserAgent","operator":"contains","value":"MistralAI"},{"key":"ClientRequestUserAgent","operator":"contains","value":"Notebook"},{"key":"ClientRequestUserAgent","operator":"contains","value":"Google-Agent"},{"key":"ClientRequestUserAgent","operator":"contains","value":"meta-"},{"key":"ClientRequestUserAgent","operator":"contains","value":"Meta-"},{"key":"ClientRequestUserAgent","operator":"contains","value":"spider"},{"key":"ClientRequestUserAgent","operator":"contains","value":"omgili"},{"key":"ClientRequestUserAgent","operator":"contains","value":"Extended"}]}} ``` 8. Keep sampling at 100% for the filtered dataset, then enable the job. See [Cloudflare's HTTP destination guide](https://developers.cloudflare.com/logs/logpush/logpush-job/enable-destinations/http/) for the current dashboard and API controls. ## Test delivery Cloudflare validates the destination with a gzip-compressed `{"content":"tests"}` payload. The Qwairy route accepts that challenge, but it does not create a crawler aggregate. After validation, request a safe path on the monitored zone with a recognized crawler User-Agent: ```bash theme={null} curl --head --user-agent "GPTBot/1.0" "https://www.example.com/docs/crawler-check" ``` Check the Logpush job for a successful upload. Qwairy moves the connector from pending after an accepted crawler row reaches the rollup pipeline. ## Limits, sampling, and cost * Each Logpush upload is limited to 5,000,000 uncompressed bytes and 10,000 records. * The pre-filter reduces delivery volume but intentionally includes some rows that Qwairy later discards. * A Cloudflare sampling rate below 100% produces partial Qwairy counts. * Logpush delivery and retained provider logs may carry Cloudflare charges. Review current pricing for the target zone. * Shared daily ingestion ceilings also apply. ## Troubleshooting * **Destination validation fails**: confirm the `%20` encoding, current secret, HTTPS endpoint, and required upload bounds. * **401**: verify that the destination still sends `Authorization: Bearer YOUR_CRAWLER_KEY` and that the key belongs to Cloudflare Logpush. * **400 with too many logs**: set `max_upload_records` to `10000` before enabling the job. * **413**: set `max_upload_bytes` to `5000000` and check compression or decoded payload size. * **Qwairy stays pending**: verify the six selected fields, the User-Agent filter, 100% sampling, and a current request to the configured hostname. * **Counts are lower than Cloudflare logs**: compare the same filter and sampling scope, then check Qwairy exclusions and delivery-continuity warnings. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # Cloudflare Worker Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/cloudflare-worker Deploy Qwairy's generated edge collector on a Cloudflare Worker route and verify crawler delivery safely. Use the Cloudflare Worker collector when the monitored site is proxied through Cloudflare but Cloudflare Logpush is not the selected delivery source. The Worker filters known AI crawler User-Agents, forwards the request to the origin, and reports eligible page responses to Qwairy. Use the **Cloudflare Worker** key created by this setup. A Cloudflare Logpush or Generic HTTP key belongs to a different provider contract and is rejected or discarded by this endpoint. ## Before you start You need: * access to **Measure > Crawler Analytics > Settings** for the brand; * a Cloudflare Worker attached to the hostname configured for that brand; * permission to create an encrypted Worker secret and attach a production route; * a test page that returns `text/html` or `text/plain`. Only activate one delivery source for a brand. Running this Worker alongside another active collector can duplicate observations. ## Create and protect the key 1. In Crawler Analytics settings, select **Cloudflare Worker**. 2. Select **Create Key**. 3. Enter a descriptive name and the IANA time zone used for daily analytics. 4. Copy the plaintext secret when it appears. Qwairy shows only its prefix later. 5. Store it as the encrypted Worker secret `QWAIRY_API_KEY`. Do not put the key in Worker source, ordinary environment variables, request URLs, browser code, or logs. Leave `QWAIRY_ENDPOINT` unset unless Qwairy explicitly gives you a different endpoint. ## Deploy the generated Worker Create `worker.js` with the recipe generated by the current Qwairy setup: ```javascript theme={null} /** * Qwairy Crawler Analytics - Cloudflare Worker * * Setup: * 1. Create a new Worker in Cloudflare dashboard * 2. Set environment variables: QWAIRY_API_KEY, QWAIRY_ENDPOINT (optional) * 3. Deploy to a route (e.g., yourdomain.com/*) */ const IGNORE_EXTENSIONS = /\.(?:png|jpe?g|gif|svg|webp|ico|mp4|webm|css|js|json|xml|woff2?|ttf|eot|map)$/i; const IGNORE_PATHS = [/^\/api\//i, /^\/dashboard\//i, /^\/_next\//i, /^\/favicon/i]; const AI_CRAWLER_UA = /(?:^|[^a-z0-9_-])(?:Claude-SearchBot|MistralAI-Index|OAI-SearchBot|PerplexityBot|ChatGPT-User|Google-GeminiNotebook|Google-NotebookLM|Google-Agent|MistralAI-User|Perplexity-User|Claude-User|Google-CloudVertexBot|ClaudeBot|GPTBot|GrokBot|meta-webindexer|Amzn-User|Meta-ExternalFetcher|AI2Bot|Ai2Bot-Dolma|Amazonbot|Bytespider|CCBot|Meta-ExternalAgent|DuckAssistBot|KimiBot|Kimi-SearchBot|YouBot|Kimi-User|Diffbot|Kangaroo Bot|omgili|omgilibot|PanguBot|Timpibot|Webzio-Extended)(?=$|[^a-z0-9_-])/i; export default { async fetch(request, env, ctx) { const url = new URL(request.url); const pathname = url.pathname; const userAgent = request.headers.get('User-Agent') || ''; if (!AI_CRAWLER_UA.test(userAgent) || IGNORE_EXTENSIONS.test(pathname) || IGNORE_PATHS.some(r => r.test(pathname))) { return fetch(request); } const response = await fetch(request); const contentType = response.headers.get('content-type')?.split(';')[0]; if (!contentType || !['text/html', 'text/plain'].includes(contentType)) { return response; } const apiKey = env.QWAIRY_API_KEY; const endpoint = env.QWAIRY_ENDPOINT || 'https://www.qwairy.co/api/v1/logs/cloudflare'; if (!apiKey) return response; ctx.waitUntil( fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey }, body: JSON.stringify({ status_code: response.status, request_method: request.method, request_path: pathname, hostname: request.headers.get('Host') || url.hostname, user_agent: userAgent, timestamp: new Date().toISOString(), }), }).catch(err => console.error('Qwairy: Failed to send log', err)) ); return response; }, }; ``` Then: 1. Paste the recipe into the Worker. 2. Add `QWAIRY_API_KEY` as an encrypted secret with the one-time Qwairy value. 3. Attach the Worker to the exact production route to monitor, such as `www.example.com/*`. 4. Deploy the Worker and confirm normal requests still receive the origin response. The generated recipe sends only the path, not the query string. Review your path design and add exclusions in Qwairy if path segments can contain sensitive identifiers. ## Verify delivery Use a dedicated page that returns HTML and replace the example hostname. This synthetic request creates a crawler observation in the current analytic day: ```bash theme={null} curl --user-agent "Mozilla/5.0 (compatible; GPTBot/1.1)" \ --output /dev/null \ --write-out "%{http_code}\n" \ "https://www.example.com/docs/qwairy-collector-check" ``` 1. Confirm the command returns the expected origin status. 2. In Qwairy, wait for the connector to move from pending to connected after the accepted event reaches a daily rollup. 3. Open the current analytic day and look for `/docs/qwairy-collector-check`. 4. If the connector stays pending, inspect Worker logs for `Qwairy: Failed to send log`, then confirm the route, secret, hostname, User-Agent, and response content type. An origin response alone proves only that the Worker proxy path works. It does not prove that Qwairy accepted and rolled up the event. ## Limits and behavior * The recipe reports only matching User-Agents on non-ignored paths whose origin response is `text/html` or `text/plain`. * The recipe can attempt delivery for other HTTP methods, but Qwairy accepts only `GET` and `HEAD` records for aggregation. * Static file extensions, `/api/`, `/dashboard/`, `/_next/`, and `/favicon` paths are ignored by the generated code. * The Worker sends one Qwairy request per matching origin response. It has no delivery queue or retry loop, so network failures can create gaps. * The integration route allows a burst of 120 ingestion requests per minute. The route can acknowledge over-limit Worker deliveries without aggregating them, so check coverage warnings after traffic spikes. * Qwairy applies its maintained crawler registry, hostname checks, exclusions, late-arrival window, and ingestion ceilings after receipt. * The shared technical ceilings are 10,000,000 events per integration per day and 200,000,000 events per billed team per day. * The collector observes requests. It does not prove indexing, model training, or citation use. ## Rotate or roll back To rotate the key, create a replacement in Qwairy, update the encrypted Worker secret, deploy, verify a new accepted observation, and then delete the previous key. To roll back the collector: 1. Detach the Worker route or disable the Worker deployment. 2. Confirm the origin still serves normal traffic and no new Worker observations arrive. 3. Remove the Worker secret. 4. Revoke the corresponding Qwairy key. Disabling delivery creates an analytics coverage gap. Check delivery-continuity warnings before comparing periods that cross the rollback. ## Related pages * [Crawler Analytics](/documentation/measure/crawler-analytics) * [Cloudflare Logpush](/documentation/measure/crawler-analytics/connectors/cloudflare) # Fastly HTTPS Logging Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/fastly Configure a bounded Fastly HTTPS logging endpoint for Crawler Analytics. Use Fastly HTTPS Logging to deliver a minimal request record from a Fastly CDN service to Crawler Analytics. ## Before you start You need: * permission to edit and activate the target Fastly service; * a service serving the hostname configured for the Qwairy brand; * access to **Measure > Crawler Analytics > Settings**; * a secure place to retain the one-time connector secret. ## Create and protect the key 1. In Crawler Analytics settings, select **Fastly**. 2. Select **Create Key**. 3. Enter a name and the IANA time zone used for daily snapshots. 4. Copy the secret when it appears. It cannot be displayed again. 5. Keep it only in Fastly's custom header value or another restricted secret store. To rotate the key, create a replacement, update the Fastly endpoint, activate the new service version, confirm delivery, and then delete the previous key. An integration can have up to five active keys. ## Configure the HTTPS endpoint 1. In the target Fastly service, add an **HTTPS** logging endpoint. 2. Set the delivery URL to: ```text theme={null} https://www.qwairy.co/api/v1/logs/fastly ``` 3. Fastly validates ownership through this public challenge endpoint: ```text theme={null} https://www.qwairy.co/.well-known/fastly/logging/challenge ``` The challenge authorizes domain validation only. Log delivery still requires the connector key. 4. Use `POST`, set the content type to `application/json`, and add this custom header: ```text theme={null} X-API-Key: YOUR_CRAWLER_KEY ``` 5. Set **Message type** to blank. 6. Set **Request max entries** to `10000` and **Request max bytes** to `2000000`. 7. Choose either an array of JSON or newline-delimited JSON. gzip compression is supported. ## Use the minimal log format Paste this format into the HTTPS logging endpoint: ```text theme={null} { "timestamp": "%{strftime(\{"%Y-%m-%dT%H:%M:%S.000Z"\}, time.start)}V", "host": "%{json.escape(req.http.Host)}V", "request_path": "%{json.escape(req.url.path)}V", "request_method": "%{json.escape(req.method)}V", "request_user_agent": "%{json.escape(req.http.User-Agent)}V", "response_status": %{resp.status}V } ``` This format omits the client address, query string, cookies, and referrer. If your Fastly service already has a condition for known AI crawler user agents, apply it before delivery. Qwairy still performs authoritative classification on receipt. Activate the updated service version on Production. See [Fastly HTTPS log streaming](https://www.fastly.com/documentation/guides/integrations/logging-endpoints/protocol-based-and-self-hosted/log-streaming-https/) for the current provider controls. ## Test delivery First confirm that Fastly accepts the domain-control challenge. Then request a safe path on the monitored hostname: ```bash theme={null} curl --head --user-agent "GPTBot/1.0" "https://www.example.com/docs/crawler-check" ``` Check Fastly's endpoint delivery status. Qwairy moves the connector from pending after an accepted log row reaches the rollup pipeline; a successful challenge alone does not connect it. ## Limits, filtering, and cost * Keep the configured request boundaries at 10,000 entries and 2,000,000 bytes. * Qwairy accepts JSON arrays, NDJSON, and gzip for this route and splits valid deliveries into bounded internal claims. * Upstream filtering reduces delivered volume. Sampling or a condition that omits crawler requests makes counts partial for the excluded scope. * Without an upstream condition, Fastly forwards other requests and Qwairy discards them after receipt. Those rows can still affect provider delivery volume and cost. * Shared daily ingestion ceilings also apply. ## Troubleshooting * **Endpoint validation fails**: confirm that the URL uses `www.qwairy.co` and that Fastly can reach the displayed well-known challenge path. * **401**: check the exact `X-API-Key` header name and the current Fastly connector secret. * **413**: restore the 2,000,000-byte request maximum or reduce the delivery size. * **Logs are rejected or ignored**: keep Message type blank, use valid JSON or NDJSON, and preserve the timestamp, host, path, method, User-Agent, and status fields from the minimal format. * **Qwairy stays pending**: activate the service version and generate a current `GET` or `HEAD` request with a recognized crawler User-Agent on the configured hostname. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # Generic HTTP collector Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/generic-http Send filtered server or edge request logs to Crawler Analytics through the generic ingestion endpoint. Use the Generic HTTP API when you control a server or edge runtime and no managed connector matches your stack. Filter requests before delivery and send only the fields needed for crawler aggregation. ## Before you start You need: * access to **Measure > Crawler Analytics > Settings** for the brand; * a server-side or edge runtime that can observe requests after routing; * the hostname configured for the Qwairy brand; * a secure secret store for the connector key. The collector accepts only `GET` and `HEAD` request events for the configured brand hostname. An apex domain and its `www` form are treated as equivalent. Other subdomains need their own brand scope. ## Create and protect the key 1. In Crawler Analytics settings, select **Generic HTTP API**. 2. Select **Create Key**. 3. Enter a descriptive name and an IANA analytics time zone, such as `Europe/Paris`. 4. Copy the secret when it appears. Qwairy shows only its prefix later. 5. Store it as a server-side secret. Do not place it in browser code, source control, request URLs, or application logs. To rotate the key, create a replacement, update the collector, confirm delivery, and then delete the previous key. An integration can have up to five active keys. ## Send events Send `POST` requests to: ```text theme={null} https://www.qwairy.co/api/v1/logs/ingest ``` Use these headers: ```text theme={null} Content-Type: application/json X-API-Key: YOUR_CRAWLER_KEY ``` `Authorization: Bearer YOUR_CRAWLER_KEY` is also accepted. Prefer `X-API-Key` for the setup shown in Qwairy. Each event used for aggregation needs these fields: | Field | Requirement | | ---------------- | -------------------------------------------------------------------- | | `request_method` | `GET` or `HEAD` | | `request_path` | Path for the requested page; query strings and fragments are removed | | `hostname` | Configured brand hostname, or its apex/`www` equivalent | | `user_agent` | Full User-Agent used for crawler classification | | `timestamp` | Valid ISO 8601 event time | | `status_code` | Optional HTTP response status | The API accepts one object, an array of objects, newline-delimited JSON, or an object with a `logs` array. A stable `Idempotency-Key` header or `batch_id` field lets byte-different retries be deduplicated. Filter known AI crawler user agents in your runtime before calling the endpoint. Qwairy applies its maintained registry again on receipt and discards human, traditional-search, and unknown user agents. ## Test delivery Replace the hostname and key before running this synthetic check: ```bash theme={null} event_time=$(date -u +"%Y-%m-%dT%H:%M:%SZ") curl --request POST "https://www.qwairy.co/api/v1/logs/ingest" \ --header "Content-Type: application/json" \ --header "X-API-Key: $QWAIRY_CRAWLER_KEY" \ --data "{\"request_method\":\"GET\",\"request_path\":\"/docs/crawler-check\",\"hostname\":\"www.example.com\",\"user_agent\":\"GPTBot/1.0\",\"timestamp\":\"${event_time}\",\"status_code\":200}" ``` A valid request returns a JSON acknowledgement. The connector changes from pending only after the rollup pipeline accepts an event in the configured brand scope. An endpoint health response or a filtered event does not establish that connection. ## Limits and delivery scope * A generic request can contain at most 1,000 events and 2,000,000 normalized bytes. * Events delivered more than 72 hours late fall outside the current late-arrival window. * The shared ceilings are 10,000,000 events per integration per day and 200,000,000 events per billed team per day. * Your runtime, network provider, or observability stack may charge for reading and forwarding logs. * Filtering or sampling before delivery makes the resulting counts partial for that scope. ## Troubleshooting * **401 or 403**: confirm that the secret belongs to the Generic HTTP connector for this brand and is still active. * **400**: validate the JSON shape, record count, method, path, hostname, User-Agent, and timestamp. * **413**: reduce the batch size or encoded payload size. * **429**: pause until the `Retry-After` interval has elapsed, then retry the same idempotent batch. * **Acknowledged but not connected**: confirm that the hostname matches the brand and that the test uses a currently recognized AI crawler User-Agent and a current timestamp. * **Counts are lower than source logs**: check upstream filters, sampling, excluded paths, excluded user agents, late delivery, and rejected hostnames. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # Netlify Log Drains Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/netlify Connect Netlify traffic logs to Crawler Analytics through a General HTTP log drain. Use a Netlify General HTTP log drain to send site traffic records to Crawler Analytics. ## Before you start You need: * a Netlify Enterprise account with permission to configure the target site; * a site serving the hostname configured for the Qwairy brand; * access to **Measure > Crawler Analytics > Settings**; * a secure place to retain the one-time connector secret. ## Create and protect the key 1. In Crawler Analytics settings, select **Netlify**. 2. Select **Create Key**. 3. Enter a name and the IANA time zone used for daily snapshots. 4. Copy the secret when it appears. Qwairy shows only its prefix later. 5. Keep the secret in the Netlify authorization setting. Do not add it to the endpoint query string, source control, or browser code. For rotation, create a replacement, update the Netlify authorization value, confirm delivery, and then delete the previous key. An integration can have up to five active keys. ## Configure the log drain 1. In the target Netlify site, open **Logs & Metrics > Log Drains**. 2. Select **Enable a log drain** and choose **General HTTP endpoint**. 3. Select **Traffic logs**. Do not add Function, Edge Function, or Deploy logs for this connector. 4. Leave **Exclude personally identifiable information (PII)** unchecked. Crawler classification requires the `user_agent` field. 5. Set the full endpoint URL to: ```text theme={null} https://www.qwairy.co/api/v1/logs/netlify ``` 6. Set the authorization header value to: ```text theme={null} Bearer YOUR_CRAWLER_KEY ``` 7. Choose **JSON** or **NDJSON** as the Log Drain Format. 8. Select **Connect**. Netlify's traffic payload can include `client_ip`. Qwairy ignores that field before aggregation and does not retain raw payloads, query parameters, or human requests. See [Netlify Log Drains](https://docs.netlify.com/manage/monitoring/log-drains/) for the current provider workflow. ## Test delivery Request a safe path on the monitored site with a recognized crawler User-Agent: ```bash theme={null} curl --head --user-agent "GPTBot/1.0" "https://www.example.com/docs/crawler-check" ``` Check that Netlify reports a successful drain delivery. In Qwairy, the connector changes from pending only after an accepted traffic row contains a matching hostname, current timestamp, `GET` or `HEAD` method, path, and recognized crawler User-Agent. ## Limits, filtering, and cost * Netlify's General HTTP drain sends the selected traffic stream and does not expose a crawler-only filter for this integration. * Human and unknown User-Agent rows are discarded by Qwairy, but they still contribute to Netlify delivery volume and the Qwairy ingress ceilings. * Review current Netlify log-drain and traffic-delivery pricing before enabling the integration on a high-volume site. * If the PII exclusion removes `user_agent`, Qwairy cannot classify the request and discards it. * The shared integration and billed-team daily ceilings are documented on the Crawler Analytics overview. ## Troubleshooting * **The drain cannot connect**: verify the full HTTPS endpoint and the `Bearer ` prefix before the current key. * **Qwairy stays pending**: confirm that **Traffic logs** are selected and that the PII exclusion is off, then generate a current request with a recognized crawler User-Agent. * **Rows are acknowledged but not counted**: check that `log_type` is `traffic`, the URL hostname matches the brand, the method is `GET` or `HEAD`, and the event has a valid timestamp. * **Counts are lower than source traffic**: human, traditional-search, unknown, excluded, invalid, or out-of-scope rows do not enter crawler aggregates. * **Delivery volume is unexpectedly high**: Netlify forwards the full selected traffic stream. Reassess provider cost before leaving the connector enabled. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # Vercel Drains Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/vercel Connect Vercel production request logs to Crawler Analytics through a dedicated JSON drain. Use a Vercel Drain to forward production Static and Runtime request logs to Crawler Analytics. ## Before you start You need: * a Vercel Pro or Enterprise team with permission to manage Drains; * a production project serving the hostname configured for the Qwairy brand; * access to **Measure > Crawler Analytics > Settings**; * a secure place to retain the one-time connector secret. ## Create and protect the key 1. In Crawler Analytics settings, select **Vercel**. 2. Select **Create Key**. 3. Enter a name and the IANA time zone that should define daily snapshots. 4. Copy the secret when it appears. It cannot be displayed again. 5. Keep it in the Vercel Drain configuration or another restricted secret store. Do not include it in a URL, repository, or client-side variable. To rotate the key, create a replacement, update the Drain header, confirm delivery, and then delete the previous key. An integration can have up to five active keys. ## Configure the drain 1. In the Vercel dashboard, open **Team Settings > Drains > Add Drain**. 2. Choose a custom log destination and set the data type to **Logs**. 3. Select **JSON** format. 4. Select the **Production** environment. 5. Include **Static** and **Runtime** sources. 6. Set the endpoint to: ```text theme={null} https://www.qwairy.co/api/v1/logs/vercel ``` 7. Add this custom header, using the secret copied from Qwairy as its value: ```text theme={null} X-API-Key: YOUR_CRAWLER_KEY ``` Existing Drains that use `x-qwairy-token` remain compatible. Prefer `X-API-Key` for new configurations. 8. Keep production traffic at 100% sampling, or add no sampling rule. A lower sampling rate produces partial counts. 9. Save the Drain. See the [Vercel Log Drains reference](https://vercel.com/docs/drains/reference/logs) for the current provider controls and schema. ## Test delivery Use Vercel's destination test to check the endpoint and header. That test may not contain an accepted crawler request, so it does not by itself move the Qwairy connector to connected. Then request an allowed path on the monitored production hostname with a recognized crawler User-Agent. For example, replace the URL below with a safe path on your site: ```bash theme={null} curl --head --user-agent "GPTBot/1.0" "https://www.example.com/docs/crawler-check" ``` Confirm that the Vercel Drain reports successful delivery. In Qwairy, the connector changes from pending after the rollup pipeline accepts an event for the configured hostname. ## Limits, sampling, and cost * Vercel cannot restrict this Drain by User-Agent. It forwards matching Static and Runtime traffic, and Qwairy discards non-AI requests on receipt. * Forwarded human traffic can still count toward Vercel delivery volume and provider charges. Review current Vercel pricing before enabling the Drain on a high-volume site. * Any Vercel sampling rule below 100% makes Crawler Analytics totals partial. * Qwairy applies shared event, request, and daily ingestion ceilings described on the Crawler Analytics overview. ## Troubleshooting * **Destination test fails**: check the HTTPS endpoint, the `X-API-Key` or legacy `x-qwairy-token` header name, and the one-time secret. * **Drain succeeds but Qwairy stays pending**: verify that Static and Runtime production sources are selected, then send a current `GET` or `HEAD` request with a recognized crawler User-Agent to the configured brand hostname. * **Counts are lower than Vercel logs**: check sampling rules, environment selection, source selection, Qwairy exclusion rules, and delivery continuity warnings. * **Counts differ from all request traffic**: only classified AI crawler requests enter Crawler Analytics; other rows are intentionally discarded. * **Provider delivery errors continue after rotation**: confirm that the Drain uses the replacement key before deleting the old one. ## Related page * [Crawler Analytics](/documentation/measure/crawler-analytics) # WordPress Source: https://docs.qwairy.co/documentation/measure/crawler-analytics/connectors/wordpress Install Qwairy's generated PHP tracker in WordPress, protect its key, and verify eligible crawler requests. Use the WordPress collector when PHP handles the monitored page requests and no managed log-stream connector is selected. The generated tracker reports eligible HTML page requests through the Generic HTTP ingestion endpoint. Full-page caches and CDNs can serve a response without running WordPress or PHP. Those requests cannot be observed by this recipe. Use an edge or managed log collector when the traffic you need to measure bypasses PHP. ## Before you start You need: * access to **Measure > Crawler Analytics > Settings** for the brand; * PHP 7.4 or later with the cURL extension enabled; * access to the active theme directory and `functions.php`; * outbound HTTPS access from PHP to `https://www.qwairy.co`; * a server-side secret facility or configuration excluded from source control. Theme updates can replace custom files. Record the installation or use your normal child-theme deployment process. Only activate one Crawler Analytics delivery source for the brand. ## Create and protect the key 1. In Crawler Analytics settings, select **WordPress**. 2. Select **Create Key**. 3. Enter a descriptive name and the IANA time zone used for daily analytics. 4. Copy the plaintext secret when it appears. Qwairy shows only its prefix later. 5. Store it in server-side configuration. Do not commit it to the theme, expose it to JavaScript, place it in a URL, or write it to logs. WordPress uses the Generic HTTP provider contract. Its endpoint is `https://www.qwairy.co/api/v1/logs/ingest`. ## Add the generated tracker Create `qwairy-tracker.php` in the active theme directory with the current generated recipe: ```php theme={null} 'https://www.qwairy.co/api/v1/logs/ingest', 'async' => true]; $options = array_merge($defaults, $options); register_shutdown_function(function() use ($api_key, $options) { qwairy_send_log($api_key, $options); }); } function qwairy_send_log($api_key, $options) { if ($_SERVER['REQUEST_METHOD'] !== 'GET') return; $user_agent = $_SERVER['HTTP_USER_AGENT'] ?? ''; if (!preg_match('/(?:^|[^a-z0-9_-])(?:Claude-SearchBot|MistralAI-Index|OAI-SearchBot|PerplexityBot|ChatGPT-User|Google-GeminiNotebook|Google-NotebookLM|Google-Agent|MistralAI-User|Perplexity-User|Claude-User|Google-CloudVertexBot|ClaudeBot|GPTBot|GrokBot|meta-webindexer|Amzn-User|Meta-ExternalFetcher|AI2Bot|Ai2Bot-Dolma|Amazonbot|Bytespider|CCBot|Meta-ExternalAgent|DuckAssistBot|KimiBot|Kimi-SearchBot|YouBot|Kimi-User|Diffbot|Kangaroo Bot|omgili|omgilibot|PanguBot|Timpibot|Webzio-Extended)(?=$|[^a-z0-9_-])/i', $user_agent)) return; $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/'; if (preg_match('/\.(png|jpe?g|gif|svg|webp|ico|mp4|webm|css|js|json|xml|woff2?|ttf|eot|map)$/i', $path)) return; $ignore_paths = ['/wp-admin/', '/wp-json/', '/api/', '/admin/']; foreach ($ignore_paths as $ignore) { if (strpos($path, $ignore) === 0) return; } $content_type = ''; foreach (headers_list() as $header) { if (stripos($header, 'content-type:') === 0) { $content_type = trim(substr($header, 13)); break; } } if ($content_type && strpos($content_type, 'text/html') === false) return; $log = [ 'status_code' => http_response_code() ?: 200, 'request_method' => $_SERVER['REQUEST_METHOD'], 'request_path' => $path, 'hostname' => $_SERVER['HTTP_HOST'] ?? null, 'user_agent' => $user_agent, 'timestamp' => gmdate('c'), ]; if (function_exists('curl_init')) { $ch = curl_init($options['endpoint']); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($log), CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: ' . $api_key], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, ]); curl_exec($ch); curl_close($ch); } } ``` Add the setup snippet to `functions.php`: ```php theme={null} Google Search Console** to connect a property and review Google Search performance alongside Qwairy data. ## Connect a property 1. Select **Connect Google Search Console**. 2. Authenticate with a Google account that can access the intended property. 3. Grant the requested read permission. 4. Select the property associated with the monitored brand. Check the exact domain and property type before confirming. Data availability follows Search Console's own processing and retention rules. ## Review queries and pages Use the query and page views to inspect the metrics returned by Search Console, including clicks, impressions, click-through rate, and position where available. Filters and sorting help identify records relevant to the selected date range. Search Console metrics describe Google Search, not AI-provider answers. ## Track a search as a prompt Use the available **Track** action to add a selected query to the workspace prompt set. Edit conversational or ambiguous wording before saving, then assign the relevant topic, funnel stage, and tags. Creating a prompt does not alter the Search Console record and does not guarantee that an AI provider receives the same wording from users. ## Export and troubleshoot Use the export control for the current view. If the page is empty, confirm the property, permissions, date range, and whether Search Console reports data for that period. ## Related pages * [Prompts](/documentation/workspace/prompts) * [Page Performance](/documentation/measure/page-performance) # Adobe Analytics Source: https://docs.qwairy.co/documentation/measure/integrations/adobe-analytics Connect an Adobe Analytics report suite for AI-referral traffic and configurable purchase or metric conversions. ## Connect Adobe Analytics 1. Open **Measure > Referrer Analytics** and choose **Adobe Analytics**. 2. Enter the OAuth Server-to-Server Client ID and Client Secret. 3. Enter the scopes requested by the connection form. 4. Select the report suite associated with the monitored domain. 5. Confirm the connection. Use credentials with only the access required for the selected report suite and follow your organization's rotation policy. ## Available data The connection can populate Referrer Analytics from Adobe referral and traffic dimensions. AI Revenue can use purchases or discovered Adobe metrics. Purchases support provider-native revenue where the connected data provides it. Other metrics can be counted as conversions and assigned a fixed value in Qwairy for an estimate. Confirm currency, allocation, and attribution settings before using any native value in a report. ## Validate and troubleshoot Compare the same report suite, segment, date range, and time zone in Adobe Analytics. If the connection fails, check the server-to-server credential, scopes, organization access, and report-suite permission. If a conversion is unavailable, verify that the metric exists and is visible to the integration. If purchases appear without measured revenue, confirm that the compatible revenue metric is populated for the same scope. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [Page Performance](/documentation/measure/page-performance) # Amplitude Source: https://docs.qwairy.co/documentation/measure/integrations/amplitude Connect an Amplitude project for AI-referrer users or events when referring-domain data is available. ## Connect Amplitude 1. Open **Measure > Referrer Analytics** and choose **Amplitude**. 2. Enter the project API key and Secret Key. 3. Select the project region. 4. Confirm the connection. Use a credential with the required read scope and follow your organization's storage and rotation policy. ## Available data Amplitude can populate Referrer Analytics when the project captures usable `referring_domain` values. Depending on the event model, the resulting measures may represent users or events rather than the session semantics used by another provider. Amplitude does not power AI Revenue in Qwairy. ## Validate and troubleshoot Check that the selected project and region are correct and that recent events contain the expected `referring_domain` property. Compare an equivalent date range and event definition in Amplitude. If the connection succeeds but the page is empty, inspect the source event schema before changing credentials. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [Referrer Analytics](/documentation/measure/referrer-analytics) # Google Analytics 4 Source: https://docs.qwairy.co/documentation/measure/integrations/google-analytics Connect a GA4 property for measured AI-referral traffic and configurable purchase or event conversions. ## Connect GA4 1. Open **Measure > Referrer Analytics**. 2. Choose **Google Analytics 4** and select **Connect**. 3. Sign in with a Google account that can read the intended property. 4. Grant the requested read access. 5. Select the GA4 property associated with the monitored domain. Check the property name, account, reporting time zone, and currency before confirming. ## Available data The connection can populate Referrer Analytics with GA4 sessions and engagement fields for traffic classified as AI referrals. AI Revenue can use purchases or discovered GA4 events. Purchases support provider-native ecommerce revenue. Other events can be counted as conversions and assigned a fixed value in Qwairy for an estimate. ## Validate and troubleshoot Compare the Qwairy period with a GA4 report using equivalent dates and referral dimensions. Differences can result from consent mode, attribution, thresholding, time zones, or referrer loss. If no properties appear, confirm the signed-in Google account and property permissions. If a conversion is unavailable, verify that the event occurred in the discovery period. If purchases appear without measured revenue, verify the ecommerce event parameters in GA4. Disconnecting stops future retrieval for this connection. Reconnecting may require selecting the property again. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [AI Revenue](/documentation/measure/ai-revenue) # Matomo Source: https://docs.qwairy.co/documentation/measure/integrations/matomo Connect a Matomo site for AI-referral traffic and configurable purchase or goal conversions. ## Connect Matomo 1. Open **Measure > Referrer Analytics** and choose **Matomo**. 2. Enter the Matomo instance URL. 3. Enter an authentication token with read access to the intended site. 4. Select the site associated with the monitored domain. 5. Confirm the connection. For a self-hosted instance, Qwairy must be able to reach the configured URL. Do not expose an internal-only endpoint solely to complete the connection without a security review. ## Available data The connection can populate Referrer Analytics with Matomo visits and engagement fields for traffic classified as AI referrals. AI Revenue can use purchases or discovered Matomo goals. Use a provider-native value when the selected source supports one, or assign a fixed value in Qwairy for an estimate. ## Validate and troubleshoot Compare the same site, dates, time zone, and referral segment in Matomo. If verification fails, check the instance URL, TLS configuration, token permissions, and site access. If a conversion is unavailable, verify the goal configuration and source data. If purchases appear without measured revenue, confirm ecommerce orders and currency in Matomo. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [AI Revenue](/documentation/measure/ai-revenue) # Analytics Integrations Source: https://docs.qwairy.co/documentation/measure/integrations/overview Choose an analytics connection for AI-referral traffic, revenue attribution, and page-level reporting. Connect one supported analytics source to populate **Referrer Analytics** and related Measure views. ## Supported providers | Provider | Status | Referrer Analytics | AI Revenue | | -------------------------------------------------------------------------- | ------ | :----------------: | --------------------------------- | | [Google Analytics 4](/documentation/measure/integrations/google-analytics) | Live | Yes | Purchases and events | | [Piwik PRO](/documentation/measure/integrations/piwik-pro) | Live | Yes | Purchases and events | | [Matomo](/documentation/measure/integrations/matomo) | Beta | Yes | Purchases and goals | | [Piano Analytics](/documentation/measure/integrations/piano-analytics) | Beta | Yes | Purchases and Transactions metric | | [Adobe Analytics](/documentation/measure/integrations/adobe-analytics) | Beta | Yes | Purchases and metrics | | [Amplitude](/documentation/measure/integrations/amplitude) | Beta | Yes | No | ## What a connection powers * **Referrer Analytics** uses the selected provider's referral and engagement data. * **AI Revenue** uses configured purchases, events, goals, or metrics on paid plans. Provider-native revenue and fixed-value estimates remain separate. * **Page Performance** can add GA4 AI-referral sessions to page-level signals. Provider metric definitions remain provider-specific. Qwairy does not make sessions, users, bounce rate, or revenue semantics identical across analytics products. ## Before connecting 1. Identify the property, site, project, or report suite for the monitored domain. 2. Confirm the required read permissions with its owner. 3. Check that referrer fields are populated. 4. For AI Revenue, verify the purchases, events, goals, or metrics you intend to configure, plus any provider-native revenue value you intend to report. 5. Use a credential that follows your organization's rotation and access policy. Connection and reconnection actions depend on your Qwairy team role. ## Validate the connection Compare a matching date range and segment in the source analytics product. Differences can result from time zones, attribution rules, consent, thresholds, sampling, or Qwairy's AI-referrer classification. ## Related pages * [Referrer Analytics](/documentation/measure/referrer-analytics) * [AI Revenue](/documentation/measure/ai-revenue) * [Page Performance](/documentation/measure/page-performance) # Piano Analytics Source: https://docs.qwairy.co/documentation/measure/integrations/piano-analytics Connect the Piano Analytics Data API for AI-referral traffic and configurable purchase or metric conversions. ## Connect Piano Analytics 1. Open **Measure > Referrer Analytics** and choose **Piano Analytics**. 2. Enter a Data API key with access to the intended site. 3. Enter the numeric Site ID. 4. Confirm the connection and the selected reporting scope. Treat the API key as a credential and rotate it according to your organization's policy. ## Available data The connection can populate Referrer Analytics from Piano traffic-source properties. AI Revenue offers purchases and the built-in `m_transactions` Transactions metric. Purchases support provider-native revenue where the connected data provides it. The Transactions metric can be counted as a conversion and assigned a fixed value in Qwairy for an estimate. Managers and Owners can enter another metric identifier manually. ## Validate and troubleshoot Compare equivalent dates, site scope, traffic-source filters, and time zones in Piano Analytics. If verification fails, check the API key permissions and Site ID. If Transactions is unavailable, verify that `m_transactions` is populated in the selected site. If purchases appear without measured revenue, verify that Sales Insights is enabled and contains revenue for the reporting period. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [AI Revenue](/documentation/measure/ai-revenue) # Piwik PRO Source: https://docs.qwairy.co/documentation/measure/integrations/piwik-pro Connect a Piwik PRO site for AI-referral traffic and configurable purchase or event conversions. ## Connect Piwik PRO 1. Open **Measure > Referrer Analytics** and choose **Piwik PRO**. 2. Enter the account address requested by the form. 3. Enter a Client ID and Client Secret with the required read access. 4. Select the site associated with the monitored domain. 5. Confirm the connection. Store and rotate the client credentials according to your organization's policy. Do not share them in support screenshots or public documentation. ## Available data The connection can populate Referrer Analytics with the sessions and engagement fields returned by Piwik PRO for classified AI-referral traffic. AI Revenue can use purchases or discovered Piwik PRO events. Purchases support provider-native ecommerce revenue. Other events can be counted as conversions and assigned a fixed value in Qwairy for an estimate. ## Validate and troubleshoot Compare equivalent dates, time zones, and referrer segments in Piwik PRO. If the connection fails, check the account address, credential scope, site access, and whether the API is available to the account. If a conversion is unavailable, verify that the event occurred in the discovery period. If purchases appear without measured revenue, verify ecommerce tracking and revenue fields in Piwik PRO before reconnecting. ## Related pages * [Analytics Integrations](/documentation/measure/integrations/overview) * [Referrer Analytics](/documentation/measure/referrer-analytics) # Page Performance Source: https://docs.qwairy.co/documentation/measure/page-performance Compare page-level signals from AI answers, GA4 referrals, retained crawler rollups, Google Search Console, and Bing Webmaster Tools. Page Performance is available on active paid plans. Each source contributes only when its own connection and entitlement requirements are met. Open **Measure > Page Performance** to compare several page-level data sources in one table. A tracked page is a normalized path present in at least one source, not a separate list of configured pages. The page can combine: * recorded source mentions in monitored AI answers; * GA4 sessions classified from recognized AI referrers; * observed crawler occurrences from retained daily rollups; * Google Search Console and Bing Webmaster Tools clicks, impressions, and position. The table exposes source connection states. Read a missing source as unavailable, not as an observed zero. The score has a documented exception below: disconnected non-crawler sources currently remain zero-valued in its percentile inputs. ## Summary and table The summary can show tracked paths, GA4 AI-referral sessions, observed crawler occurrences, and combined Google plus Bing clicks. The table keeps the underlying units separate and shows source-specific trends where comparison data is available. Use source-state indicators to distinguish connected, disconnected, empty, and unavailable data. Sort by the metric relevant to the current question rather than treating every column as the same type of evidence. ## About the score The score compares each page with the site's other pages using site-wide percentiles: * AI-answer source mentions: 40%; * GA4 AI-referral sessions: 25%; * crawler occurrences: 20%; * Google plus Bing clicks: 15%. If crawler detail is not measured, its component is `null` and the remaining 80% is normalized to 100%; the score is marked partial. Other disconnected sources currently remain zero-valued rather than having their weights removed. A folder filter does not recalculate the site-wide percentiles. The score is a relative Qwairy summary for the current site dataset. It is not an absolute quality grade, a search-engine ranking, or a forecast of AI visibility. ## Scope and coverage Crawler page detail is queried only for windows of 30 days or less. The dashboard reads at most 250 crawler page identities; it marks crawler detail as bounded when pagination, count precision, or occurrences without a retained 2xx page identity prevent an exact page-level match. Missing delivery is not inferred as zero. When crawler detail is not measured, the crawler occurrence value is `null`. Paths pseudonymized by Crawler Analytics are excluded from matching with GA4, Search Console, and Bing. ## Reconcile URLs Cross-source matching removes query strings and fragments and lowercases paths. It does not reliably merge trailing-slash variants, redirects, or canonical alternatives. Confirm those mappings before comparing totals. ## Page Performance in MCP The dashboard and the MCP tool answer different questions: * **Measure > Page Performance** combines page-level signals from the connected product surfaces listed above. * MCP `get_page_performance` returns retained Crawler Analytics rollups only. It does not return answer citations, referral sessions, Search Console metrics, or raw HTTP events. The MCP page-detail window covers at most 30 completed UTC days. `period: "all"` means 30 days, not lifetime history. Read the coverage caveats in the returned text before interpreting counts. The tool does not render a measured zero when no supported public occurrence exists. Pseudonymized paths are labelled as such in the rendered result and do not receive a page title. The tool also separates current pages from up to 10 neglected page identities and reports lifecycle states such as `new`, `hot`, and `neglected`. ## Related pages * [Source Explorer](/documentation/monitor/content-sources) * [Referrer Analytics](/documentation/measure/referrer-analytics) * [Crawler Analytics](/documentation/measure/crawler-analytics) * [Google Search Console](/documentation/measure/google-search-console) * [Bing Webmaster Tools](/documentation/measure/bing-webmaster) * [MCP Measure tools](/mcp/tools/measure) # Referrer Analytics Source: https://docs.qwairy.co/documentation/measure/referrer-analytics Analyze sessions that a connected analytics provider classifies as referrals from AI platforms. Open **Measure > Referrer Analytics** to review human traffic associated with detected AI referrers. ## Connect a provider Choose a supported provider from the connection flow: * Google Analytics 4; * Piwik PRO; * Matomo; * Piano Analytics; * Adobe Analytics; * Amplitude. See [Analytics Integrations](/documentation/measure/integrations/overview) for provider-specific requirements. ## Metrics and views Depending on the connected provider, the page can show sessions or users, duration, bounce or engagement behavior, pages per session, source distribution, trends, and landing pages. Metric names and semantics come from the analytics provider. For example, session, user, bounce, and duration definitions are not necessarily identical across providers. ## Attribution limits AI referrer classification relies on the referral information available in the analytics source. Traffic can be missed or grouped differently when: * a provider removes or changes the referrer; * a user copies a URL or moves across devices; * consent, blockers, or browser controls prevent measurement; * redirects or campaign parameters alter attribution; * the analytics provider applies thresholds or sampling. Use the results to describe measured AI-referral traffic, not total influence from AI platforms. ## Troubleshoot an empty view Confirm the connection, selected property or site, date range, permissions, and whether matching referrer values exist in the source system. ## Related pages * [AI Revenue](/documentation/measure/ai-revenue) * [Page Performance](/documentation/measure/page-performance) # Response Analysis Source: https://docs.qwairy.co/documentation/monitor/analyzing-answers Search and filter individual AI answers, then inspect the mentions, sources, and insight metadata attached to them. Open **Monitor > Response Analysis** to inspect the answers behind dashboard metrics. ## Filter answers The page supports filters for: * prompt text; * answer text; * funnel stage; * competitor status; * source status; * detected insights such as web search, shopping, social, local, sponsored, or no insights. Global brand, provider, list, and date controls may also affect the result set. ## Read a row Each row represents a captured answer. Open its details to review the prompt, provider, generated text, detected brands, citations or links, and available insight metadata. Use the raw answer to verify a dashboard conclusion. Detection is structured analysis of provider output, so review the surrounding text when classification or context matters. ## Common checks ### A metric has an unexpected denominator Check whether the answer contains at least one SELF or DIRECT brand or source. Mention Rate and Citation Rate exclude answers without the corresponding SELF or DIRECT denominator. ### A prompt is missing Clear filters, widen the date range, and confirm in **Workspace > Prompts** that the prompt was active for the selected run. ### An insight page is empty Filter for the corresponding insight on this page. If no matching metadata is present, the dedicated insight page may have no records for the current scope. ## Related pages * [Overview](/documentation/cockpit/performance-dashboard) * [Competitor Mentions](/documentation/monitor/competitor-mentions) * [Source Explorer](/documentation/monitor/content-sources) # Competitor Mentions Source: https://docs.qwairy.co/documentation/monitor/competitor-mentions Review detected brands, correct their relationship to your brand, and inspect competitor presence in monitored answers. Open **Monitor > Competitor Mentions** to review brands detected in monitored answers. ## Relationship tabs The page organizes records into the tabs that apply to the workspace: * **Competitors**: the monitored brand and brands classified as SELF or DIRECT. * **Related Brands**: brands classified as INDIRECT. * **To Review**: detections awaiting a relationship decision. * **Favorites**: favorited competitors, when present. * **Ignored**: brands excluded from competitive analysis. * **Processing**: records still being analyzed, when present. ## Why classification matters The dashboard's Mention Rate, Citation Rate, and Share of Voice calculations use SELF and DIRECT relationships. Changing a relationship can therefore change a historical view without changing the underlying answer text. Use DIRECT for a brand that should be included in the competitive denominator. Use INDIRECT for a related brand that you want to keep visible without treating it as a direct competitor. Ignore a detection when it should not participate in analysis. ## Review competitors You can add a competitor manually, edit a detected relationship, and favorite records used frequently in comparison views. The page also supports filters for detected shopping, local, and sponsored signals. Open a competitor to inspect its appearances in the current scope. A mention shows that a brand appeared in a captured answer. It does not establish recommendation, preference, market share, or a commercial relationship without further context. ## Related pages * [Compare](/documentation/cockpit/compare) * [Response Analysis](/documentation/monitor/analyzing-answers) * [Core Concepts](/documentation/get-started/core-concepts) # Source Explorer Source: https://docs.qwairy.co/documentation/monitor/content-sources Analyze cited domains and pages, ownership, usage type, and on-page presence across monitored answers. Open **Monitor > Source Explorer** to inspect sources associated with monitored answers. ## Tabs * **Domains** groups source activity by domain. * **Pages** lists individual source URLs. * **On-Page Presence** compares detected page content with brand presence where analysis is available. ## Filters Use the available filters to narrow by: * domain or page search; * source type; * own, competitor, third-party, or available-to-buy status; * top-level domain; * usage as Citation, Linked, or Grounding; * on-page presence or mention status. The usage labels describe how Qwairy classified the source in the captured provider output. They are not interchangeable across every provider. ## Investigate a source 1. Start with the domain view to understand its overall presence. 2. Open the Pages tab to identify the URLs behind that total. 3. Compare the source with the answers in **Response Analysis**. 4. Use On-Page Presence when you need to check whether the analyzed page mentions the selected brand. Source analysis may fetch a public page URL. Results can be incomplete when a page blocks access, changes after the answer was captured, or requires authentication. ## Related pages * [Response Analysis](/documentation/monitor/analyzing-answers) * [Backlink Opportunities](/documentation/act/backlink-opportunities) * [Site Diagnostics](/documentation/optimize/site-diagnostics) # Local Businesses Source: https://docs.qwairy.co/documentation/monitor/insights/local-intelligence Review businesses, categories, locations, and opportunities detected in local AI answer metadata. Open **Monitor > Insights > Local Businesses** to inspect local-business metadata detected in monitored answers. ## Summary cards The page shows **Business Mentions**, **Unique Businesses**, **Top Category**, and **Opportunities** for the active scope. ## Tabs * **Overview** summarizes local result activity. * **Businesses** lists detected businesses and their available details. * **Opportunities** groups detected gaps or comparison points for review. Use the available filters and row details to connect a business to its prompt, provider, and answer. ## Data limitations Names, categories, ratings, addresses, and other local details come from captured provider metadata. They can be missing, normalized differently, or become outdated. Verify operational details with the business or source before publishing them. ## Related pages * [Response Analysis](/documentation/monitor/analyzing-answers) * [Competitor Mentions](/documentation/monitor/competitor-mentions) # Query Fan-Out Source: https://docs.qwairy.co/documentation/monitor/insights/query-fan-out Review search queries detected within AI answers, their intents, brand mentions, and prompt opportunities. Open **Monitor > Insights > Query Fan-Out** to review search queries detected as part of provider answer generation. ## Summary cards The page shows: * **Total Queries**; * **Total Usages**; * **Brand Mention Rate**; * **Opportunities**. Read each value within the active date, provider, and workspace filters. ## Tabs * **Overview** lists detected queries and their usage information. * **Intents** groups queries by detected intent. Use search, prompt, and priority filters to narrow the table. ## Add a query to monitoring When a detected query belongs in the monitoring scope, add it to the prompt set. Review its wording, then assign a topic, funnel stage, and tags before saving. Adding a query creates a prompt for future monitoring. It does not change the answer in which the query was detected. Query fan-out metadata depends on what the provider returns and what Qwairy can detect. Absence from this page does not prove that a provider performed no internal query expansion. ## Related pages * [Prompts](/documentation/workspace/prompts) * [Response Analysis](/documentation/monitor/analyzing-answers) # Shopping Results Source: https://docs.qwairy.co/documentation/monitor/insights/shopping-intelligence Review products, stores, ratings, and brand opportunities detected in shopping-related AI answer metadata. Open **Monitor > Insights > Shopping Results** to inspect shopping metadata detected in monitored answers. ## Summary cards The page shows **Total Products**, **Total Stores**, **Avg Rating**, and **Opportunities** for the current scope. ## Tabs * **Overview** summarizes shopping results. * **Stores** groups detected products by store. * **Products** lists individual detected products. Use priority, store, competitor, and relationship filters to narrow the records. ## Interpret a record Shopping metadata reflects the provider output captured by Qwairy. Product names, prices, ratings, and availability may change after the answer was collected. Verify time-sensitive information at the referenced store before using it externally. An opportunity indicates a detected comparison or gap in the monitored dataset. It does not predict that a product change will alter future AI answers. ## Related pages * [Competitor Mentions](/documentation/monitor/competitor-mentions) * [Response Analysis](/documentation/monitor/analyzing-answers) # Social Signals Source: https://docs.qwairy.co/documentation/monitor/insights/social-intelligence Review social platforms and pages cited in monitored AI answers, including their contribution to the source mix. Open **Monitor > Insights > Social Signals** to inspect social sources detected in monitored answers. ## Summary cards * **Social Citations** counts citations classified as social. * **Social Share of Voice** is the share of citations in scope that are social citations. * **Top Platform** identifies the leading detected social platform for the current scope. * **Active Platforms** counts social platforms represented in the result set. ## Explore platforms The **Overview** tab summarizes the result set. Additional tabs can appear for platforms detected in the current scope. Use the page to identify the cited platform, page or discussion, related prompt, and provider. Open the underlying answer before deciding how the source influenced the response. A citation records source use in a monitored answer. It does not by itself measure reach, engagement, authority, or sentiment on the social platform. ## Related pages * [Source Explorer](/documentation/monitor/content-sources) * [Response Analysis](/documentation/monitor/analyzing-answers) # Sponsored Content Source: https://docs.qwairy.co/documentation/monitor/insights/sponsored-content Review ads, advertisers, products, and prompts detected in sponsored AI answer metadata. Open **Monitor > Insights > Sponsored Content** to inspect advertising metadata detected in monitored provider output. ## Summary cards The page shows **Total Ads**, **Advertisers**, **Prompts with Ads**, and **Products** for the current scope. ## Tabs * **Overview** summarizes detected sponsored records. * **Advertisers** groups records by advertiser. * **Products** lists products associated with detected ads. * **Prompts** shows which monitored prompts returned sponsored content. Open a record to connect the ad, advertiser, product, prompt, and provider where those fields are available. Sponsored metadata depends on provider, market, account context, and collection time. An empty result means no supported sponsored record was detected in the selected dataset, not that advertising was absent for every user. ## Related pages * [Response Analysis](/documentation/monitor/analyzing-answers) * [Shopping Results](/documentation/monitor/insights/shopping-intelligence) # Prompt Tracking Source: https://docs.qwairy.co/documentation/monitor/tracking-queries Review monitored prompts, their latest results, and detected signals without changing workspace configuration. Open **Monitor > Prompt Tracking** to review how the current prompt set is represented in monitoring data. ## Tracking tab The **Tracking** tab summarizes prompts and their latest available monitoring information. Use the page filters to narrow the current brand, date range, provider, topic, tag, or funnel scope where those controls are available. Select a prompt to move from the summary to its related answers or details. ## Signals tab The **Signals** tab groups detected changes or notable observations associated with monitored prompts. A signal reflects the data and comparison window shown in the interface. Check the underlying answers before treating it as a durable change. You can use the available share and export controls for the current signal view. ## Manage the prompt set Prompt Tracking is an analysis page. To add, edit, pause, schedule, tag, or boost a prompt, use [Workspace > Prompts](/documentation/workspace/prompts). Each prompt can: * inherit the workspace monitoring frequency; * override it with Daily, Weekly, Monthly, or No monitoring; * be paused and resumed; * request 2 to 20 answers per model and run with a boost. These settings affect future runs and credit estimates. They do not rewrite previously collected answers. ## Related pages * [Response Analysis](/documentation/monitor/analyzing-answers) * [Monitoring](/documentation/workspace/monitoring) * [Prompts](/documentation/workspace/prompts) # Site Diagnostics Source: https://docs.qwairy.co/documentation/optimize/site-diagnostics Crawl selected public pages, review technical and content checks, and track audit results over time. Open **Optimize > Site Diagnostics** to analyze public pages associated with the monitored brand. Unlike the answer-monitoring pipeline, Site Diagnostics fetches pages. Only submit URLs that Qwairy is permitted to access. ## Add pages Use the available sitemap synchronization or URL controls to create the page set. Review the discovered URLs and remove paths that should not be audited. Pages behind authentication, bot protection, or network restrictions may not be accessible. A failed fetch should be investigated separately from a failed page check. ## Run an audit An audit can examine technical access, metadata, content structure, and other signals shown in the interface. Open a page to see its current checks, affected elements, and suggested remediation. Each completed page audit or re-audit costs **5 credits**. A multi-page run is billed per completed page. See [Providers & Credits](/documentation/get-started/credits) for the operation table and calculation examples. Scores summarize the checks implemented by this feature. They are not search rankings and do not guarantee inclusion in an AI answer. ## Work through findings 1. Separate fetch errors from page findings. 2. Confirm the current page content in a browser. 3. Group repeated findings by template or component. 4. Prioritize according to business scope and implementation risk. 5. Re-run the relevant pages after deployment. Use audit history to compare results only when the page set and check configuration are comparable. ## Related pages * [Site Readiness](/documentation/optimize/site-readiness) * [Page Performance](/documentation/measure/page-performance) * [Source Explorer](/documentation/monitor/content-sources) # Site Readiness Source: https://docs.qwairy.co/documentation/optimize/site-readiness Check robots.txt policy for configured crawler and robots-policy identities, then review llms.txt guidance. Open **Optimize > Site Readiness** to review access signals for the monitored domain. ## Crawler and robots-policy access The access table checks configured crawler and robots-policy identities against the site's current robots.txt rules. Use it to identify whether each identity appears allowed, blocked, mixed by path, or could not be evaluated. Some entries are observable crawler User-Agents, such as GPTBot. Others are policy tokens, such as `Google-Extended`, that control permitted uses of content fetched by another crawler. A policy-token result cannot produce a Crawler Analytics occurrence or last-seen timestamp. The result is a robots-policy observation. It does not show that a provider has crawled, indexed, cited, or will use the site. Only the supported observable User-Agent subset can appear in Crawler Analytics. ## robots.txt The robots.txt section reads the public file and summarizes relevant directives. Review the raw file before changing it, especially when rules differ by user agent or path. If Qwairy cannot retrieve the file, check the URL, status code, redirects, firewall, and authentication requirements. ## llms.txt The llms.txt section helps you inspect or prepare a text file that describes selected site resources. Support and interpretation vary across AI systems. Publishing the file does not guarantee discovery or citation. If Qwairy generates suggested content, review every URL and description before placing it on the public site. ## Recommended review sequence 1. Confirm the monitored domain and protocol. 2. Inspect the current robots.txt response. 3. Review user-agent-specific rules. 4. Check the proposed llms.txt content, if used. 5. Coordinate deployment with the site owner. 6. Re-run the check after the public files change. ## Related pages * [Site Diagnostics](/documentation/optimize/site-diagnostics) * [Crawler Analytics](/documentation/measure/crawler-analytics) # Billing Source: https://docs.qwairy.co/documentation/team/billing Review the current plan, credit allocation, payment status, subscription controls, purchases, and invoices. Open **Team Management > Billing** to manage billing for the root team. Only the team Owner can access this page. Sub-teams inherit their parent organization's plan and do not have a separate Billing page. ## Current plan The plan card shows the subscription, billing period, renewal or scheduled change, and payment status available for the account. Use **Compare plans** to review plan options. When the subscription is linked to a billing customer, **Manage billing** opens the external billing portal for payment and subscription controls. ## Credits The credit card shows the current team balance and the plan allocation for the active billing period where available. An active subscription with a billing customer can expose **Buy credits**. Credit consumption and transaction detail are documented on [Usage](/documentation/team/usage). ## Payment history and invoices The payment history lists subscription payments and credit-pack purchases. Use the invoice actions on a row to view or download an available invoice. If an invoice link has expired, use the row action again to request a current link. ## Troubleshooting When an expected control is missing, check: * that you are the Owner of the root team; * the current subscription and payment status; * whether the account is linked to a billing customer; * whether the item is a plan payment or a one-time credit purchase. ## Related pages * [Usage](/documentation/team/usage) * [Providers & Credits](/documentation/get-started/credits) # Members Source: https://docs.qwairy.co/documentation/team/manage-members Invite team members, assign roles, manage Viewer brand access, and review pending invitations. Open **Team Management > Members** to review the team roster and invitations. ## Roles | Role | Verified product permissions | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | **Owner** | Can modify workspaces, consume credits, manage the team, access root-team billing, manage SSO, and transfer ownership. | | **Manager** | Can modify workspaces, consume credits, create brands, invite and manage members, and manage Viewers. Cannot access root-team billing or manage SSO. | | **Member** | Read-only access to every brand in the team. Cannot modify workspace data or consume credits. | | **Viewer** | Read-only access only to explicitly assigned brands. | The product can grant additional access to administrator accounts outside the team-role hierarchy. ## Invite a member Owner and Manager roles can: 1. select **Add Member**; 2. enter the email address; 3. choose Manager, Member, or Viewer; 4. assign brand access for a Viewer; 5. send the invitation. Managers and Members receive team-wide brand access according to their role. Viewers require explicit brand assignments. ## Manage an existing member Use the row menu to change an editable member's role or Viewer brand access. Role changes take effect across the team and can change both navigation and API permissions. The Owner has protected status. Follow the ownership-transfer workflow rather than trying to remove the Owner directly. ## Pending invitations Pending invitations can be resent or revoked by an Owner or Manager. Confirm that an invitation was sent to the intended address before resending. ## Related pages * [Viewers](/documentation/workspace/viewers) * [Virtual Teams](/documentation/team/virtual-teams) * [Team Settings](/documentation/team/settings) # Settings Source: https://docs.qwairy.co/documentation/team/settings Review the team identifier, change the display name, and see feature overrides and beta access on the account. Open **Team Management > Settings** for the current team's general information. ## Team ID The Team ID is the immutable identifier shown by the page. Use it when an integration or support workflow explicitly asks for the team scope. Do not confuse the Team ID with a Brand ID, API token, or billing-customer identifier. ## Team name The team name appears in the team switcher and team-level views. Owners and Managers can edit it; Members can read team settings but the update API requires Manager access or higher. Choose a name that identifies the organization or isolated sub-team without including confidential customer data unnecessarily. ## Add-ons and beta access When the account has feature overrides or beta grants, the page lists them under **Add-ons & beta access**. An override can have an expiration date; a beta feature can change or be removed before public release. This list describes account access. It does not replace the plan and billing information shown on the root team's [Billing](/documentation/team/billing) page. ## Related pages * [Members](/documentation/team/manage-members) * [Usage](/documentation/team/usage) # SSO Source: https://docs.qwairy.co/documentation/team/sso Configure an OIDC identity provider, verify email domains, enable sign-in, and optionally enforce SSO. SSO is available on the Enterprise plan or through an explicit feature override. Only the team Owner can manage it. Open **Team Management > SSO** to configure an OpenID Connect provider using the Authorization Code flow with PKCE. ## Prepare the identity provider Create a web OIDC application with the callback displayed on the Qwairy SSO page. The default callback path is: ```text theme={null} /api/auth/sso/callback ``` The origin must match the domain used for sign-in. A white-label custom domain therefore requires its own callback origin. Collect the issuer URL, Client ID, and Client Secret. The issuer should support OIDC discovery and JWKS retrieval. ## Create the connection 1. Enter a provider display name. 2. Enter the issuer URL, Client ID, and Client Secret. 3. Choose the default role for just-in-time provisioning. 4. Save the connection. 5. Use **Test OIDC Connection** to check discovery and signing-key access. The available default roles are Member and Viewer. A newly provisioned Viewer still needs brand access before viewing a workspace. ## Verify a domain Add the organization's email domain, then publish the TXT record shown by the interface. Select **Verify** after DNS can resolve the record. Each domain can belong to one SSO connection. Removing the last verified domain disables SSO and enforcement for that connection. ## Enable and enforce You need at least one verified domain before enabling SSO. Matching users can then authenticate through the configured provider and be provisioned with the default role. **Enforce SSO** requires SSO to be enabled. Test sign-in with the intended domain before enforcement. Users whose matching identity cannot authenticate may otherwise lose access. ## Troubleshooting * **Discovery failed**: check the issuer root URL and network access to its discovery document and JWKS. * **Redirect mismatch**: copy the exact origin and `/api/auth/sso/callback` path shown in Qwairy. * **Domain not verified**: confirm the TXT host and value in public DNS. * **User not provisioned**: confirm a verified email claim, matching domain, and default role. Deleting the SSO connection also removes its associated domain records from Qwairy. Coordinate the change before deletion. # Usage Source: https://docs.qwairy.co/documentation/team/usage Review the team credit balance, consumption, additions, projection, breakdowns, and transaction history. Open **Team Management > Usage** to analyze credit movements. The page is available to Member roles and higher; Viewer access is excluded by the usage API. ## Choose the scope The default is the current billing period. If the team has no active billing period, the default becomes the last 30 days. Available periods include Last 7 days, Last 30 days, Last 90 days, All time, and a custom date range. When the team has several brands, you can filter to one brand. Brand filtering scopes consumption rows. Team-level grants and adjustments remain team movements rather than brand consumption. ## Summary cards * **Balance** shows credits currently available and the period allocation where present. * **Credits used** sums feature-consumption transactions in the selected period. * **Credits added** covers plan allocations, purchases, and grants, with adjustments identified separately. * **Projection** estimates a runout date from the current burn rate when enough information is available. The projection is an extrapolation. Changes to monitoring, models, boosts, imports, or purchases can make it inaccurate. ## Charts and breakdowns Switch the consumption chart between Daily, Weekly, and Monthly views where the selected period supports them. Breakdowns show consumption by brand and by feature. A brand filter removes the redundant team-wide brand split. ## Transactions The **Activity** tab lists feature consumption with date, brand, feature, model, credits, and description. Filter by feature and model where options are available. The **Credits added** tab lists grants and other positive additions for the selected period. For model and operation costs, see [Providers & Credits](/documentation/get-started/credits). # Virtual Teams Source: https://docs.qwairy.co/documentation/team/virtual-teams Create isolated sub-teams, allocate credits and workspace quota, and move eligible workspaces within one organization. Virtual Teams is available on Business, Enterprise, and Agency Business plans, or through an explicit feature override. It is managed by Owners and Managers of the parent organization. Open **Team Management > Virtual Teams** from the parent organization. Sub-teams inherit the parent plan and do not have their own subscription or Billing page. ## Organization model * The parent holds the unallocated credit pool and workspace quota. * Each sub-team has its own members, brands, credit balance, and allocated workspace quota. * Virtual Teams are one level deep. A sub-team cannot create another sub-team. ## Create a sub-team Select **Create**, then enter a name, optional initial credits, and a workspace quota. You can also move eligible existing workspaces during creation. The sub-team quota must be at least 1 and cannot use more of the organization's available allocation than the preview allows. ## Allocate or reclaim credits Use **Allocate** to move credits from the parent pool to a sub-team. Use **Reclaim** to return available sub-team credits to the parent. Credit transfers are ledger adjustments, not feature consumption, so they do not enter the burn-rate calculation as usage. ## Change workspace quota Use **Quota** to change a sub-team allocation. It cannot be reduced below the sub-team's current number of live workspaces, and the organization envelope remains authoritative. ## Move a workspace The parent can move an eligible LIVE standard workspace between the parent and its active sub-teams, or between sub-teams. The move carries the workspace's reserved slot, so it is net-zero for the organization's total quota. Prompt, answer, source, competitor, analytics, and share-link records move with the workspace. Access changes to the destination team. Monitoring then uses the destination team's credits. If that balance is empty, future monitoring cannot consume credits until an allocation is added. While the background re-stamp is settling, the workspace shows a migration state and product reads or a second transfer can be temporarily blocked. Draft and audit workspaces are not eligible. ## Membership Switch to a sub-team and open **Team Management > Members** to manage its roster. A member of one sub-team does not automatically see sibling sub-teams. Use a sub-team when collaborators need active management inside an isolated boundary. Use a [Viewer](/documentation/workspace/viewers) for read-only access to selected brands in the same team. ## Related pages * [Members](/documentation/team/manage-members) * [Usage](/documentation/team/usage) * [Billing](/documentation/team/billing) # Activity Source: https://docs.qwairy.co/documentation/workspace/activity Follow scheduled, manual, onboarding, and new-prompt generation runs with their scope, progress, and status. Open **Workspace > Activity** to review generation runs for the selected brand. This page shows execution state. It does not replace the individual answers in Response Analysis or the credit ledger in Team Usage. ## Activity types Runs are labeled by purpose: * **Scheduled monitoring**; * **New prompts**; * **Manual analysis**; * **Onboarding**. A scheduled run can also display its Daily, Weekly, or Monthly frequency. Priority appears for urgent work where applicable. ## Scope and progress Each row shows: * the start time; * requested response count; * prompt and model counts; * completed, processing, failed, and remaining work in the progress display; * the current status. Statuses include Queued, Processing, Completed, Completed with warnings, Failed, Cancelled, and No work. **Completed with warnings** means the run finished with at least one non-successful item. Inspect the corresponding answers and provider results before treating the run as complete for reporting. **No work** means planning found no eligible generation for that run. Check prompt state, frequency, model selection, and credit availability. ## Live updates and history The page refreshes active runs and paginates older records. If live refresh fails, use **Retry** before assuming the run stopped. ## Related pages * [Monitoring](/documentation/workspace/monitoring) * [Prompts](/documentation/workspace/prompts) * [Response Analysis](/documentation/monitor/analyzing-answers) * [Usage](/documentation/team/usage) # Configuration Source: https://docs.qwairy.co/documentation/workspace/brand-settings Add a brand and configure competitor detection and domain scope for the current workspace. Open **Workspace > Configuration** to review workspace-level settings for the selected brand. ## Add a brand 1. Open the brand selector in the dashboard header. 2. Select **Add new brand**. 3. Complete the onboarding flow. 4. Review the generated brand profile, topics, prompts, and competitors. You can switch brands from the same selector. See [Quickstart](/documentation/get-started/quickstart) for the onboarding sequence. ## Auto-detect competitors When enabled, Qwairy can add brand detections from monitored answers to the competitor review workflow. Classify new records in **Monitor > Competitor Mentions** as DIRECT, INDIRECT, or IGNORED. Disabling automatic detection stops new automatic additions. It does not remove previously detected competitors. ## Limit pages to this subdomain By default, page and source views can include the registrable domain and its relevant subdomains. Enable **Limit pages to this subdomain** when the workspace should use the exact host and its sub-subdomains instead. For a workspace on `blog.example.com`: | Included | Excluded | | ---------------------------------------- | ---------------------------------------------------- | | `blog.example.com`, `*.blog.example.com` | `example.com`, `www.example.com`, `shop.example.com` | This setting changes page and source scope. It does not change brand-level mention, sentiment, or Share of Voice data. Domain scope can change which records appear in Source Explorer and page-level Measure views. Record the setting when comparing exports across workspaces. # Exports Source: https://docs.qwairy.co/documentation/workspace/exports Generate, monitor, download, and remove point-in-time data exports for the current workspace. Open **Workspace > Exports** to manage generated files. ## Export states | Status | Meaning | | -------------- | ----------------------------------------------------------------------- | | **Pending** | The request is queued. | | **Processing** | The file is being generated. | | **Completed** | The file is available for download. | | **Failed** | Generation did not complete. Review the scope and retry if appropriate. | The table can also show the export type, reporting period, row count, creator, and creation time. ## Download an export Download a completed export from its row action. The file is a snapshot of the data and filters used at generation time. Later monitoring runs, taxonomy changes, or verification decisions are not added to that file. ## Filter and remove exports Use the status or type filters to locate a request. Deleting an export removes the generated record or file available through this page; it does not delete the underlying Qwairy data. Before sharing a file, review its contents for confidential data and use an approved destination. ## Related pages * [Shared Links](/documentation/workspace/shared-links) * [Lists](/documentation/workspace/lists) # Lists Source: https://docs.qwairy.co/documentation/workspace/lists Save a named filter scope and reuse it across compatible dashboard and analysis views. Open **Workspace > Lists** to manage saved analysis scopes. ## Create a list Select **Add List**, enter a name, and configure the fields available in the form. A list can include provider, topic, tag, and period selections. Use a name that describes both the audience and scope, such as `Quarterly report: France, Core models`. ## Load a list Choose a saved list from a compatible view to apply its stored filters. Check the active filter chips after loading because a list may not control every page-specific filter. ## Edit or delete a list Editing a list changes the saved definition used the next time it is loaded. It does not rewrite an export generated from an earlier version. Deleting a list removes the preset, not the underlying prompts or answers. When a topic or tag is deleted, Qwairy removes that taxonomy reference from saved lists. ## Reporting practice Record the list name, date range, and relevant configuration with external reports. A mutable saved list is not by itself a durable record of an earlier filter state. ## Related pages * [Exports](/documentation/workspace/exports) * [Shared Links](/documentation/workspace/shared-links) # Monitoring Source: https://docs.qwairy.co/documentation/workspace/monitoring Set the workspace monitoring schedule, choose models, and review the credit estimate for prompt-level settings and boosts. Open **Workspace > Monitoring** to configure the default schedule and selected AI models for the current brand. Monitoring configuration is unavailable on the Free plan. Existing data remains separate from the ability to schedule new runs. ## Set the workspace default Choose one default frequency: * **No monitoring**; * **Daily**; * **Weekly**, with a weekday; * **Monthly**, with a day of the month. This value is the default for prompts set to **Auto**. Save the page after changing the schedule. ## Select models Choose the Core and Premium models to use for scheduled monitoring: * **Core models** capture a supported public AI experience and cost 1 credit per prompt-model answer. * **Premium models** use official provider APIs and cost 2 to 20 credits per prompt-model answer. Model cost is displayed in the interface and documented in [Providers & Credits](/documentation/get-started/credits). Changing the model set affects future runs. It does not regenerate historical answers. ## Prompt-level overrides Open **Workspace > Prompts** to configure individual prompts. Each prompt can: * inherit the default with **Auto**; * use Daily, Weekly, Monthly, or No monitoring; * be paused or resumed; * use a boost. Bulk actions can apply frequency, pause, resume, or boost changes to several prompts. ## Boosts A boost requests **2 to 20 answers per model and run** for a prompt. The workspace also enforces the boosted-prompt limit shown in the interface. Boosted prompts consume credits for every generated answer. Use the page estimate to review the combined effect of prompt frequencies, selected models, and boosts before saving. ## Estimate limitations The displayed credit estimate reflects the current configuration. Actual use depends on which scheduled or manual generations run and which answers complete. ## Related pages * [Prompts](/documentation/workspace/prompts) * [Providers & Credits](/documentation/get-started/credits) # Notifications Source: https://docs.qwairy.co/documentation/workspace/notifications Enable or disable the weekly email report for the current brand and understand related transactional emails. Open **Workspace > Notifications** to configure the weekly report for the selected brand. ## Weekly report Use the **Weekly report** toggle to enable or disable the email summary. The setting applies to the current brand and the cadence is weekly; this page does not provide a custom frequency selector. Recipients depend on team access to the brand. Review membership and viewer access when an expected recipient is missing. ## Related emails Other Qwairy emails are controlled by the workflow that creates them. For example, an export can send a completion email when that option is enabled, and team-level credit notifications relate to billing and usage settings. Turning off the workspace weekly report does not disable required account, security, billing, or transactional messages. ## Troubleshooting Confirm the current brand, the toggle state, the recipient's access, and the account email address. Delivery can also be affected by mail filtering outside Qwairy. ## Related pages * [Exports](/documentation/workspace/exports) * [Billing](/documentation/team/billing) * [Viewers](/documentation/workspace/viewers) # Personas Source: https://docs.qwairy.co/documentation/workspace/personas Create audience profiles that provide optional context to supported content-generation workflows. Open **Workspace > Personas** to manage audience context for the current brand. ## Create a persona Select **Add Persona** and describe the audience using the fields available in the form. Use a clear name and include only information needed for the content task. Avoid personal data or assumptions about a real individual. A persona should represent a research-backed audience segment. ## Edit or delete a persona Editing a persona changes its current context for future supported generation. It does not rewrite briefs that were already generated. Deleting a persona removes it from future selection. Review active editorial work before removal. ## Use in Content Studio Choose a persona when a Content Studio workflow exposes that option. The persona guides generated tone and focus, but the output still requires editorial and factual review. ## Related pages * [Content Studio](/documentation/act/content-studio) * [Content Opportunities](/documentation/act/content-opportunities) # Prompts Source: https://docs.qwairy.co/documentation/workspace/prompts Add, organize, schedule, pause, boost, regenerate, and remove the prompts monitored for a brand. Open **Workspace > Prompts** to manage the questions in the current brand's monitoring scope. ## Add prompts Use **Add Prompt** for manual entry. Review the prompt text, topic, funnel stage, tags, and monitoring settings before saving. Use **Generate Prompts** to create suggestions from brand and taxonomy context. Generated prompts should be reviewed for market, language, duplication, and intent. Creating prompt suggestions, adding prompts manually, and importing prompt definitions are free. If automatic answers run after prompts are added, each prompt consumes the sum of the models configured and available for the workspace. A boost multiplies the number of answers billed for each model. ## Organize the table Search and filter by the fields available on the page, including topic, tag, funnel, and prompt text. Select rows to apply bulk actions. A prompt can have one topic, multiple tags, and one funnel stage. Editing those fields changes future grouping and can affect historical breakdowns that use the current taxonomy projection. ## Configure monitoring per prompt Each prompt supports: * **Auto**, which inherits the workspace default; * Daily, Weekly, Monthly, or No monitoring; * pause and resume; * a boost of 2 to 20 answers per model and run. Use the bulk controls to apply frequency, pause, resume, topic, tag, or boost changes where available. The workspace's boosted-prompt limit still applies. ## Regenerate Use the generation action to run selected prompts on chosen models. Confirm the model costs and number of generated answers before starting. ## Delete prompts Deleting a prompt hard-deletes the prompt and its dependent monitoring data. Use the confirmation dialog and export required evidence before deletion. This differs from deleting a topic, which keeps prompts and leaves them unassigned. ## Related pages * [Monitoring](/documentation/workspace/monitoring) * [Topics](/documentation/workspace/topics) * [Tags](/documentation/workspace/tags) * [Prompt Tracking](/documentation/monitor/tracking-queries) # Shared Links Source: https://docs.qwairy.co/documentation/workspace/shared-links Create, review, expire, and revoke links that expose selected Qwairy views outside the signed-in workspace. Open **Workspace > Shared Links** to manage shareable views. ## Share types Available share types can include Overview, Perception, GEO Matrix, competitor comparison, and Prompt Signals. Create a link from the share control on a supported page, then give it a recognizable name and expiration where available. The Shared Links table shows its type, creator, dates, status, and view activity. ## Manage access Use row actions to copy or open an active link, edit its expiration, or revoke it. Revocation prevents further access through that link. A shared link can be opened without the recipient joining your team. Review the visible filters and data before sending it, choose an expiration, and revoke it when access is no longer needed. The link reflects the share behavior defined by its type. Confirm whether the recipient sees a snapshot or an interactive view before relying on it for a fixed report. ## Related pages * [Viewers](/documentation/workspace/viewers) * [Exports](/documentation/workspace/exports) # Tags Source: https://docs.qwairy.co/documentation/workspace/tags Create reusable labels, assign several tags to a prompt, and filter Qwairy views by custom groupings. Open **Workspace > Tags** to manage reusable labels for prompts. Tags support overlapping groupings such as campaigns, markets, audiences, or internal reporting sets. A prompt can have multiple tags. ## Create and edit a tag Select **Add Tag**, enter a name, choose a color, and save. Keep names distinct enough to remain understandable in filters and exports. Renaming or recoloring a tag updates its current presentation on assigned prompts. ## Assign tags Assign tags from a prompt or with the bulk actions in **Workspace > Prompts**. Use tags in the dashboard and analysis filters that expose them. ## Delete a tag Deleting a tag removes its assignments and taxonomy references. It does not delete the prompts or their answers. If the tag defined an external reporting segment, record the mapping before deletion. ## Topics and tags Use a topic for one primary thematic assignment. Use tags for several optional labels that can overlap. ## Related pages * [Prompts](/documentation/workspace/prompts) * [Topics](/documentation/workspace/topics) * [Lists](/documentation/workspace/lists) # Topics Source: https://docs.qwairy.co/documentation/workspace/topics Group prompts into thematic topics and use those groups in dashboard filters and breakdowns. Open **Workspace > Topics** to manage the thematic groups used by the current brand. Topics appear in views such as GEO Matrix, Compare, and Content Opportunities. A prompt can be assigned to one topic or remain unassigned. ## Add and rename topics Select **Add Topic**, enter a distinct name, and save. A brand can have up to **5 topics**. Renaming a topic updates its label for assigned prompts and related views. Use names that describe a stable subject rather than a temporary campaign. Tags are more suitable for flexible or overlapping labels. ## Delete a topic Deleting a topic: * keeps the prompts; * sets their topic assignment to unassigned; * removes the deleted topic from saved taxonomy references; * cleans related topic projections used by analytical fact tables. It does not delete the prompt answers. Reassign the affected prompts if you want them included in another topic breakdown. Topic deletion changes historical topic-level grouping because the topic reference is removed. Export or record the prior mapping if it is needed for reporting. ## Related pages * [Prompts](/documentation/workspace/prompts) * [Tags](/documentation/workspace/tags) * [GEO Matrix](/documentation/cockpit/geo-matrix) # Viewers Source: https://docs.qwairy.co/documentation/workspace/viewers Grant and remove read-only access to selected brands for team members with the Viewer role. Open **Workspace > Viewers** to manage read-only brand access. Access to this administration page requires the role shown by the interface. ## Active viewers The active list shows Viewer members who can access the current brand. Removing access does not remove the person from the team or from other brands. ## Pending invitations The pending list shows Viewer invitations that have not been accepted. Revoke an invitation when the recipient or brand scope is no longer correct. ## Add a viewer You can invite a new Viewer by email or grant the current brand to an existing Viewer. Confirm the recipient and scope before sending. Viewers can read the product areas exposed to their brand-scoped role but cannot change prompts, monitoring, or workspace settings. **Action Center**, **Content Studio**, and the full-page **Agents** destination are hidden. A beta page can also remain hidden unless the team has access to that beta. ## Choose the right sharing method Use a Viewer for ongoing read-only access within the team. Use [Shared Links](/documentation/workspace/shared-links) for a specific shareable view. Use [Virtual Teams](/documentation/team/virtual-teams) when a separate team, workspace allocation, or operating boundary is required. # Connect Looker Studio Source: https://docs.qwairy.co/looker-studio/getting-started Connect Qwairy to Google Looker Studio and build reports from governed monitoring data The Qwairy Looker Studio connector exposes purpose-specific monitoring data sources in Google Looker Studio. Use them to build reports, combine Qwairy data with other sources, and control how metrics are presented.