{
  "swagger": "2.0",
  "info": {
    "title": "LLM Pulse API",
    "description": "REST API for the LLM Pulse AI visibility platform. Swagger 2.0 document generated from the OpenAPI 3.0 spec at /openapi.json, which is the canonical contract. OAuth 2.1 endpoints (/oauth/authorize, /oauth/token, /oauth/register, /oauth/jwks.json, /.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource) are at the host root, outside this document's /api/v1 basePath, so they are documented in the OpenAPI 3.0 spec only. For the full endpoint reference see /api-docs/llms-full.txt or https://api.llmpulse.ai/api-docs.",
    "version": "1.64.0",
    "contact": {
      "name": "LLM Pulse Support",
      "url": "https://llmpulse.ai",
      "email": "info@llmpulse.ai"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://llmpulse.ai/terms"
    }
  },
  "host": "api.llmpulse.ai",
  "basePath": "/api/v1",
  "schemes": [
    "https"
  ],
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "securityDefinitions": {
    "BearerAuth": {
      "type": "apiKey",
      "name": "Authorization",
      "in": "header",
      "description": "API key authentication. Keys are managed at https://llmpulse.ai/app/api_keys (Scale plan or above). Used for the REST API and as a headless option for /mcp. A key can be limited to one or several projects when it is created; the set cannot be changed later."
    },
    "OAuth2": {
      "type": "oauth2",
      "flow": "accessCode",
      "authorizationUrl": "https://api.llmpulse.ai/oauth/authorize",
      "tokenUrl": "https://api.llmpulse.ai/oauth/token",
      "scopes": {
        "mcp:read": "Read data via MCP tools",
        "mcp:write": "Create resources via MCP write tools"
      },
      "description": "OAuth 2.1 with PKCE-S256 and Dynamic Client Registration. Accepted on /mcp only, available on every plan and during eligible trials. Discovery metadata at /.well-known/oauth-authorization-server."
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Health check that validates an API key and, optionally, access to a project."
    },
    {
      "name": "Account",
      "description": "Plan, subscription window, quota consumption and API rate limits for the authenticated account. Read it before spending quota so an integration can see its remaining budget instead of discovering the ceiling by hitting it."
    },
    {
      "name": "Projects",
      "description": "A project is one tracked brand. List and read projects, see which AI models and locales have data, create a project in one call, or drive the multi-step draft wizard."
    },
    {
      "name": "Metrics",
      "description": "Aggregated visibility, share of voice, citation and position data for the project and its competitors: time series, period summary, per-prompt summary, Share of Voice and top cited sources."
    },
    {
      "name": "Competitors",
      "description": "The brands tracked alongside your own. Competitor ids are the actor_id values the metrics endpoints report against."
    },
    {
      "name": "Prompts",
      "description": "The questions executed against the AI models every week, and the execution records each run produces."
    },
    {
      "name": "Collections & Tags",
      "description": "Collections (labelled Tags in the app) group prompts by topic, funnel stage or campaign so metrics can be filtered by them."
    },
    {
      "name": "Answers",
      "description": "The AI responses behind every metric, with their full text, mentions, citations and sentiment analysis."
    },
    {
      "name": "Mentions & Citations",
      "description": "The raw records behind the visibility metrics: one row per brand mention or per cited URL, for the project brand, for competitors, or for both in a single stream."
    },
    {
      "name": "Sentiments",
      "description": "Sentiment records with their comments, topics and scores, plus the category catalog used to label them."
    },
    {
      "name": "Sources & Citation Intelligence",
      "description": "Every URL extracted from AI responses, plus grouped URL, domain and host views, page-cache metadata, mention evidence inside cited pages, occurrences and sanitized cached content."
    },
    {
      "name": "AI Model Insights",
      "description": "Aggregate AI Model Insights report endpoints (summary, position distribution, AI Overview results). All filter by executed_at and share a standard actor shape."
    },
    {
      "name": "AI & Agent Traffic",
      "description": "What AI sends to your site: referral traffic from AI assistants measured by the connected web analytics provider, and AI crawler traffic hitting your origin server. Scale plan and above."
    },
    {
      "name": "Shopping & Ads",
      "description": "Commercial surfaces inside AI answers: the product cards models return, the paid placements that run alongside them, and the local businesses they list for prompts with local intent. Shopping and ads need the Scale plan or above; local businesses are available on every plan."
    },
    {
      "name": "Owned Media & Communities",
      "description": "Which of your own channels and which community conversations AI answers cite: YouTube, Instagram, Facebook, TikTok, LinkedIn, app-store listings and Reddit, with each tab's summary, the answers behind one cited item, and the profiles a project claims. Growth plan and above."
    },
    {
      "name": "Reputation & Studies",
      "description": "Multi-model analyst reports: the monthly reputation scoring of your brand against its competitors, and the custom AI studies you define over any subjects and dimensions. Requires reputation monitoring."
    },
    {
      "name": "Search Console",
      "description": "Google Search Console performance data (impressions, clicks, CTR, average position) for projects with a connected GSC property: headline totals, time series, top queries and top pages. Growth plan and above."
    },
    {
      "name": "Custom Reports",
      "description": "The custom reports built under Reports > Custom Reports: list them, read one with the data of every widget as its page shows it, and email it to its recipients now. Scale plan and above."
    },
    {
      "name": "Recommendations",
      "description": "Recommendation runs that power the in-app Recommendations page, the endpoint that launches a new one, and the one that completes, archives or restores an item."
    },
    {
      "name": "GEO Writer",
      "description": "AI-powered GEO Writer tasks (formerly Content Intelligence). Tasks are processed asynchronously, so create one and poll it until it completes."
    },
    {
      "name": "Technical GEO Reports",
      "description": "Run the technical GEO analysis bundle, then list, poll and retrieve each completed report."
    },
    {
      "name": "GEO Audits",
      "description": "Audits re-run one technical GEO check type on a domain or URL, once or weekly/monthly. Each run is kept: compare runs check by check, follow issues across runs (new, fixed, regressed), accept known issues and read the alerts scheduled runs raised."
    },
    {
      "name": "Annotations",
      "description": "Mark a date on the project timeline (a campaign launch, a site migration) so charts show what happened when."
    },
    {
      "name": "Team",
      "description": "The account team: list members and pending invitations, invite someone by email with their projects and access, revoke an invitation or remove a member, with the same rules as the Team Members settings page. Not available to a key limited to some projects."
    },
    {
      "name": "Store Integrations",
      "description": "The endpoints behind the LLM Pulse app for Shopify: match a store to its project, push the orders AI assistants referred to the store and read them back, and turn catalog products into buyer prompts to track. They need an API key (Scale plan or above) and are not available to accounts with a white-label portal or an embed, which receive ERR_INTEGRATION_UNAVAILABLE (403)."
    },
    {
      "name": "Webhooks",
      "description": "Outbound event notifications: subscribe an HTTPS URL to project events and receive HMAC-signed deliveries with no polling. Scale plan and above."
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol endpoint for AI clients (Claude, ChatGPT, Cursor and other MCP-compatible assistants)."
    }
  ],
  "parameters": {
    "Output": {
      "name": "output",
      "in": "query",
      "description": "Rectangular output for BI tools (Tableau, Excel, Sheets, ELT). Omit for the default nested JSON. 'flat' returns the same metadata plus 'columns' and 'rows'; 'csv' returns those rows as text/csv. Errors are always returned as JSON.",
      "type": "string",
      "enum": [
        "flat",
        "csv"
      ]
    },
    "SovView": {
      "name": "view",
      "in": "query",
      "description": "Which Share of Voice projection to flatten. Only valid together with 'output'. 'over_time' (default) is one row per date and actor, 'current' the ranked snapshot, 'breakdown' the Top 4 plus Others.",
      "type": "string",
      "default": "over_time",
      "enum": [
        "over_time",
        "current",
        "breakdown"
      ]
    },
    "ProjectId": {
      "name": "project_id",
      "in": "query",
      "required": true,
      "description": "Project ID",
      "type": "integer"
    },
    "Competitors": {
      "name": "competitors",
      "in": "query",
      "description": "Comma-separated competitor IDs (unknown IDs return ERR_INVALID_PARAM)",
      "type": "string"
    },
    "Page": {
      "name": "page",
      "in": "query",
      "type": "integer",
      "default": 1,
      "minimum": 1
    },
    "PerPage": {
      "name": "per_page",
      "in": "query",
      "type": "integer",
      "default": 20,
      "maximum": 100,
      "minimum": 1
    },
    "Metrics": {
      "name": "metrics",
      "in": "query",
      "description": "Comma-separated list of metrics: mentions, citations, responses, mention_rate, visibility (alias for mention_rate), weighted_visibility, ai_visibility_score (alias for weighted_visibility), citation_rate, avg_position, avg_mention_position, net_sentiment, sentiment_very_positive, sentiment_positive, sentiment_neutral, sentiment_negative, sentiment_very_negative. Citations and citation_rate include visible citations and background source references; avg_position uses visible citations only.",
      "type": "string"
    },
    "Granularity": {
      "name": "granularity",
      "in": "query",
      "type": "string",
      "enum": [
        "day",
        "week",
        "month"
      ]
    },
    "GscSearchType": {
      "name": "search_type",
      "in": "query",
      "description": "Which search surface to measure. Defaults to web. discover and googleNews carry no query dimension, so Google rejects /search_console/queries for them.",
      "type": "string",
      "default": "web",
      "enum": [
        "web",
        "image",
        "video",
        "news",
        "discover",
        "googleNews"
      ]
    },
    "GscFilters": {
      "name": "filters",
      "in": "query",
      "description": "Narrow the query; every entry must match (AND). Send the whole list as one JSON value: filters=[{\"dimension\":\"page\",\"operator\":\"contains\",\"expression\":\"/blog/\"}] (URL-encoded). An array of objects has no query-parameter form a generated client can produce, so the string is what the official SDKs send; see the SearchConsoleFilters schema for the shape it encodes. includingRegex and excludingRegex take RE2 syntax. At most 10 entries, expression at most 500 characters. The bracket form filters[][dimension]=page&filters[][operator]=contains&filters[][expression]=/blog/ is also accepted.",
      "x-example": "[{\"dimension\":\"page\",\"operator\":\"contains\",\"expression\":\"/blog/\"}]",
      "type": "string"
    },
    "GscFrom": {
      "name": "from",
      "in": "query",
      "description": "Start date (YYYY-MM-DD). Defaults to 30 days ago.",
      "type": "string",
      "format": "date"
    },
    "GscTo": {
      "name": "to",
      "in": "query",
      "description": "End date (YYYY-MM-DD), inclusive. Defaults to today. Google publishes data with a 2-3 day lag, so data_through in the response says how far it reaches.",
      "type": "string",
      "format": "date"
    },
    "GscRange": {
      "name": "range",
      "in": "query",
      "description": "Number of days to look back, today included (alternative to from/to). At most 487, Google's retention.",
      "type": "integer",
      "maximum": 487,
      "minimum": 1
    },
    "GscAggregationType": {
      "name": "aggregation_type",
      "in": "query",
      "description": "How Google counts impressions. auto (default) is what Search Console shows (by page when page is grouped or filtered, else by property). Google rejects byProperty with a page dimension or filter.",
      "type": "string",
      "default": "auto",
      "enum": [
        "auto",
        "byPage",
        "byProperty"
      ]
    },
    "GscStartRow": {
      "name": "start_row",
      "in": "query",
      "description": "Google ranks rows by clicks and returns at most 25,000 per request. start_row moves that window down the ranking (e.g. 25000 for the next block); sort and pagination apply within the window.",
      "type": "integer",
      "default": 0,
      "minimum": 0
    },
    "GscDataState": {
      "name": "data_state",
      "in": "query",
      "description": "final (default) counts only rows Google has finalized. all also counts the most recent days, which are still being filled in and will change.",
      "type": "string",
      "default": "final",
      "enum": [
        "final",
        "all"
      ]
    },
    "Range": {
      "name": "range",
      "in": "query",
      "description": "Number of days to look back (alternative to from/to)",
      "type": "integer"
    },
    "From": {
      "name": "from",
      "in": "query",
      "type": "string",
      "format": "date-time"
    },
    "To": {
      "name": "to",
      "in": "query",
      "description": "End of the window. A date-only value such as 2026-09-01 covers that whole day. Pass a full timestamp to end the window earlier.",
      "type": "string",
      "format": "date-time"
    },
    "Model": {
      "name": "model",
      "in": "query",
      "description": "Filter by one AI model. It must be a model enabled for the API key's user: an unknown value, or a model the user has not enabled, returns 422 ERR_INVALID_PARAM with the valid values in the message.",
      "type": "string",
      "enum": [
        "chatgpt",
        "perplexity",
        "ai_mode",
        "ai_overview",
        "gemini",
        "copilot",
        "amazon_rufus",
        "claude",
        "grok",
        "deepseek",
        "naver_ai",
        "baidu_ai",
        "meta_ai"
      ]
    },
    "MetricsModel": {
      "name": "model",
      "in": "query",
      "description": "Filter by one AI model. It must be a model enabled for the API key's user: an unknown value, or a model the user has not enabled, returns 422 ERR_INVALID_PARAM with the valid values in the message. The legacy values google-ai-mode and google-ai-overview are also accepted, as ai_mode and ai_overview.",
      "type": "string",
      "enum": [
        "chatgpt",
        "perplexity",
        "ai_mode",
        "ai_overview",
        "gemini",
        "copilot",
        "amazon_rufus",
        "claude",
        "grok",
        "deepseek",
        "naver_ai",
        "baidu_ai",
        "meta_ai"
      ]
    },
    "CitationModel": {
      "name": "model",
      "in": "query",
      "description": "Filter by one AI model. Only chatgpt, ai_overview, ai_mode, perplexity, gemini, naver_ai and baidu_ai are accepted: any other value, including another model enabled for the API key's user, returns 422 ERR_INVALID_PARAM (invalid model). One of those seven that the user has not enabled also returns 422 ERR_INVALID_PARAM, with the valid values in the message.",
      "type": "string",
      "enum": [
        "chatgpt",
        "perplexity",
        "ai_mode",
        "ai_overview",
        "gemini",
        "copilot",
        "amazon_rufus",
        "claude",
        "grok",
        "deepseek",
        "naver_ai",
        "baidu_ai",
        "meta_ai"
      ]
    },
    "CollectionId": {
      "name": "collection_id",
      "in": "query",
      "description": "One collection/tag ID or a comma-separated list of IDs. A query value is always a string on the wire, so it is typed as one: the previous integer-or-string union made generators emit a wrapper type they could not serialize.",
      "x-example": "12,34",
      "type": "string",
      "pattern": "^\\d+(,\\d+)*$"
    },
    "CountryCode": {
      "name": "country_code",
      "in": "query",
      "description": "One ISO country code or a comma-separated list (e.g. US,GB,DE)",
      "type": "string"
    },
    "LanguageCode": {
      "name": "language_code",
      "in": "query",
      "description": "One ISO language code or a comma-separated list (e.g. en,es,de)",
      "type": "string"
    },
    "Prompt": {
      "name": "prompt",
      "in": "query",
      "description": "Filter by prompt ID",
      "type": "integer"
    },
    "PromptType": {
      "name": "prompt_type",
      "in": "query",
      "description": "One prompt type or a comma-separated list: informational, navigational, commercial, transactional",
      "type": "string"
    },
    "BrandKind": {
      "name": "brand_kind",
      "in": "query",
      "description": "Filter by brand kind: brand (own brand/products), brand_other (competitors/other brands), non_brand (generic, no brand named). For fair 1:1 brand-vs-competitor comparisons (visibility, share of voice), use non_brand: brand-focused prompts skew results toward the brand they name. The in-app Overview page applies non_brand by default.",
      "type": "string",
      "enum": [
        "brand",
        "brand_other",
        "non_brand"
      ]
    },
    "MentionFilter": {
      "name": "mention_filter",
      "in": "query",
      "description": "Filter by which brands are mentioned, as a two-axis matrix (your brand x competitors): mentions_you / not_mentions_you, mentions_competitor / not_mentions_competitor, and the four combined cells you_and_competitor, competitor_not_you (a rival wins and you are absent), you_not_competitor, no_brands (no tracked brand appears, i.e. open space). Combine with 'competitors' to narrow the competitor side to specific rivals; on a negative cell that reads 'none of these'. On /dimensions/sources it applies to the crawled content of each cited page instead of the answer text. The legacy value 'competitors_only' is still accepted as an alias of competitor_not_you.",
      "type": "string",
      "enum": [
        "mentions_you",
        "not_mentions_you",
        "mentions_competitor",
        "not_mentions_competitor",
        "you_and_competitor",
        "competitor_not_you",
        "you_not_competitor",
        "no_brands"
      ]
    },
    "CitationFilter": {
      "name": "citation_filter",
      "in": "query",
      "description": "Same two-axis matrix applied to the domains cited in the answer instead of the brands named in it. Independent of mention_filter; pass both to intersect them (e.g. mentions_you + not_cites_you finds answers that talk about you without linking to you).",
      "type": "string",
      "enum": [
        "cites_you",
        "not_cites_you",
        "cites_competitor",
        "not_cites_competitor",
        "you_and_competitor",
        "competitor_not_you",
        "you_not_competitor",
        "cites_no_brands"
      ]
    },
    "IncludeProject": {
      "name": "include_project",
      "in": "query",
      "type": "boolean",
      "default": true
    },
    "UrlSha256": {
      "name": "url_sha256",
      "in": "path",
      "required": true,
      "description": "64-character hex SHA-256 of the cited URL",
      "type": "string",
      "pattern": "^[a-f0-9]{64}$"
    }
  },
  "paths": {
    "/ping": {
      "get": {
        "tags": [
          "Health"
        ],
        "summary": "Health check",
        "x-llms-description": "Health check; validates the API key and optionally pings a project.",
        "description": "Validates the API key and optionally pings a project. Returns the authenticated user_id, project (if project_id is supplied), and a request_id.",
        "operationId": "ping",
        "x-rest-only": "Key and project health check; an MCP client checks its connection with the initialize handshake.",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Optional project to verify access for",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "API is healthy",
            "schema": {
              "type": "object",
              "properties": {
                "ok": {
                  "type": "boolean"
                },
                "user_id": {
                  "type": "integer"
                },
                "project": {
                  "$ref": "#/definitions/Project"
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Account plan, quota usage and rate limits",
        "x-llms-description": "Plan, subscription window, quota usage (prompts, projects, competitors, intelligence tasks) and API rate limits for the authenticated account.",
        "description": "Returns the account plan, tracking cadence, subscription window, how much of each quota is used (prompts, projects, competitors per project, monthly GEO Writer tasks, team members, active recurring GEO audits and manual GEO audit runs in the last 24 hours) and the published API rate limits. Limits resolve through the account owner, so a team member sees the capacity that applies to them. An unlimited quota returns limit and remaining as null with unlimited set to true, since Infinity is not representable in JSON. The subscription block is only present for callers who can access Billing and Plans in the app (the account owner, or a team member with billing access); everyone else gets the same response without that key. requests_per_minute is the ceiling of the key used for the call, not a fixed number. For a key limited to some projects, the response carries nothing about the rest of the account: plan, plan_name, subscription and limits.team_members are absent, api_key_project_ids lists the key's projects, limits.prompts.used counts those projects' prompts, limits.prompts, limits.intelligence_tasks and the two GEO audit quotas show only remaining and unlimited (the capacity the key can still spend, without the account total), and the projects quota is capped at the projects it reaches. tracking_frequency stays, as the cadence those projects' data is collected at.",
        "operationId": "getAccount",
        "x-mcp-tool": "get_account_usage",
        "responses": {
          "200": {
            "description": "Account usage snapshot",
            "schema": {
              "type": "object",
              "properties": {
                "plan": {
                  "type": "string",
                  "description": "Plan key (starter, growth, scale, ...). Absent for a key limited to some projects."
                },
                "plan_name": {
                  "type": "string",
                  "description": "Display name of the plan to show people (e.g. Scale++ for the scaleplusplus key). Absent for a key limited to some projects."
                },
                "tracking_frequency": {
                  "type": "string",
                  "description": "How often prompts run (weekly, daily, monthly, ...)"
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "owner",
                    "member"
                  ],
                  "description": "Whether the key belongs to the account owner or a team member"
                },
                "api_key_project_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  },
                  "description": "The projects the calling API key is limited to; null for a key that sees the whole account, and for OAuth",
                  "x-nullable": true
                },
                "subscription": {
                  "type": "object",
                  "description": "Billing state. Present only for callers who can access Billing and Plans in the app; absent otherwise.",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "trialing": {
                      "type": "boolean"
                    },
                    "current_period_ends_at": {
                      "type": "string",
                      "format": "date-time",
                      "x-nullable": true
                    }
                  }
                },
                "limits": {
                  "type": "object",
                  "properties": {
                    "prompts": {
                      "$ref": "#/definitions/AccountQuota"
                    },
                    "projects": {
                      "$ref": "#/definitions/AccountQuota"
                    },
                    "competitors_per_project": {
                      "$ref": "#/definitions/AccountCapacity"
                    },
                    "intelligence_tasks": {
                      "$ref": "#/definitions/AccountQuota"
                    },
                    "team_members": {
                      "$ref": "#/definitions/AccountCapacity"
                    },
                    "recurring_geo_audits": {
                      "$ref": "#/definitions/AccountQuota"
                    },
                    "geo_audit_manual_runs": {
                      "$ref": "#/definitions/AccountQuota"
                    }
                  }
                },
                "rate_limits": {
                  "type": "object",
                  "properties": {
                    "requests_per_minute": {
                      "type": "integer",
                      "description": "The ceiling enforced for the API key used on this call, which may be above the 300/min default."
                    },
                    "write_requests_per_minute": {
                      "type": "integer",
                      "description": "Flat ceiling on write requests, the same for every key."
                    }
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          }
        }
      }
    },
    "/dimensions/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "x-llms-description": "List all projects accessible to the API key.",
        "operationId": "listProjects",
        "x-mcp-tool": "list_projects",
        "description": "All projects accessible with your API key.",
        "parameters": [
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Projects",
            "schema": {
              "type": "object",
              "properties": {
                "projects": {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/Project"
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/dimensions/projects/{id}": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Project details",
        "x-llms-description": "Detailed info for one project: matching names, locale, industry, app store IDs, prompt limit, stats.",
        "operationId": "getProjectDetails",
        "x-mcp-tool": "get_project_details",
        "description": "Detailed info for one project: matching_names, additional_owned_domains, industry, business model, primary products, target audience, brand voice, locale, app store IDs, prompt_limit (the project's own prompt cap, null without one), stats (incl. prompts_by_brand_kind counts and prompts_available, how many prompts can still be added: the lower of the project's free slots and the account's remaining prompts, null when neither caps it) and data_coverage (models, countries and languages with data).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Project details",
            "schema": {
              "$ref": "#/definitions/ProjectDetails"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/dimensions/models": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List models with data",
        "x-llms-description": "List AI models the project has execution data for.",
        "operationId": "listModels",
        "x-mcp-tool": "list_models",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "Models",
            "schema": {
              "$ref": "#/definitions/ModelsResponse"
            }
          }
        }
      }
    },
    "/dimensions/locales": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List locales with data",
        "x-llms-description": "List countries and languages with data for the project.",
        "operationId": "listLocales",
        "x-mcp-tool": "list_locales",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "Locales",
            "schema": {
              "$ref": "#/definitions/LocalesResponse"
            }
          }
        }
      }
    },
    "/project_drafts": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Start a project draft (wizard step 1)",
        "x-llms-description": "Start the multi-step project wizard: returns a draft_id + AI suggestions for the URL (name, description, industry, aliases).",
        "operationId": "createProjectDraft",
        "x-mcp-tool": "start_project_draft",
        "x-write": true,
        "description": "Start the multi-step project-creation wizard. Returns a draft_id plus AI suggestions (name, description, industry, brand aliases) for the URL. Cold URLs can take up to ~2 minutes to analyze; pass suggest=false to skip AI and respond instantly. Drafts expire after 24h. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "website_url",
                "main_country",
                "main_language"
              ],
              "properties": {
                "website_url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Public HTTP(S) URL with a DNS hostname or public IP address. Credentials, private and special IP addresses, localhost and internal hostnames are rejected."
                },
                "main_country": {
                  "type": "string"
                },
                "main_language": {
                  "type": "string"
                },
                "use_subdomain": {
                  "type": "boolean",
                  "default": false
                },
                "suggest": {
                  "type": "boolean",
                  "default": true
                },
                "execute_prompts_immediately": {
                  "type": "boolean",
                  "default": true
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Draft created; envelope with draft state, suggestions and limits"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "403": {
            "$ref": "#/responses/KeyProjectScoped"
          }
        }
      }
    },
    "/project_drafts/{id}": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Read a project draft",
        "x-llms-description": "Read a draft: state, current step, accumulated data; include_suggestions=true returns cached suggestions only.",
        "operationId": "getProjectDraft",
        "x-mcp-tool": "get_project_draft",
        "x-write": true,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Draft id (draft_...)",
            "type": "string"
          },
          {
            "name": "include_suggestions",
            "in": "query",
            "description": "Cache-only: returns suggestions for the current step if already generated, never triggers AI",
            "type": "boolean",
            "default": false
          }
        ],
        "responses": {
          "200": {
            "description": "Draft envelope"
          },
          "403": {
            "$ref": "#/responses/KeyProjectScoped"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Projects"
        ],
        "summary": "Submit a wizard step",
        "x-llms-description": "Submit a wizard step (details, prompts, competitors, owned_media) with strict forward gating; responds with next-step AI suggestions.",
        "operationId": "updateProjectDraft",
        "x-mcp-tool": "update_project_draft",
        "x-write": true,
        "description": "Submit one step (details, prompts, competitors, owned_media). Strict forward gating: a step is only accepted when every previous step is complete (`ERR_DRAFT_STATE` otherwise); completed steps can be resubmitted. Responds with the updated draft plus AI suggestions for the next step.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "step"
              ],
              "properties": {
                "step": {
                  "type": "string",
                  "enum": [
                    "details",
                    "prompts",
                    "competitors",
                    "owned_media"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "brand_name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "industry": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "matching_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "external_identifier": {
                  "type": "string"
                },
                "prompts": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "maxItems": 100
                },
                "competitors": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "youtube_channel_url": {
                  "type": "string"
                },
                "instagram_profile_url": {
                  "type": "string"
                },
                "facebook_page_url": {
                  "type": "string"
                },
                "tiktok_profile_url": {
                  "type": "string"
                },
                "app_store_url": {
                  "type": "string"
                },
                "google_play_url": {
                  "type": "string"
                },
                "suggest": {
                  "type": "boolean",
                  "default": true
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Draft envelope with next-step suggestions"
          },
          "403": {
            "$ref": "#/responses/KeyProjectScoped"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/project_drafts/{id}/finalize": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Finalize a draft into a real project",
        "x-llms-description": "Create the real project from a completed draft (idempotent; same effects as POST /projects).",
        "operationId": "finalizeProjectDraft",
        "x-mcp-tool": "finalize_project_draft",
        "x-write": true,
        "description": "Creates the project with all accumulated draft data (same effects as POST /projects). Idempotent: finalizing an already-finalized draft returns 200 with the existing project. Optional overrides: weekly_email_subscribed, execute_prompts_immediately.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": false,
            "schema": {
              "type": "object",
              "properties": {
                "weekly_email_subscribed": {
                  "type": "boolean"
                },
                "execute_prompts_immediately": {
                  "type": "boolean"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Project created",
            "schema": {
              "$ref": "#/definitions/ProjectCreateResponse"
            }
          },
          "200": {
            "description": "Idempotent replay"
          },
          "403": {
            "$ref": "#/responses/KeyProjectScoped"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/projects": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Create a project (fast mode)",
        "x-llms-description": "Create a complete project in one call (name, locale, prompts, competitors, owned media, Brand Book profile); idempotent via external_identifier.",
        "operationId": "createProject",
        "x-mcp-tool": "create_project",
        "x-write": true,
        "description": "Create a complete project in one call: project fields, prompts (queued for execution and categorization), collections tagging those prompts, competitors, weekly email subscription. The response lists same_domain_projects so an accidental duplicate is visible; it never blocks the create. Idempotent via `external_identifier` (embed-enabled accounts only; replay returns 200 with the existing project). Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/ProjectCreateRequest"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "schema": {
              "$ref": "#/definitions/ProjectCreateResponse"
            }
          },
          "200": {
            "description": "Idempotent replay (existing external_identifier)",
            "schema": {
              "$ref": "#/definitions/ProjectCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/KeyProjectScoped"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/projects/{id}": {
      "patch": {
        "tags": [
          "Projects"
        ],
        "summary": "Update a project profile (Brand Book)",
        "x-llms-description": "Update the project profile / Brand Book (brand_name, description, industry, business_model, target_audience, brand_voice, goals, primary_products, matching_names), additional_owned_domains (account owner only) and prompt_limit (account owner or a co-owner with Projects update). A matching_names or additional_owned_domains change reprocesses past answers in the background.",
        "operationId": "updateProject",
        "x-mcp-tool": "update_project",
        "x-write": true,
        "description": "Updates the project name and profile, the same fields as Project Settings: name, brand_name, description, industry, business_model (plus business_model_other when it is OTHER), target_audience, brand_voice, goals, primary_products, matching_names, and the additional owned domains (additional_owned_domains). Send only the fields to change; unknown fields are rejected, and a request is applied whole or not at all. All seven Brand Book fields feed every GEO Writer task and prompt suggestions; only industry, description, and target_audience help Recommendations. A matching_names change re-runs mention/citation matching over the project history in the background, and an additional_owned_domains change reprocesses the past answers that cite an added or removed domain (rematching=true); further edits are rejected while that runs. Only the account owner can send additional_owned_domains: a team member key gets ERR_INSUFFICIENT_PERMISSION and nothing is applied. prompt_limit sets or (null) removes the project's own prompt cap, as in Account Settings > Prompt Limits. Only the account owner or a co-owner whose permissions grant Projects update can send it (Account Settings > Prompt Limits lets any co-owner change it; here a co-owner, like every team member, also needs Projects update): any other team member gets ERR_INSUFFICIENT_PERMISSION, and a key limited to some projects ERR_KEY_PROJECT_SCOPED, with nothing applied. A request that sends only prompt_limit is applied even while a rematch runs. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Project name shown in the app. A label: it does not change mention detection unless brand_name is empty. Cannot be blank"
                },
                "brand_name": {
                  "type": "string",
                  "description": "Brand name used to detect mentions. Applies to future runs; it does not rewrite history"
                },
                "description": {
                  "type": "string",
                  "description": "What the brand does. Context for Recommendations and GEO Writer (Brand Book)"
                },
                "industry": {
                  "type": "string",
                  "description": "Single industry key (e.g. SAAS), stored as sent; an array of keys is also accepted and stored as an array, like the in-app multi-select. Unknown keys are rejected with the valid keys listed"
                },
                "business_model": {
                  "type": "string",
                  "description": "Business model key (e.g. B2B_SAAS); unknown keys are rejected"
                },
                "business_model_other": {
                  "type": "string",
                  "description": "Free-text business model, only accepted when business_model is OTHER; rejected against any other key"
                },
                "target_audience": {
                  "type": "string",
                  "description": "Who the brand sells to (Brand Book)"
                },
                "brand_voice": {
                  "type": "string",
                  "description": "Tone of voice guidance for generated content (Brand Book)"
                },
                "goals": {
                  "type": "string",
                  "description": "What the brand wants to achieve. Context for GEO Writer and prompt suggestions"
                },
                "primary_products": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Full replacement list of the main products or services"
                },
                "matching_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of the brand-name variants used to detect mentions; send every variant to keep"
                },
                "additional_owned_domains": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list (at most 10) of other domains the brand owns, such as a blog or a country site; [] clears it. Citations from them count as owned, like the project website. Each entry is a website's own domain and covers its subdomains; a pasted URL is stored as its domain (https://www.acme.co.uk/x becomes acme.co.uk). A list the domain editor would refuse (a subdomain such as blog.acme.com, a platform many companies share such as youtube.com, amazon.com or wordpress.com, the project website, a tracked competitor domain, a duplicate or an entry that is not a domain) is rejected with ERR_INVALID_PARAM and the reason. Order does not matter, and an unchanged list starts nothing. Account owner only, on every plan"
                },
                "prompt_limit": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 2147483647,
                  "description": "The most prompts this project can hold, a JSON integer from 1 to 2147483647; null removes the project's limit. A string, a decimal, 0 or a negative number is rejected with ERR_INVALID_PARAM. The account's plan allowance still applies, so the lower of the two wins (GET /dimensions/projects/{id} returns prompt_limit and stats.prompts_available). A limit below the current count keeps every prompt and refuses new ones until the project is under it; note says what the project can now hold. Account owner or a co-owner with Projects update permission only; a key limited to some projects answers ERR_KEY_PROJECT_SCOPED. The Swift, Kotlin, Dart and R clients leave a null field out of the request body, so they can set a limit but not remove one: remove it with a plain HTTP request that sends prompt_limit as null",
                  "x-nullable": true
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated",
            "schema": {
              "$ref": "#/definitions/ProjectUpdateResponse"
            }
          },
          "403": {
            "description": "A write with a read-only key answers ERR_INSUFFICIENT_SCOPE. A team member whose permissions do not grant Projects update (a co-owner included), who sends additional_owned_domains, or who sends prompt_limit without being a co-owner answers ERR_INSUFFICIENT_PERMISSION. A key limited to some projects that sends prompt_limit answers ERR_KEY_PROJECT_SCOPED. All with the same status, and nothing in the request is applied",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_INSUFFICIENT_PERMISSION",
                  "message": "insufficient_team_member_permission"
                },
                "request_id": "abc-123"
              }
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/metrics/timeseries": {
      "get": {
        "tags": [
          "Metrics"
        ],
        "summary": "Time-series metrics",
        "x-llms-description": "Time-series for one or more metrics, broken down by actor (brand + competitors).",
        "description": "Returns time-series data for one or more metrics, broken down by actor (project + competitors). Supports day/week/month granularity, with sticky carry-forward semantics for week/month aggregates.",
        "operationId": "getTimeseries",
        "x-mcp-tool": "get_timeseries",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Metrics"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/MetricsModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/IncludeProject"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Time-series data",
            "schema": {
              "$ref": "#/definitions/TimeseriesResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/metrics/summary": {
      "get": {
        "tags": [
          "Metrics"
        ],
        "summary": "Aggregated metrics summary",
        "x-llms-description": "Aggregated period summary with totals (sum for counts, average for rates; see aggregation field), min/max/last, plus position distribution.",
        "description": "Same as /metrics/timeseries but adds a `summary` block with total/min/max/last per metric per actor, plus a `position_distribution` block (Position 1, Position 2, Position 3+). Citations and citation rate include visible citations and background source references. Background references use position 0 and are excluded from avg_position and position distributions. `total` is a SUM for count metrics (mentions, citations, responses) and an AVERAGE across periods for rate/percentage and average metrics (visibility/mention_rate, citation_rate, ai_visibility_score, sentiment shares, avg_position, avg_mention_position, net_sentiment); rates are never summed. Each summary row carries an `aggregation` field (`sum` or `average`).",
        "operationId": "getSummary",
        "x-mcp-tool": "get_summary",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Metrics"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/MetricsModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Summary metrics",
            "schema": {
              "$ref": "#/definitions/SummaryResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/metrics/prompt_summary": {
      "get": {
        "tags": [
          "Metrics"
        ],
        "summary": "Per-prompt metrics summary",
        "x-llms-description": "Paginated per-prompt metrics (responses, mentions, citations, mention_rate, citation_rate, avg position, brand sentiment) with each prompt's country, language, type, brand focus and tags.",
        "description": "Paginated per-prompt aggregated metrics. Returns responses, mentions, citations, mention_rate, citation_rate, avg_mention_position and avg_position per prompt, plus the brand sentiment of its responses (sentiment_responses, the five sentiment percentages and net_sentiment, computed like the sentiment timeseries) and the prompt's classification (country_code, language_code, prompt_type, brand_kind and tags, as in /dimensions/prompts). Citations and citation rate include visible citations and background source references; avg_position uses visible citations only. Pass `breakdown=model` to split each prompt by model.",
        "operationId": "getPromptSummary",
        "x-mcp-tool": "get_prompt_summary",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "name": "breakdown",
            "in": "query",
            "description": "Add per-(prompt, model) rows to the output",
            "type": "string",
            "enum": [
              "model"
            ]
          },
          {
            "$ref": "#/parameters/MetricsModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "name": "sort",
            "in": "query",
            "type": "string",
            "default": "responses",
            "enum": [
              "responses",
              "mentions",
              "citations",
              "mention_rate",
              "visibility",
              "citation_rate",
              "avg_mention_position",
              "avg_position"
            ]
          },
          {
            "name": "sort_dir",
            "in": "query",
            "type": "string",
            "default": "desc",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-prompt metrics",
            "schema": {
              "$ref": "#/definitions/PromptSummaryResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          }
        }
      }
    },
    "/metrics/sov": {
      "get": {
        "tags": [
          "Metrics"
        ],
        "summary": "Share of Voice",
        "x-llms-description": "Share of Voice for the project vs competitors (over time, current snapshot, Top-4 + Others breakdown).",
        "description": "Share of Voice breakdown comparing your project to competitors. Returns over_time, current snapshot, and a Top-4 + Others breakdown.",
        "operationId": "getShareOfVoice",
        "x-mcp-tool": "get_sov",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/MetricsModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Output"
          },
          {
            "$ref": "#/parameters/SovView"
          }
        ],
        "responses": {
          "200": {
            "description": "Share of voice data",
            "schema": {
              "$ref": "#/definitions/SovResponse"
            }
          }
        }
      }
    },
    "/metrics/top_sources": {
      "get": {
        "tags": [
          "Metrics"
        ],
        "summary": "Top cited sources",
        "x-llms-description": "Top cited domains across AI responses with sortable mention/citation stats.",
        "description": "Registrable domains most frequently cited in AI responses for the project, including visible citations and background source references. This endpoint remains a domain rollup when exact-subdomain matching is enabled. Results can be sorted by total responses, average mention rate, or average visibility.",
        "operationId": "getTopSources",
        "x-mcp-tool": "get_top_sources",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/MetricsModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "name": "sort",
            "in": "query",
            "type": "string",
            "default": "total_responses",
            "enum": [
              "total_responses",
              "avg_mention_rate",
              "avg_visibility"
            ]
          },
          {
            "name": "query",
            "in": "query",
            "description": "Filter domains by case-insensitive partial match",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Top sources",
            "schema": {
              "$ref": "#/definitions/TopSourcesResponse"
            }
          }
        }
      }
    },
    "/dimensions/competitors": {
      "get": {
        "tags": [
          "Competitors"
        ],
        "summary": "List competitors",
        "x-llms-description": "List competitors for a project (optionally including the project brand as actor_type=project).",
        "operationId": "listCompetitors",
        "x-mcp-tool": "list_competitors",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "include_project_brand",
            "in": "query",
            "description": "When true, prepends the project brand with actor_type=project and is_own=true",
            "type": "boolean",
            "default": false
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Competitors",
            "schema": {
              "type": "object",
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "competitors": {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/Competitor"
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/dimensions/competitors/{id}": {
      "get": {
        "tags": [
          "Competitors"
        ],
        "summary": "Competitor details",
        "x-llms-description": "Detailed info for one competitor: matching names, app store IDs, color.",
        "operationId": "getCompetitorDetails",
        "x-mcp-tool": "get_competitor_details",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Competitor details",
            "schema": {
              "$ref": "#/definitions/CompetitorDetails"
            }
          }
        }
      }
    },
    "/competitors/suggestions": {
      "get": {
        "tags": [
          "Competitors"
        ],
        "summary": "List competitor suggestions",
        "x-llms-description": "The Competitors page's Suggested tab, competing brands the AI discovery proposed or the most cited domains not tracked yet.",
        "operationId": "listCompetitorSuggestions",
        "x-mcp-tool": "list_competitor_suggestions",
        "description": "The two lists of the Suggested tab of the Competitors page. `basis=brands` (default) returns the competing brands the AI discovery proposed that are not tracked or rejected yet, with the names it would match and, when counted, how many answers name it (`mentions`). `basis=citations` returns the domains AI answers cite most that are not your own or tracked yet, with how many answers cite each (`citations`). `discovery` tells whether a discovery run is still loading. Read-only: it never starts a discovery run, which the page starts from its Discover button. Add a suggestion with POST /competitors.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "basis",
            "in": "query",
            "type": "string",
            "default": "brands",
            "enum": [
              "brands",
              "citations"
            ]
          },
          {
            "name": "query",
            "in": "query",
            "description": "Only suggestions whose brand name or domain contains this text",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated suggestions",
            "schema": {
              "$ref": "#/definitions/CompetitorSuggestionsResponse"
            }
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/competitors": {
      "post": {
        "tags": [
          "Competitors"
        ],
        "summary": "Add a competitor",
        "x-llms-description": "Add a competitor (brand_name + domain) to a project; respects the plan competitor cap.",
        "operationId": "createCompetitor",
        "x-mcp-tool": "create_competitor",
        "x-write": true,
        "description": "Adds a competitor with its own citation URL matching rule. Honours the per-plan max competitors cap. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "brand_name",
                "domain"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "brand_name": {
                  "type": "string"
                },
                "domain": {
                  "type": "string",
                  "description": "URL is accepted and normalised to host (e.g. https://www.openai.com → openai.com)"
                },
                "matching_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "citation_match_mode": {
                  "type": "string",
                  "enum": [
                    "domain",
                    "host",
                    "path_prefix"
                  ],
                  "default": "domain",
                  "description": "domain includes the registrable domain and all subdomains; host requires the exact hostname; path_prefix also requires citation_match_path"
                },
                "citation_match_path": {
                  "type": "string",
                  "description": "Required when citation_match_mode=path_prefix, e.g. /es. Case-sensitive; trailing slash is optional; query and fragment are ignored"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "schema": {
              "$ref": "#/definitions/CompetitorCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/competitors/{id}": {
      "patch": {
        "tags": [
          "Competitors"
        ],
        "summary": "Update a competitor",
        "x-llms-description": "Update a competitor (brand_name, website domain or host, matching_names, color, citation rule). Name, website or rule changes re-run matching in the background.",
        "operationId": "updateCompetitor",
        "x-mcp-tool": "update_competitor",
        "x-write": true,
        "description": "Updates brand_name, the competitor website domain or host, matching_names (full replacement list; the brand name is always included automatically), color and/or the citation URL matching rule. Website domain/host and citation-rule changes share one seven-day cooldown per competitor; other fields remain editable during the cooldown. Name, website or citation-rule changes re-run historical matching in the background: the competitor shows processing=true for a few minutes and further edits are rejected meanwhile. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "brand_name": {
                  "type": "string"
                },
                "domain": {
                  "type": "string",
                  "description": "Website domain or host used for citation matching. A full URL is accepted and normalised to its host."
                },
                "matching_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "color": {
                  "type": "string",
                  "description": "Hex color, e.g. #1a2b3c"
                },
                "citation_match_mode": {
                  "type": "string",
                  "enum": [
                    "domain",
                    "host",
                    "path_prefix"
                  ]
                },
                "citation_match_path": {
                  "type": "string",
                  "description": "Required when changing citation_match_mode to path_prefix"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Competitors"
        ],
        "summary": "Delete a competitor",
        "x-llms-description": "Delete a competitor (irreversible; frees a competitor slot; its mentions, citations and sentiment are purged in the background, its ads, shopping products, local businesses and brand entities kept).",
        "operationId": "deleteCompetitor",
        "x-mcp-tool": "delete_competitor",
        "x-write": true,
        "description": "Deletes a competitor (irreversible). It disappears immediately and frees a competitor slot; a background job then deletes its mentions, citations, sentiment and share of voice history. Its ads, shopping products, local businesses and brand entities are kept and move to the project or another competitor they match, or stay unattributed. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/dimensions/prompts": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "List prompts",
        "x-llms-description": "Paginated list of prompts with their primary tag, all tags, locale, and last_executed_at.",
        "operationId": "listPrompts",
        "x-mcp-tool": "list_prompts",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "model",
            "in": "query",
            "description": "Keep only the prompts with at least one execution on this AI model, of any outcome and date (the prompts /dimensions/prompt_executions lists for the same model). Prompts have no model of their own, so a prompt with no execution on this model yet (a newly enabled model, or a locale the model does not cover) is left out. An unknown value, or a model the API key's user has not enabled, returns 422 ERR_INVALID_PARAM. from and to still filter prompt creation.",
            "type": "string",
            "enum": [
              "chatgpt",
              "perplexity",
              "ai_mode",
              "ai_overview",
              "gemini",
              "copilot",
              "amazon_rufus",
              "claude",
              "grok",
              "deepseek",
              "naver_ai",
              "baidu_ai",
              "meta_ai"
            ]
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated prompts. Every row carries app_url, the link that opens the prompt in the app",
            "schema": {
              "$ref": "#/definitions/PromptsResponse"
            }
          }
        }
      }
    },
    "/dimensions/prompt_executions": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "List prompt executions",
        "x-llms-description": "Paginated list of execution records: model, executed_at, success, fan_out_queries, counts.",
        "operationId": "listPromptExecutions",
        "x-mcp-tool": "list_prompt_executions",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/MentionFilter"
          },
          {
            "$ref": "#/parameters/CitationFilter"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated executions. Every row carries app_url, the link that opens the answer in the app",
            "schema": {
              "$ref": "#/definitions/PromptExecutionsResponse"
            }
          }
        }
      }
    },
    "/dimensions/query_fan_outs": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "List query fan-out",
        "x-llms-description": "Query fan-out: the sub-queries models issued behind your prompts, per distinct sub-query (view=query) or per prompt (view=prompt).",
        "operationId": "listQueryFanOuts",
        "x-mcp-tool": "list_query_fan_outs",
        "description": "The sub-queries a model actually issued when answering your tracked prompts. view=query (default) returns one row per distinct sub-query with count and share of all occurrences; view=prompt returns one row per prompt with how many distinct sub-queries it produced. Fan-out is reported mainly by ChatGPT, so an empty result usually means the models in scope do not expose it. The API returns the aggregation only: for a period-over-period delta, call it twice with explicit from/to.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "description": "Row shape: one per distinct sub-query, or one per prompt",
            "type": "string",
            "default": "query",
            "enum": [
              "query",
              "prompt"
            ]
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort field; the allowed set depends on view",
            "type": "string",
            "enum": [
              "count",
              "share",
              "query_text",
              "total_count",
              "variations_count",
              "prompt_text"
            ]
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "type": "string",
            "default": "desc",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring filter on the sub-query text",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated fan-out rows"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/suggested_prompts": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "List suggested prompts",
        "x-llms-description": "List the project's Suggested prompts queue (every generator), filterable by source and status.",
        "operationId": "listSuggestedPrompts",
        "x-mcp-tool": "list_suggested_prompts",
        "description": "Lists the project's Suggested prompts queue, the same list the app's Suggested Prompts tab shows, oldest first. It holds the suggestions of every generator (website, magic for keyword runs, topic, reddit, paa, reputational, translate, gsc for Search Console, catalog). Pending suggestions by default; status=all also returns accepted (with the prompt they became) and rejected ones. Team members need create access to Prompts, like the app's tab.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "source",
            "in": "query",
            "description": "Only suggestions from this generator",
            "type": "string",
            "enum": [
              "website",
              "magic",
              "paa",
              "topic",
              "reddit",
              "reputational",
              "gsc",
              "translate",
              "catalog"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only suggestions in this status",
            "type": "string",
            "default": "pending",
            "enum": [
              "pending",
              "accepted",
              "rejected",
              "all"
            ]
          },
          {
            "name": "page",
            "in": "query",
            "type": "integer",
            "default": 1,
            "minimum": 1
          },
          {
            "name": "per_page",
            "in": "query",
            "type": "integer",
            "default": 50,
            "maximum": 100,
            "minimum": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated suggestions",
            "schema": {
              "$ref": "#/definitions/SuggestedPromptsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientPermission"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/prompt_generations/{id}": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "Get a prompt generation run",
        "x-llms-description": "Poll a prompt suggestion run until it completes; returns the suggestions it saved.",
        "operationId": "getPromptGeneration",
        "x-mcp-tool": "get_prompt_generation",
        "description": "Returns a run started with POST /prompt_generations: processing while the generator works, then completed with the suggestions it saved to the Suggested prompts queue, or failed with an error code and message. Poll every few seconds; most runs finish within a minute. Runs are kept for 24 hours, their suggestions stay in the queue.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "string",
            "format": "uuid"
          }
        ],
        "responses": {
          "200": {
            "description": "The run",
            "schema": {
              "$ref": "#/definitions/PromptGenerationResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientPermission"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/prompts": {
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Bulk-create prompts",
        "x-llms-description": "Bulk-create prompts (up to 100 per request); skips duplicates and validates the account prompt quota.",
        "operationId": "createPrompts",
        "x-mcp-tool": "create_prompts",
        "x-write": true,
        "description": "Add prompts to a project in bulk (up to 100 per request). Validates the account prompt quota and skips duplicates. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/PromptsCreateRequest"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "schema": {
              "$ref": "#/definitions/PromptsCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/prompts/import": {
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Import prompts",
        "x-llms-description": "Import up to 500 prompts, each row with its own country, language and tags (the app's CSV import as JSON).",
        "operationId": "importPrompts",
        "x-mcp-tool": "import_prompts",
        "x-write": true,
        "description": "The app's CSV import as JSON: up to 500 rows, each with its own prompt, country_code, language_code (codes or names; both default to the project's) and tags, created when missing. Duplicates are skipped and existing prompts still get the row's tags. The prompt limits are checked before anything is written: when the new prompts would exceed the plan the call returns ERR_LIMIT_REACHED and imports nothing. Rows with an unknown country or language come back in row_errors. Responds 201 when it created prompts, 200 otherwise. Requires a `read_write` scope API key and, for team members, create access to Prompts.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/PromptsImportRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Nothing new was created (every row was a duplicate)",
            "schema": {
              "$ref": "#/definitions/PromptsImportResponse"
            }
          },
          "201": {
            "description": "Prompts imported",
            "schema": {
              "$ref": "#/definitions/PromptsImportResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/prompt_generations": {
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Generate prompt suggestions",
        "x-llms-description": "Start a prompt suggestion run with one of the Add prompts generators (website, keyword, topic, reddit, paa, reputational, translate, search_console).",
        "operationId": "createPromptGeneration",
        "x-mcp-tool": "generate_prompt_suggestions",
        "x-write": true,
        "description": "Starts a suggestion run with one of the generators behind the app's Add prompts wizards and answers 202 with the run. website writes suggestions from the scraped website (80% non-brand, 20% brand; up to 100 per project, then ERR_LIMIT_REACHED), keyword from a keyword or the project profile (optionally around a competitor), topic from a topic, reddit from Reddit discussions of a topic or brand, paa from Google People Also Ask (up to 5 keywords, counted against the account's 24-hour lookup allowance), reputational from brand reputation questions, translate from the project's prompts of another locale, search_console from the connected property's top queries of the last 28 days (Growth plan or above, ERR_PLAN_REQUIRED). The suggestions land in the Suggested prompts queue; poll GET /prompt_generations/{id}, then accept the ones you want with POST /suggested_prompts/accept. Nothing is tracked until accepted. The request is validated before the run starts, and refused with ERR_LIMIT_REACHED when the plan has no prompt slot left, or ERR_QUOTA_EXCEEDED after 60 runs in an hour (the per-user prompt suggestion allowance, shared with project setup). Requires a `read_write` scope API key and, for team members, create access to Prompts.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/PromptGenerationRequest"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The run, status processing",
            "schema": {
              "$ref": "#/definitions/PromptGenerationResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/suggested_prompts/accept": {
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Accept suggested prompts",
        "x-llms-description": "Track up to 100 pending suggestions as prompts, each in its own locale, optionally in one tag.",
        "operationId": "acceptSuggestedPrompts",
        "x-mcp-tool": "accept_suggested_prompts",
        "x-write": true,
        "description": "Starts tracking up to 100 pending suggestions: each becomes a prompt in its own country and language, optionally put in one tag (tag_ids or tag_names; create_missing creates a missing name when the team member may create tags). Suggestions that are no longer pending come back in skipped. When the new prompts would exceed the plan the call returns ERR_LIMIT_REACHED and accepts nothing. Requires a `read_write` scope API key and, for team members, create access to Prompts.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SuggestedPromptsAcceptRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and skipped suggestions",
            "schema": {
              "$ref": "#/definitions/SuggestedPromptsAcceptResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/suggested_prompts/reject": {
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Reject suggested prompts",
        "x-llms-description": "Mark up to 100 pending suggestions as rejected.",
        "operationId": "rejectSuggestedPrompts",
        "x-mcp-tool": "reject_suggested_prompts",
        "x-write": true,
        "description": "Marks up to 100 pending suggestions as rejected, so they leave the pending list; suggestions that are no longer pending stay as they are. Tracks and deletes nothing. Requires a `read_write` scope API key and, for team members, create access to Prompts.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/SuggestedPromptIdsRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The suggestions rejected",
            "schema": {
              "$ref": "#/definitions/SuggestedPromptsRejectResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/prompts/{id}": {
      "patch": {
        "tags": [
          "Prompts"
        ],
        "summary": "Edit a prompt's categorization",
        "x-llms-description": "Set a prompt's search intent (prompt_type) and/or brand focus (brand_kind) by hand, like editing its badges on the prompt page.",
        "operationId": "updatePrompt",
        "x-mcp-tool": "update_prompt_categorization",
        "x-write": true,
        "description": "Manual categorization, exactly like editing the intent and brand badges on the prompt page: send `prompt_type`, `brand_kind` or both. The prompt is marked as manually categorized (`categorization_source` becomes `manual`), so automatic categorization no longer changes it; the prompt page can revert it to automatic. Any other field returns ERR_INVALID_PARAM. Not available from a white-label portal (ERR_INTEGRATION_UNAVAILABLE), where the page hides the editor. Requires a `read_write` scope API key and, for team members, update permission on Prompts.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "prompt_type": {
                  "type": "string",
                  "enum": [
                    "informational",
                    "navigational",
                    "commercial",
                    "transactional"
                  ],
                  "description": "Search intent"
                },
                "brand_kind": {
                  "type": "string",
                  "enum": [
                    "brand",
                    "brand_other",
                    "non_brand"
                  ],
                  "description": "Brand focus: brand (your own brand or products), brand_other (competing brands), non_brand (generic)"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The prompt with its new categorization",
            "schema": {
              "$ref": "#/definitions/PromptCategorizationResponse"
            }
          },
          "403": {
            "$ref": "#/responses/PortalWriteForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Prompts"
        ],
        "summary": "Delete a prompt",
        "x-llms-description": "Delete a prompt (irreversible; frees a prompt slot; history purged in the background).",
        "operationId": "deletePrompt",
        "x-mcp-tool": "delete_prompt",
        "x-write": true,
        "description": "Deletes a prompt (irreversible). The prompt disappears immediately and frees a prompt slot; its historical data (executions, mentions, citations, sentiment) is purged by a background job. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/dimensions/collections": {
      "get": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "List tags/collections",
        "x-llms-description": "List prompt tags/collections (alias: /dimensions/tags).",
        "operationId": "listCollections",
        "x-mcp-tool": "list_collections",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Collections",
            "schema": {
              "$ref": "#/definitions/CollectionsResponse"
            }
          }
        }
      }
    },
    "/dimensions/tags": {
      "get": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "List tags (alias for /collections)",
        "x-llms-description": "Alias for /dimensions/collections.",
        "operationId": "listTags",
        "x-mcp-tool": "list_tags",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Tags",
            "schema": {
              "$ref": "#/definitions/CollectionsResponse"
            }
          }
        }
      }
    },
    "/prompts/assign_tags": {
      "post": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "Bulk-attach tags to prompts",
        "x-llms-description": "Bulk-attach tags to existing prompts (resolve by id or name, optional create-on-the-fly). Idempotent.",
        "operationId": "assignPromptTags",
        "x-mcp-tool": "assign_prompt_tags",
        "x-write": true,
        "description": "Idempotent bulk assignment of tags (Collections) to existing prompts. Tags can be resolved by id or by name (case-insensitive). Use `create_missing: true` to auto-create unknown tag names. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "prompt_ids"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "prompt_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "tag_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "tag_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "create_missing": {
                  "type": "boolean"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tags attached",
            "schema": {
              "$ref": "#/definitions/PromptTagsAssignResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/prompts/remove_tags": {
      "post": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "Bulk-detach tags from prompts",
        "x-llms-description": "Bulk-detach tags from existing prompts (resolve by id or name). Never deletes tags or prompts. Idempotent.",
        "operationId": "removePromptTags",
        "x-mcp-tool": "remove_prompt_tags",
        "x-write": true,
        "description": "Idempotent bulk removal of tags (Collections) from existing prompts: up to 500 prompt_ids and 50 tags, resolved by id or by name (case-insensitive). Only the prompt-tag links are removed; tags and prompts stay. Links that did not exist are counted in skipped_not_linked. Requires a `read_write` scope API key and, for team members, update access to Prompts.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "prompt_ids"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "prompt_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "tag_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                },
                "tag_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tags detached",
            "schema": {
              "$ref": "#/definitions/PromptTagsRemoveResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/collections": {
      "post": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "Create a tag",
        "x-llms-description": "Create a tag (Collection) in a project; can attach existing prompts in the same call.",
        "operationId": "createCollection",
        "x-mcp-tool": "create_collection",
        "x-write": true,
        "description": "Creates a tag (Collection) in a project. Optional `prompt_ids` attaches existing prompts in the same call. Tag name must be unique per project (case-insensitive). Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "name"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "prompt_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "schema": {
              "$ref": "#/definitions/CollectionCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/collections/{id}": {
      "patch": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "Update a tag",
        "x-llms-description": "Rename a tag/collection or change its description.",
        "operationId": "updateCollection",
        "x-mcp-tool": "update_collection",
        "x-write": true,
        "description": "Renames a tag/collection or changes its description. Prompt membership is managed via POST /prompts/assign_tags, not here. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Collections & Tags"
        ],
        "summary": "Delete a tag",
        "x-llms-description": "Delete a tag/collection; the prompts inside it are kept.",
        "operationId": "deleteCollection",
        "x-mcp-tool": "delete_collection",
        "x-write": true,
        "description": "Deletes a tag/collection. The prompts inside it are NOT deleted; only the grouping disappears. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/answers": {
      "get": {
        "tags": [
          "Answers"
        ],
        "summary": "List AI responses",
        "x-llms-description": "Paginated AI responses with truncated content (10k chars), counts, executed_at. query = full-text search (total becomes the exact match count; rows return snippet + match_count).",
        "operationId": "listAnswers",
        "x-mcp-tool": "list_answers",
        "description": "Successful prompt-execution responses with truncated content (max 10,000 chars). Pass `query` for case-insensitive full-text search inside response texts: `total` becomes the exact count of matching responses and each item returns `snippet` + `match_count` instead of `response`/`response_truncated`.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/MentionFilter"
          },
          {
            "$ref": "#/parameters/CitationFilter"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Case-insensitive full-text search inside AI response texts. Switches items to snippet + match_count mode.",
            "type": "string"
          },
          {
            "name": "no_result",
            "in": "query",
            "required": false,
            "description": "Filter sentinel non-answers (the provider returned nothing after all retries, or the model declined to answer, today only Gemini; excluded from platform metrics). false = only real answers, true = only sentinels, omit = both. Every item carries its own no_result flag.",
            "type": "boolean"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated answers. Every item carries app_url, the link that opens the answer in the app"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/answers/{id}": {
      "get": {
        "tags": [
          "Answers"
        ],
        "summary": "Get one AI response",
        "x-llms-description": "Full AI response with mentions, citations, sentiments, sources, shopping_products, brand_entities, fan_out_queries.",
        "operationId": "getAnswer",
        "x-mcp-tool": "get_answer",
        "description": "Full answer with mentions, citations, sentiments, sources, shopping_products, brand_entities, fan_out_queries. Pass `include_source_page_details=true` to nest page-cache metadata under each source.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "include_source_page_details",
            "in": "query",
            "type": "boolean",
            "default": false
          }
        ],
        "responses": {
          "200": {
            "description": "Answer details",
            "schema": {
              "$ref": "#/definitions/AnswerDetails"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/dimensions/mentions": {
      "get": {
        "tags": [
          "Mentions & Citations"
        ],
        "summary": "List brand mentions",
        "x-llms-description": "Paginated brand mention records (project brand only).",
        "operationId": "listMentions",
        "x-mcp-tool": "list_mentions",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated brand mentions",
            "schema": {
              "$ref": "#/definitions/MentionsResponse"
            }
          }
        }
      }
    },
    "/dimensions/citations": {
      "get": {
        "tags": [
          "Mentions & Citations"
        ],
        "summary": "List brand citations",
        "x-llms-description": "Paginated brand citation records (project brand only).",
        "description": "Includes visible citations and background source references. Background references use position 0, meaning no visible rank.",
        "operationId": "listCitations",
        "x-mcp-tool": "list_citations",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated brand citations",
            "schema": {
              "$ref": "#/definitions/CitationsResponse"
            }
          }
        }
      }
    },
    "/dimensions/competitor_mentions": {
      "get": {
        "tags": [
          "Mentions & Citations"
        ],
        "summary": "List competitor mentions",
        "x-llms-description": "Paginated competitor mention records.",
        "operationId": "listCompetitorMentions",
        "x-mcp-tool": "list_competitor_mentions",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated competitor mentions",
            "schema": {
              "$ref": "#/definitions/CompetitorMentionsResponse"
            }
          }
        }
      }
    },
    "/dimensions/competitor_citations": {
      "get": {
        "tags": [
          "Mentions & Citations"
        ],
        "summary": "List competitor citations",
        "x-llms-description": "Paginated competitor citation records.",
        "description": "Includes visible citations and background source references. Background references use position 0, meaning no visible rank.",
        "operationId": "listCompetitorCitations",
        "x-mcp-tool": "list_competitor_citations",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated competitor citations"
          }
        }
      }
    },
    "/dimensions/all_mentions": {
      "get": {
        "tags": [
          "Mentions & Citations"
        ],
        "summary": "List all mentions (brand + competitor)",
        "x-llms-description": "Unified brand + competitor mentions in one stream (actor_type field discriminates).",
        "operationId": "listAllMentions",
        "x-mcp-tool": "list_all_mentions",
        "description": "Unified mentions stream. Each record has an `actor_type` field (`project` or `competitor`) so the same payload covers both.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated mentions with actor_type discriminator"
          }
        }
      }
    },
    "/dimensions/all_citations": {
      "get": {
        "tags": [
          "Mentions & Citations"
        ],
        "summary": "List all citations (brand + competitor)",
        "x-llms-description": "Unified brand + competitor citations in one stream (actor_type field discriminates).",
        "operationId": "listAllCitations",
        "x-mcp-tool": "list_all_citations",
        "description": "Unified citations stream with an `actor_type` field on each record. Includes visible citations and background source references; background references use position 0, meaning no visible rank.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated citations with actor_type discriminator"
          }
        }
      }
    },
    "/dimensions/sentiments": {
      "get": {
        "tags": [
          "Sentiments"
        ],
        "summary": "List sentiment categories (Growth plan or above)",
        "x-llms-description": "List sentiment categories (metric keys + labels + colors). For records use /sentiments.",
        "operationId": "listSentimentCategories",
        "x-mcp-tool": "list_sentiments",
        "x-plan": "growth",
        "description": "Sentiment metric keys + labels + colors. For records, use /sentiments. Requires the Growth plan; lower tiers receive ERR_PLAN_REQUIRED.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Sentiment buckets"
          },
          "403": {
            "$ref": "#/responses/GrowthPlanRequired"
          }
        }
      }
    },
    "/sentiments": {
      "get": {
        "tags": [
          "Sentiments"
        ],
        "summary": "List sentiment records (Growth plan or above)",
        "x-llms-description": "Paginated sentiment analysis records with comments, topics, scores; filterable by analysis level and brand_only.",
        "operationId": "listSentimentRecords",
        "x-mcp-tool": "list_detailed_sentiments",
        "x-plan": "growth",
        "description": "Requires the Growth plan; lower tiers receive ERR_PLAN_REQUIRED.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "competitor_id",
            "in": "query",
            "type": "integer"
          },
          {
            "name": "brand_only",
            "in": "query",
            "type": "boolean"
          },
          {
            "name": "analysis",
            "in": "query",
            "description": "One sentiment level or a comma-separated list: very_positive, positive, neutral, negative, very_negative",
            "type": "string"
          },
          {
            "name": "model",
            "in": "query",
            "description": "Filter by one AI model. Here an unknown value, or a model the API key's user has not enabled, is ignored rather than rejected: the request succeeds and returns the records of every enabled model, as if model were omitted.",
            "type": "string",
            "enum": [
              "chatgpt",
              "perplexity",
              "ai_mode",
              "ai_overview",
              "gemini",
              "copilot",
              "amazon_rufus",
              "claude",
              "grok",
              "deepseek",
              "naver_ai",
              "baidu_ai",
              "meta_ai"
            ]
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated sentiments",
            "schema": {
              "$ref": "#/definitions/SentimentsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/GrowthPlanRequired"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/reports/sentiment/topics": {
      "get": {
        "tags": [
          "Sentiments"
        ],
        "summary": "Sentiment by topic (Growth plan or above)",
        "x-llms-description": "The Sentiment page's Topics table: analyzed mentions per topic with positive, neutral and negative counts, shares and net sentiment, under the page filters.",
        "operationId": "getSentimentTopics",
        "x-mcp-tool": "get_sentiment_topics",
        "x-plan": "growth",
        "description": "One row per topic AI answers associate with the brand (topics are matched case-insensitively), with its analyzed mentions, the positive (very positive included), neutral and negative counts, their shares on a 0-100 scale and the net sentiment score ((positive - negative) / mentions x 100). The same rows and filters as the Topics tab of the Sentiment page: the window defaults to the last 28 days, `brand` empty counts your own brand, `all` every brand and a competitor id that competitor. Requires the Growth plan; lower tiers receive ERR_PLAN_REQUIRED.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "range",
            "in": "query",
            "description": "Number of days to look back, today included (default 28, like the Sentiment page). Ignored when from or to is given.",
            "type": "integer"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "name": "analysis",
            "in": "query",
            "description": "One sentiment level or a comma-separated list: very_positive, positive, neutral, negative, very_negative",
            "type": "string"
          },
          {
            "name": "topic",
            "in": "query",
            "description": "Only sentiments tagged with this topic (case-insensitive)",
            "type": "string"
          },
          {
            "name": "query",
            "in": "query",
            "description": "Only sentiments whose answer or prompt text contains this text",
            "type": "string"
          },
          {
            "name": "brand",
            "in": "query",
            "description": "Whose sentiment to count. Empty or omitted for your own brand, all for every brand, or a competitor id (an unknown id returns ERR_INVALID_PARAM).",
            "type": "string"
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Sort column: mentions, name (the topic or tag), positive, negative or neutral (their shares) or net_score. Ties sort by name.",
            "type": "string",
            "default": "mentions",
            "enum": [
              "mentions",
              "name",
              "positive",
              "negative",
              "neutral",
              "net_score"
            ]
          },
          {
            "name": "sort_dir",
            "in": "query",
            "type": "string",
            "default": "desc",
            "enum": [
              "desc",
              "asc"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated topic rows",
            "schema": {
              "$ref": "#/definitions/SentimentTopicsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/GrowthPlanRequired"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/reports/sentiment/tags": {
      "get": {
        "tags": [
          "Sentiments"
        ],
        "summary": "Sentiment by tag (Growth plan or above)",
        "x-llms-description": "The Sentiment page's Tags table: analyzed mentions per tag with positive, neutral and negative counts, shares and net sentiment, under the page filters.",
        "operationId": "getSentimentTags",
        "x-mcp-tool": "get_sentiment_tags",
        "x-plan": "growth",
        "description": "One row per tag of the project with at least one analyzed mention, with the positive (very positive included), neutral and negative counts, their shares on a 0-100 scale and the net sentiment score. A mention counts once for every tag its prompt carries, as on the Sentiment page. Same filters as GET /reports/sentiment/topics. Requires the Growth plan; lower tiers receive ERR_PLAN_REQUIRED.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "range",
            "in": "query",
            "description": "Number of days to look back, today included (default 28, like the Sentiment page). Ignored when from or to is given.",
            "type": "integer"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "name": "analysis",
            "in": "query",
            "description": "One sentiment level or a comma-separated list: very_positive, positive, neutral, negative, very_negative",
            "type": "string"
          },
          {
            "name": "topic",
            "in": "query",
            "description": "Only sentiments tagged with this topic (case-insensitive)",
            "type": "string"
          },
          {
            "name": "query",
            "in": "query",
            "description": "Only sentiments whose answer or prompt text contains this text",
            "type": "string"
          },
          {
            "name": "brand",
            "in": "query",
            "description": "Whose sentiment to count. Empty or omitted for your own brand, all for every brand, or a competitor id (an unknown id returns ERR_INVALID_PARAM).",
            "type": "string"
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Sort column: mentions, name (the topic or tag), positive, negative or neutral (their shares) or net_score. Ties sort by name.",
            "type": "string",
            "default": "mentions",
            "enum": [
              "mentions",
              "name",
              "positive",
              "negative",
              "neutral",
              "net_score"
            ]
          },
          {
            "name": "sort_dir",
            "in": "query",
            "type": "string",
            "default": "desc",
            "enum": [
              "desc",
              "asc"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated tag rows",
            "schema": {
              "$ref": "#/definitions/SentimentTagsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/GrowthPlanRequired"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/sources": {
      "get": {
        "tags": [
          "Sources & Citation Intelligence"
        ],
        "summary": "List source URLs",
        "x-llms-description": "Paginated list of every URL extracted from AI responses with source_type classification.",
        "operationId": "listSources",
        "x-mcp-tool": "list_sources",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "name": "source_type",
            "in": "query",
            "description": "Filter by source ownership. Owned and competitor matching honor the project's exact-subdomain setting. Any other value, including a differently cased one, returns ERR_INVALID_PARAM.",
            "type": "string",
            "enum": [
              "owned",
              "competitor",
              "third_party"
            ]
          },
          {
            "$ref": "#/parameters/MentionFilter"
          },
          {
            "$ref": "#/parameters/Competitors"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated sources"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/citation_intelligence/groups": {
      "get": {
        "tags": [
          "Sources & Citation Intelligence"
        ],
        "summary": "Grouped citation intelligence",
        "x-llms-description": "Grouped citation intelligence by URL / domain / host with filters and per-model breakdown; key reads one group and include_series adds its citations per period.",
        "operationId": "listCitationGroups",
        "x-mcp-tool": "list_citation_groups",
        "description": "Grouped citation intelligence by url / domain / host with per-model breakdown, citation rate, and avg citation position. Counts and citation rate include visible citations and background source references. Average position ignores rows with position=0. Owned and competitor source matching honor the project's exact-subdomain setting. Filter vocabulary aligns with `source_type` returned by the API. Unavailable page content keeps the cited URL and citation metrics. Page metadata/content and unknown brand_mentioned/competitor_mentioned return null. content_gap_status is content_unavailable (or missing_page_cache) without usable content, and mentions_not_processed when the page has usable content but its mention analysis has not completed for this project. Mention fields (brand_mentioned, competitor_mentioned, brand_position, brands, mentions, content_gap_status) describe the analysis for the requesting project: a page not yet analyzed for it returns null (never false) brand_mentioned, competitor_mentioned and brand_position, empty brands and mentions arrays and page_cache.mentions_processed false. page_cache.mentions_processed is true exactly when brand_mentioned is not null. An analyzed page keeps its last result until a newer analysis replaces it. status_code shows a saved successful response or observed 404/410; other crawl failures and error_message are hidden. last_crawled_at dates the saved copy. Domain/host crawled_urls_count counts usable copies; brand_mentioned_urls_count and competitor_mentioned_urls_count count only URLs analyzed for the project and are null when none is. Pass key to read one group; brand_kind filters by the brand focus of the prompts, like the source page. citation_series (with include_series=true) holds the answers citing the URL, domain or host under the same filters: granularity (day, week or month, from the account tracking cadence, with weeks when a daily account spans more than 60 days, starting on Monday), total (equal to total_responses), by_model (answers per AI model, largest first, which can include enabled models outside the seven the model filter accepts) and data, one { date, value } point per period that holds a filtered answer, so a zero means answers ran and none cited it. Periods date each answer by its executed_at in Europe/Madrid time, the column from and to filter.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "view",
            "in": "query",
            "type": "string",
            "default": "url",
            "enum": [
              "url",
              "domain",
              "host"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "order",
            "in": "query",
            "type": "string",
            "enum": [
              "group_key",
              "total_responses",
              "total_citations",
              "citation_rate",
              "avg_citation_position",
              "first_seen_at",
              "last_seen_at"
            ]
          },
          {
            "name": "direction",
            "in": "query",
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "$ref": "#/parameters/CitationModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "name": "key",
            "in": "query",
            "description": "Return only this group of the chosen view: a domain (view=domain), a host (view=host) or a cited URL exactly as listed (view=url). Domains and hosts match case-insensitively, without www. A view=url key is one raw URL, so its variants (a tracking parameter, a trailing slash) stay separate rows; GET /citation_intelligence/urls/{url_sha256} merges them, as the app does. No match returns an empty list (total 0), not an error.",
            "type": "string"
          },
          {
            "name": "include_series",
            "in": "query",
            "description": "Requires key, else 422 ERR_INVALID_PARAM. Adds citation_series to the group's row.",
            "type": "boolean",
            "default": false
          },
          {
            "name": "query",
            "in": "query",
            "type": "string"
          },
          {
            "name": "source_type",
            "in": "query",
            "type": "string",
            "enum": [
              "owned",
              "competitor",
              "third_party",
              "social_media",
              "own_domain",
              "ugc",
              "background"
            ]
          },
          {
            "name": "sentiment",
            "in": "query",
            "type": "string",
            "enum": [
              "negative"
            ]
          },
          {
            "name": "content_gap",
            "in": "query",
            "description": "mentioned: the cited page mentions your brand. gap: it mentions a competitor but not your brand. Only pages with usable content whose mention analysis has completed for this project match.",
            "type": "string",
            "enum": [
              "mentioned",
              "gap"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Grouped citation intelligence"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/citation_intelligence/mentions_by_domain": {
      "get": {
        "tags": [
          "Sources & Citation Intelligence"
        ],
        "summary": "Mention share by citing domain",
        "x-llms-description": "For responses citing each given source domain, the brand-vs-competitor mention share (sums to 100% per domain).",
        "operationId": "getMentionsByCitingDomain",
        "x-mcp-tool": "get_mentions_by_citing_domain",
        "description": "For the responses where each given source domain is cited, returns the share of those responses that mention the brand vs each competitor (brand + competitors sum to 100% per domain). Pass multiple domains to get the whole matrix in one call.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "domains",
            "in": "query",
            "required": true,
            "description": "Source domains to analyze, e.g. domains[]=gmac.com&domains[]=educaweb.com",
            "type": "array",
            "items": {
              "type": "string"
            },
            "collectionFormat": "multi"
          },
          {
            "$ref": "#/parameters/CitationModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          }
        ],
        "responses": {
          "200": {
            "description": "Mention share per citing domain"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/citation_intelligence/urls/{url_sha256}": {
      "get": {
        "tags": [
          "Sources & Citation Intelligence"
        ],
        "summary": "Cited URL detail",
        "x-llms-description": "Per-URL detail: counts, citation rate, page-cache metadata, page mention evidence; include_series=true adds citations by AI model and per period.",
        "operationId": "getCitedUrlDetail",
        "x-mcp-tool": "get_cited_url_details",
        "description": "Unavailable page content keeps the cited URL and citation metrics. Page metadata/content and unknown brand_mentioned/competitor_mentioned return null. content_gap_status is content_unavailable (or missing_page_cache) without usable content, and mentions_not_processed when the page has usable content but its mention analysis has not completed for this project. Mention fields (brand_mentioned, competitor_mentioned, brand_position, brands, mentions, content_gap_status) describe the analysis for the requesting project: a page not yet analyzed for it returns null (never false) brand_mentioned, competitor_mentioned and brand_position, empty brands and mentions arrays and page_cache.mentions_processed false. page_cache.mentions_processed is true exactly when brand_mentioned is not null. An analyzed page keeps its last result until a newer analysis replaces it. status_code shows a saved successful response or observed 404/410; other crawl failures and error_message are hidden. last_crawled_at dates the saved copy. Domain/host crawled_urls_count counts usable copies; brand_mentioned_urls_count and competitor_mentioned_urls_count count only URLs analyzed for the project and are null when none is. The filters narrow stats and citation_series alike. citation_series (with include_series=true) holds the answers citing the URL under the same filters: granularity (day, week or month, from the account tracking cadence, with weeks when a daily account spans more than 60 days, starting on Monday), total (equal to total_responses), by_model (answers per AI model, largest first, which can include enabled models outside the seven the model filter accepts) and data, one { date, value } point per period that holds a filtered answer, so a zero means answers ran and none cited it. Periods date each answer by its executed_at in Europe/Madrid time, the column from and to filter.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/UrlSha256"
          },
          {
            "$ref": "#/parameters/CitationModel"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "name": "source_type",
            "in": "query",
            "type": "string",
            "enum": [
              "owned",
              "competitor",
              "third_party",
              "social_media",
              "own_domain",
              "ugc",
              "background"
            ]
          },
          {
            "name": "sentiment",
            "in": "query",
            "type": "string",
            "enum": [
              "negative"
            ]
          },
          {
            "name": "content_gap",
            "in": "query",
            "description": "mentioned: the cited page mentions your brand. gap: it mentions a competitor but not your brand. Only pages with usable content whose mention analysis has completed for this project match.",
            "type": "string",
            "enum": [
              "mentioned",
              "gap"
            ]
          },
          {
            "name": "include_series",
            "in": "query",
            "description": "Adds citation_series. Without it the response is unchanged.",
            "type": "boolean",
            "default": false
          }
        ],
        "responses": {
          "200": {
            "description": "URL-level intelligence"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/citation_intelligence/urls/{url_sha256}/occurrences": {
      "get": {
        "tags": [
          "Sources & Citation Intelligence"
        ],
        "summary": "Cited URL occurrences",
        "x-llms-description": "Paginated occurrences of a cited URL across prompt executions.",
        "operationId": "listCitedUrlOccurrences",
        "x-mcp-tool": "list_cited_url_occurrences",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/UrlSha256"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated occurrences"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/citation_intelligence/urls/{url_sha256}/content": {
      "get": {
        "tags": [
          "Sources & Citation Intelligence"
        ],
        "summary": "Cited URL cached content",
        "x-llms-description": "Sanitized cached page content for a cited URL plus mention evidence.",
        "operationId": "getCitedUrlContent",
        "x-mcp-tool": "get_cited_url_content",
        "description": "Unavailable page content keeps the cited URL and citation metrics. Page metadata/content and unknown brand_mentioned/competitor_mentioned return null. content_gap_status is content_unavailable (or missing_page_cache) without usable content, and mentions_not_processed when the page has usable content but its mention analysis has not completed for this project. Mention fields (brand_mentioned, competitor_mentioned, brand_position, brands, mentions, content_gap_status) describe the analysis for the requesting project: a page not yet analyzed for it returns null (never false) brand_mentioned, competitor_mentioned and brand_position, empty brands and mentions arrays and page_cache.mentions_processed false. page_cache.mentions_processed is true exactly when brand_mentioned is not null. An analyzed page keeps its last result until a newer analysis replaces it. status_code shows a saved successful response or observed 404/410; other crawl failures and error_message are hidden. last_crawled_at dates the saved copy. Domain/host crawled_urls_count counts usable copies; brand_mentioned_urls_count and competitor_mentioned_urls_count count only URLs analyzed for the project and are null when none is.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/UrlSha256"
          }
        ],
        "responses": {
          "200": {
            "description": "Sanitized cached content + mention evidence"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/reports/ai_model_insights/summary": {
      "get": {
        "tags": [
          "AI Model Insights"
        ],
        "summary": "AI Model Insights summary",
        "x-llms-description": "AI Model Insights summary: per-model mentions, citations, sentiment, weighted visibility, actor matrices.",
        "operationId": "getAiModelInsightsSummary",
        "x-mcp-tool": "get_ai_model_summary",
        "description": "Per-model mentions, citations, brand net sentiment with raw counts, weighted visibility totals/shares, plus actor matrices. All actor entries use the standard shape `{ type, id, competitor_id, name, domain }` with bare (scheme-less) domains.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Competitors"
          }
        ],
        "responses": {
          "200": {
            "description": "Summary"
          }
        }
      }
    },
    "/reports/ai_model_insights/position_distribution": {
      "get": {
        "tags": [
          "AI Model Insights"
        ],
        "summary": "Position distribution comparison",
        "x-llms-description": "Per-model position distribution comparison (Position 1, 2-3, 4-7, 8+) for one or two brands.",
        "operationId": "getAiModelPositionDistribution",
        "x-mcp-tool": "get_ai_model_position_distribution",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "name": "model",
            "in": "query",
            "description": "Restrict the comparison to one AI model: chatgpt, ai_mode, ai_overview, perplexity, gemini, naver_ai or baidu_ai, whether or not that model is enabled for the API key's user. Any other value returns 422 ERR_INVALID_PARAM (invalid model).",
            "type": "string",
            "enum": [
              "chatgpt",
              "perplexity",
              "ai_mode",
              "ai_overview",
              "gemini",
              "copilot",
              "amazon_rufus",
              "claude",
              "grok",
              "deepseek",
              "naver_ai",
              "baidu_ai",
              "meta_ai"
            ]
          },
          {
            "name": "brand1",
            "in": "query",
            "description": "Competitor ID for the first comparison brand (omit to compare project brand)",
            "type": "integer"
          },
          {
            "name": "brand2",
            "in": "query",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Bucketed position totals + chart-ready series"
          }
        }
      }
    },
    "/reports/ai_model_insights/ai_overview_results": {
      "get": {
        "tags": [
          "AI Model Insights"
        ],
        "summary": "Google AI Overview result availability",
        "x-llms-description": "Google AI Overview result-availability data with by-prompt result-rate table.",
        "operationId": "getAiOverviewResults",
        "x-mcp-tool": "get_ai_overview_results",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "AI Overview result-availability data + per-prompt table"
          }
        }
      }
    },
    "/metrics/agent_traffic": {
      "get": {
        "tags": [
          "AI & Agent Traffic"
        ],
        "summary": "AI bot crawler traffic (Scale plan or above, Beta)",
        "x-llms-description": "AI bot crawler traffic by bot/company over time (Scale plan or above, Beta).",
        "description": "Aggregated AI bot traffic hitting the project's origin server (GPTBot, PerplexityBot, ClaudeBot, OAI-SearchBot, Google-Extended, etc.). Sourced from Cloudflare or CSV uploads. Requires the Scale plan; lower tiers receive ERR_PLAN_REQUIRED. A range below 1, or a window whose from is later than its to, returns ERR_INVALID_PARAM.",
        "operationId": "getAgentTraffic",
        "x-mcp-tool": "get_agent_traffic",
        "x-plan": "scale",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "name": "bot",
            "in": "query",
            "description": "Filter by bot slug (e.g. gptbot, claudebot, perplexitybot)",
            "type": "string"
          },
          {
            "name": "company",
            "in": "query",
            "description": "Filter by company (e.g. openai, anthropic, google)",
            "type": "string"
          },
          {
            "name": "group_by",
            "in": "query",
            "type": "string",
            "default": "bot",
            "enum": [
              "bot",
              "company"
            ]
          },
          {
            "$ref": "#/parameters/Granularity"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent traffic data",
            "schema": {
              "$ref": "#/definitions/AgentTrafficResponse"
            }
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/metrics/ai_traffic": {
      "get": {
        "tags": [
          "AI & Agent Traffic"
        ],
        "summary": "AI referral traffic (Scale plan or above)",
        "x-llms-description": "AI referral traffic (human visits from ChatGPT, Perplexity, Gemini, etc.) by source over time, from the connected web analytics provider (Scale plan or above).",
        "description": "AI referral traffic for a project: human visits arriving from AI assistants (ChatGPT, Perplexity, Gemini, Claude, etc.), measured from the connected web analytics provider (Google Analytics 4, Adobe Analytics, PostHog, Plausible, Matomo or Piano). Returns per-source users, sessions and conversions with totals and a conversion rate. Requires a connected provider and the Scale plan; otherwise returns ERR_AI_TRAFFIC_NOT_CONNECTED or ERR_PLAN_REQUIRED. A range below 1, or a window whose from is later than its to, returns ERR_INVALID_PARAM.",
        "operationId": "getAiTraffic",
        "x-mcp-tool": "get_ai_traffic",
        "x-mcp-plan": "growth",
        "x-plan": "scale",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "name": "source",
            "in": "query",
            "description": "Filter by a single AI source slug (e.g. chatgpt, perplexity, gemini, claude)",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Granularity"
          }
        ],
        "responses": {
          "200": {
            "description": "AI referral traffic data"
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/web_analytics/schema": {
      "get": {
        "tags": [
          "AI & Agent Traffic"
        ],
        "summary": "Web analytics query format (Growth+)",
        "x-llms-description": "How to query the connected web analytics provider live: its native query format, enforced rules, an example and its live field list (Growth+).",
        "description": "How to query the web analytics provider connected to the project, live: the provider, the property every query runs on, the native query format it accepts, the allowed top-level fields, the rules the bridge enforces (the connected property is always used, only reads run, row limits), a worked example and, where the provider offers it, its live field list. Cached for an hour. Supported providers: Google Analytics 4, Adobe Analytics or Customer Journey Analytics, Matomo, PostHog, Plausible and Piano, connected on the AI Traffic page. Requires the Growth plan or above; otherwise ERR_PLAN_REQUIRED. Without a connected provider returns ERR_WEB_ANALYTICS_NOT_CONNECTED (404); a provider that refuses the stored credentials returns ERR_WEB_ANALYTICS_ACCESS_REVOKED (403); an unavailable provider, an exhausted provider quota, more than 20 uncached queries a minute or too many running at once for the project returns ERR_WEB_ANALYTICS_UPSTREAM (503): wait for the number of seconds in Retry-After before retrying.",
        "operationId": "getWebAnalyticsSchema",
        "x-mcp-tool": "get_web_analytics_schema",
        "x-plan": "growth",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "Query format for the connected provider",
            "schema": {
              "$ref": "#/definitions/WebAnalyticsSchemaResponse"
            }
          },
          "403": {
            "$ref": "#/responses/Forbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/web_analytics/query": {
      "post": {
        "tags": [
          "AI & Agent Traffic"
        ],
        "summary": "Live web analytics query (Growth+)",
        "x-llms-description": "Live, read-only query in the provider's native format (GA4 runReport, Adobe report, Matomo API, PostHog HogQL, Plausible v2, Piano getData) on the connected property; columns and rows (Growth+).",
        "description": "Runs a read-only query, written in the connected provider's native format, against the project's property and returns columns and rows: a GA4 Data API runReport body, an Adobe Analytics or Customer Journey Analytics report request, Matomo Reporting API parameters (get methods only), PostHog HogQL, a Plausible Stats API v2 query or a Piano getData body. The connected property, site, report suite or project is always used and any property field in the query is ignored. Rows default to 100 and are capped at 5,000. Identical queries are answered from a 10-minute cache (cached: true). A query the provider rejects returns ERR_WEB_ANALYTICS_INVALID_QUERY (422) with the provider's own validation message. Each uncached query spends the customer's provider API quota. A read: no writable key is needed. Supported providers: Google Analytics 4, Adobe Analytics or Customer Journey Analytics, Matomo, PostHog, Plausible and Piano, connected on the AI Traffic page. Requires the Growth plan or above; otherwise ERR_PLAN_REQUIRED. Without a connected provider returns ERR_WEB_ANALYTICS_NOT_CONNECTED (404); a provider that refuses the stored credentials returns ERR_WEB_ANALYTICS_ACCESS_REVOKED (403); an unavailable provider, an exhausted provider quota, more than 20 uncached queries a minute or too many running at once for the project returns ERR_WEB_ANALYTICS_UPSTREAM (503): wait for the number of seconds in Retry-After before retrying.",
        "operationId": "queryWebAnalytics",
        "x-mcp-tool": "query_web_analytics",
        "x-plan": "growth",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "query"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "query": {
                  "description": "The query in the provider's native format (see GET /web_analytics/schema): a JSON object for GA4, Adobe, Matomo, Plausible and Piano; for PostHog, {\"query\": \"<HogQL>\"} or the HogQL string. Deliberately untyped so generated clients accept either shape."
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Query result as columns and rows",
            "schema": {
              "$ref": "#/definitions/WebAnalyticsQueryResponse"
            }
          },
          "403": {
            "$ref": "#/responses/Forbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/dimensions/agent_bots": {
      "get": {
        "tags": [
          "AI & Agent Traffic"
        ],
        "summary": "AI bot catalog (Scale plan or above)",
        "x-llms-description": "Static catalog of AI bots that Agent Analytics can identify (slug, company, category).",
        "operationId": "listAgentBots",
        "x-mcp-tool": "list_agent_bots",
        "x-plan": "scale",
        "description": "Static catalog of AI bots that Agent Analytics can identify. Useful for rendering filter UIs that mirror our internal classification (slug, display name, company, category, Cloudflare verified-bot mapping, description). Requires the Scale plan; lower tiers receive ERR_PLAN_REQUIRED. The equivalent MCP tool list_agent_bots is available on the Scale plan or above.",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": true,
            "description": "Accepted for compatibility and ignored. The catalog is the same for every project, so any value returns the same list and the project is not checked.",
            "type": "integer"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Bot catalog",
            "schema": {
              "$ref": "#/definitions/AgentBotsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          }
        }
      }
    },
    "/dimensions/shopping": {
      "get": {
        "tags": [
          "Shopping & Ads"
        ],
        "summary": "List shopping results",
        "x-llms-description": "Shopping results returned inside AI answers, per distinct product (view=products) or per selling merchant (view=merchants), with appearance counts, price range and rating (Scale plan or above).",
        "operationId": "listShopping",
        "x-mcp-tool": "list_shopping_products",
        "x-plan": "scale",
        "description": "Product cards returned inside AI answers. view=products (default) returns one row per distinct product, merged across executions, with its appearance count, price range, rating and whether it is yours, plus a currency_count saying how many currencies it was priced in (above 1 means the row reports its highest-priced listing and min_price may be another currency); view=merchants returns one row per selling merchant, with a currency field naming the money its price range and average are expressed in (providers price each market in its own currency, so a merchant that sells in more than one reports the currency most of its prices use). Every response also carries a totals block matching the KPI cards in the app, whose avg_price is computed inside the single currency named by avg_price_currency. Requires the Scale plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "description": "Row shape: one per distinct product, or one per merchant",
            "type": "string",
            "default": "products",
            "enum": [
              "products",
              "merchants"
            ]
          },
          {
            "name": "owned",
            "in": "query",
            "required": false,
            "description": "Return only products identified as the tracked brand's own. On view=merchants this narrows to the merchants selling those products; the totals block stays account-wide.",
            "type": "boolean"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort field; the allowed set depends on view",
            "type": "string",
            "enum": [
              "appearances",
              "price",
              "rating",
              "title",
              "products",
              "avg_price",
              "avg_rating",
              "merchant"
            ]
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "type": "string",
            "default": "desc",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring filter on the product title",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated shopping rows plus totals"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/ads": {
      "get": {
        "tags": [
          "Shopping & Ads"
        ],
        "summary": "List AI ad placements",
        "x-llms-description": "Paid placements returned inside AI answers, per advertising domain (view=advertisers) or per individual placement (view=ads), with counts and positions (Scale plan or above).",
        "operationId": "listAds",
        "x-mcp-tool": "list_ads",
        "x-plan": "scale",
        "description": "Paid placements returned inside AI answers. view=advertisers (default) returns one row per advertising domain with its placement count, prompt reach and average and best position; view=ads returns the individual placements with title, snippet, position and the prompt that triggered them. Position 1 is the best slot, so a LOWER average position is better. Requires the Scale plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "description": "Row shape: one per advertising domain, or one per placement",
            "type": "string",
            "default": "advertisers",
            "enum": [
              "advertisers",
              "ads"
            ]
          },
          {
            "name": "owned",
            "in": "query",
            "required": false,
            "description": "Return only placements identified as the tracked brand's own (view=ads)",
            "type": "boolean"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort field; the allowed set depends on view",
            "type": "string",
            "enum": [
              "ads",
              "prompts",
              "avg_position",
              "domain",
              "recent",
              "oldest",
              "position"
            ]
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction for view=advertisers. Defaults to desc, except avg_position and domain which default to asc.",
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring filter on the ad title, domain or snippet",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated ad rows plus totals"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/local_businesses": {
      "get": {
        "tags": [
          "Shopping & Ads"
        ],
        "summary": "List local businesses",
        "x-llms-description": "Local businesses listed inside AI answers, one row per business at its address, with appearances, prompts, average rating and position, and whether it is yours or a competitor (every plan).",
        "operationId": "listLocalBusinesses",
        "x-mcp-tool": "list_local_businesses",
        "description": "Local businesses (shops, restaurants, services) listed inside AI answers, one row per business: its name at its address, so two locations of a chain are two rows. Each row carries its appearance count, the number of prompts that listed it, its average rating and average position in the list, its review count, and whether it is yours or a tracked competitor. Every response also carries a totals block matching the KPI cards in the app. Local business lists come from a subset of models and only for prompts with local intent. Available on every plan.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "owned",
            "in": "query",
            "required": false,
            "description": "Return only listings identified as the tracked brand's own locations. The totals block stays account-wide.",
            "type": "boolean"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort field",
            "type": "string",
            "default": "appearances",
            "enum": [
              "appearances",
              "business",
              "rating",
              "reviews",
              "avg_position",
              "prompts"
            ]
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "type": "string",
            "default": "desc",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring filter on the business name or address",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/Prompt"
          },
          {
            "$ref": "#/parameters/PromptType"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated local business rows plus totals",
            "schema": {
              "$ref": "#/definitions/LocalBusinessesResponse"
            }
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/owned_media": {
      "get": {
        "tags": [
          "Owned Media & Communities"
        ],
        "summary": "List owned-media citations",
        "x-llms-description": "Owned-media citations by platform (provider=youtube, instagram, facebook, tiktok, linkedin, mobile_apps): cited videos, posts, profiles, channels and app-store listings, each flagged as yours or not (Growth+).",
        "operationId": "listOwnedMedia",
        "x-mcp-tool": "list_owned_media",
        "x-plan": "growth",
        "description": "Which owned-media content AI answers cite, by platform. `provider` is required. Each row carries a `yours` flag so you can compare your own presence against everyone else cited on the same platform. Video, post, channel and profile rows also carry their image (thumbnail_url or avatar_url, our cached copy where one exists), their average position in the answers and, on YouTube, the video id, the channel handle and URL, and the channel key that /dimensions/owned_media/citing_prompts accepts. view=own_citations returns the raw citations of the connected profile only and stays empty until a profile is connected. For Reddit use /dimensions/reddit. A team member also needs the YouTube Intelligence permission for provider=youtube. Requires the Growth plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "provider",
            "in": "query",
            "required": true,
            "description": "The platform to report on",
            "type": "string",
            "enum": [
              "youtube",
              "instagram",
              "facebook",
              "tiktok",
              "linkedin",
              "mobile_apps"
            ]
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "description": "Row shape; the allowed set depends on provider",
            "type": "string",
            "enum": [
              "videos",
              "channels",
              "posts",
              "profiles",
              "own_citations",
              "apps"
            ]
          },
          {
            "name": "store",
            "in": "query",
            "required": false,
            "description": "provider=mobile_apps only",
            "type": "string",
            "default": "google_play",
            "enum": [
              "google_play",
              "app_store"
            ]
          },
          {
            "name": "owned",
            "in": "query",
            "required": false,
            "description": "Return only rows belonging to the account's own connected profile",
            "type": "boolean"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated owned-media rows",
            "schema": {
              "$ref": "#/definitions/OwnedMediaListResponse"
            }
          },
          "403": {
            "description": "A plan below Growth answers ERR_PLAN_REQUIRED. A team member whose permissions do not grant Owned Media (or YouTube Intelligence for provider=youtube) answers ERR_INSUFFICIENT_PERMISSION, with the same status",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_INSUFFICIENT_PERMISSION",
                  "message": "Your team-member permissions do not include the youtube report."
                },
                "request_id": "abc-123"
              }
            }
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/owned_media/summary": {
      "get": {
        "tags": [
          "Owned Media & Communities"
        ],
        "summary": "Get an owned-media summary",
        "x-llms-description": "The summary of one Owned Media tab (provider=youtube, instagram, facebook, tiktok, linkedin, mobile_apps) or of the cross-platform overview: KPI cards with the previous period and change, weekly series, breakdowns by model, country, content type and age (Growth+).",
        "operationId": "getOwnedMediaSummary",
        "x-mcp-tool": "get_owned_media_summary",
        "x-plan": "growth",
        "description": "The numbers behind one Owned Media tab. kpis gives the platform citations (total), yours (your profiles and their content), your_profile and your_content apart, and share_pct; previous gives the same KPIs for the period of the same length that ends just before from, and changes the difference (percent for counts, percentage points for share_pct, null when there is nothing to compare). weekly is the citations per week, yours against everyone else. by_model and by_country split every citation of the platform; content_types (reel, post, tv, pulse, or regular, shorts and live on YouTube) and age_buckets cover posts and videos. provider=mobile_apps adds stores, the per-store totals, and has no content type or age; provider=overview returns platforms, one rollup per platform the caller can read, and no breakdowns. A team member also needs the YouTube Intelligence permission for provider=youtube. Defaults to the last 30 days. Requires the Growth plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "provider",
            "in": "query",
            "required": true,
            "description": "The platform to summarize, or overview for every platform at once",
            "type": "string",
            "enum": [
              "youtube",
              "instagram",
              "facebook",
              "tiktok",
              "linkedin",
              "mobile_apps",
              "overview"
            ]
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          }
        ],
        "responses": {
          "200": {
            "description": "The KPI cards, comparison, weekly series and breakdowns",
            "schema": {
              "$ref": "#/definitions/OwnedMediaSummaryResponse"
            },
            "examples": {
              "application/json": {
                "project_id": 123,
                "provider": "instagram",
                "from": "2026-09-15T00:00:00.000+02:00",
                "to": "2026-10-14T23:59:59.999+02:00",
                "connected": true,
                "kpis": {
                  "total": 48,
                  "yours": 12,
                  "your_profile": 4,
                  "your_content": 8,
                  "share_pct": 25.0
                },
                "previous": {
                  "from": "2026-08-16T00:00:00.000+02:00",
                  "to": "2026-09-14T23:59:59.000+02:00",
                  "kpis": {
                    "total": 40,
                    "yours": 8,
                    "your_profile": 3,
                    "your_content": 5,
                    "share_pct": 20.0
                  }
                },
                "changes": {
                  "total": 20.0,
                  "yours": 50.0,
                  "your_profile": 33.3,
                  "your_content": 60.0,
                  "share_pct": 5.0
                },
                "weekly": [
                  {
                    "week": "2026-09-14",
                    "citations": 9,
                    "yours": 2,
                    "other": 7
                  }
                ],
                "by_model": [
                  {
                    "model": "chatgpt",
                    "citations": 30,
                    "share_pct": 62.5
                  }
                ],
                "by_country": [
                  {
                    "country_code": "US",
                    "citations": 48,
                    "share_pct": 100.0
                  }
                ],
                "content_types": [
                  {
                    "content_type": "reel",
                    "citations": 20,
                    "share_pct": 66.67
                  }
                ],
                "age_buckets": [
                  {
                    "bucket": "m1_6",
                    "citations": 18,
                    "share_pct": 60.0
                  }
                ],
                "stores": null,
                "platforms": null,
                "request_id": "4f7c2a1e-0d2b-4c1a-9e55-1b2c3d4e5f60"
              }
            }
          },
          "403": {
            "description": "A plan below Growth answers ERR_PLAN_REQUIRED. A team member whose permissions do not grant Owned Media (or YouTube Intelligence for provider=youtube) answers ERR_INSUFFICIENT_PERMISSION, with the same status",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_PLAN_REQUIRED",
                  "message": "Growth plan or above required"
                },
                "request_id": "abc-123"
              }
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/owned_media/citing_prompts": {
      "get": {
        "tags": [
          "Owned Media & Communities"
        ],
        "summary": "List the answers citing an owned-media item",
        "x-llms-description": "One YouTube video or channel, or one Instagram, Facebook, TikTok or LinkedIn post or profile, with the AI answers that cited it: prompt, answer id, model, date and an excerpt (Growth+).",
        "operationId": "listOwnedMediaCitingPrompts",
        "x-mcp-tool": "list_owned_media_citing_prompts",
        "x-plan": "growth",
        "description": "The drill-down behind one row of an Owned Media or YouTube Intelligence table. Pass exactly one of url (a post or video URL, any variant of it) or profile (a handle with or without @, a profile or channel URL, or the channel key the YouTube channels rows return). item describes it (title, image, owner, whether it is yours and its stats), citation_count and prompt_count count its citations in the window, and data lists the citing answers newest first, each with its prompt, answer id, model, country, language, execution date, a plain-text excerpt and its link in the app. It shows prompt and answer text, so a team member also needs the Prompts permission, and YouTube Intelligence for provider=youtube. Defaults to the last 30 days. Requires the Growth plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "provider",
            "in": "query",
            "required": true,
            "description": "The platform the item belongs to",
            "type": "string",
            "enum": [
              "youtube",
              "instagram",
              "facebook",
              "tiktok",
              "linkedin"
            ]
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "A post or video URL, or a profile or channel URL. Exclusive with profile",
            "type": "string"
          },
          {
            "name": "profile",
            "in": "query",
            "required": false,
            "description": "A handle (with or without @), a profile or channel URL, or a YouTube channel key (handle:..., channel:... or name:...). Exclusive with url",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "The item and a page of the answers that cited it",
            "schema": {
              "$ref": "#/definitions/OwnedMediaCitingPromptsResponse"
            },
            "examples": {
              "application/json": {
                "project_id": 123,
                "provider": "youtube",
                "from": "2026-09-15T00:00:00.000+02:00",
                "to": "2026-10-14T23:59:59.999+02:00",
                "item": {
                  "kind": "post",
                  "title": "Best CRM tools in 2026",
                  "subtitle": "Acme",
                  "url": "https://www.youtube.com/watch?v=abc123",
                  "image_url": "https://i.ytimg.com/vi/abc123/hqdefault.jpg",
                  "yours": true,
                  "date": "2026-05-02T10:00:00Z",
                  "stats": {
                    "citations": 14,
                    "views": 92000
                  }
                },
                "citation_count": 14,
                "prompt_count": 6,
                "page": 1,
                "per_page": 20,
                "total": 9,
                "data": [
                  {
                    "answer_id": 987654,
                    "prompt_id": 4321,
                    "prompt_text": "best crm for startups",
                    "model": "chatgpt",
                    "country_code": "US",
                    "language_code": "en",
                    "executed_at": "2026-10-12T00:14:03.000+02:00",
                    "excerpt": "For a startup, the strongest options are...",
                    "app_url": "https://llmpulse.ai/app/prompts/4321/prompt_executions/987654?project_id=123"
                  }
                ],
                "request_id": "4f7c2a1e-0d2b-4c1a-9e55-1b2c3d4e5f60"
              }
            }
          },
          "403": {
            "description": "A plan below Growth answers ERR_PLAN_REQUIRED. A team member whose permissions do not grant Owned Media and Prompts (and YouTube Intelligence for provider=youtube) answers ERR_INSUFFICIENT_PERMISSION, with the same status",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_INSUFFICIENT_PERMISSION",
                  "message": "Your team-member permissions do not include prompts, which this answer list shows."
                },
                "request_id": "abc-123"
              }
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/dimensions/reddit": {
      "get": {
        "tags": [
          "Owned Media & Communities"
        ],
        "summary": "List cited Reddit content",
        "x-llms-description": "Reddit content cited by AI answers, per subreddit (view=subreddits), per author (view=authors) or per thread (view=threads), with sentiment split and engagement (Growth+).",
        "operationId": "listRedditCitations",
        "x-mcp-tool": "list_reddit_citations",
        "x-plan": "growth",
        "description": "Which Reddit content AI answers cite for your tracked prompts. view=subreddits (default) returns one row per subreddit with its citation count, unique authors and positive/negative sentiment split; view=authors returns one row per author; view=threads returns the individual cited threads with upvotes, comments, average position and dominant sentiment. Requires the Growth plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "view",
            "in": "query",
            "required": false,
            "type": "string",
            "default": "subreddits",
            "enum": [
              "subreddits",
              "authors",
              "threads"
            ]
          },
          {
            "name": "subreddit",
            "in": "query",
            "required": false,
            "description": "Filter to one subreddit (name without the r/ prefix)",
            "type": "string"
          },
          {
            "name": "author",
            "in": "query",
            "required": false,
            "description": "Filter to one Reddit author",
            "type": "string"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "view=threads only",
            "type": "string",
            "enum": [
              "open",
              "archived"
            ]
          },
          {
            "name": "owned",
            "in": "query",
            "required": false,
            "description": "Return only subreddits/authors the account has claimed as its own",
            "type": "boolean"
          },
          {
            "name": "brand",
            "in": "query",
            "required": false,
            "description": "Filter to citations whose scraped Reddit content mentions a brand: 'brand' for the tracked brand, or a competitor id. Reads the page content, not the AI answer.",
            "type": "string"
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort field; the allowed set depends on view",
            "type": "string",
            "enum": [
              "citations",
              "subreddit",
              "unique_authors",
              "positive_pct",
              "negative_pct",
              "author",
              "avg_position",
              "upvotes",
              "comments",
              "sentiment"
            ]
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "type": "string",
            "default": "desc",
            "enum": [
              "asc",
              "desc"
            ]
          },
          {
            "$ref": "#/parameters/Model"
          },
          {
            "$ref": "#/parameters/CollectionId"
          },
          {
            "$ref": "#/parameters/CountryCode"
          },
          {
            "$ref": "#/parameters/LanguageCode"
          },
          {
            "$ref": "#/parameters/BrandKind"
          },
          {
            "$ref": "#/parameters/Range"
          },
          {
            "$ref": "#/parameters/From"
          },
          {
            "$ref": "#/parameters/To"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated Reddit rows"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/projects/{id}/owned_media": {
      "get": {
        "tags": [
          "Owned Media & Communities"
        ],
        "summary": "Get a project's owned-media profiles",
        "x-llms-description": "The owned-media profiles a project claims per platform (YouTube, app stores, Instagram, Facebook, TikTok, LinkedIn, Reddit), with name, avatar and scrape status, plus the current value of every editable field (Growth+).",
        "operationId": "getOwnedMediaProfiles",
        "x-mcp-tool": "get_owned_media_profiles",
        "x-plan": "growth",
        "description": "The profiles whose citations count as yours in the Owned Media and YouTube Intelligence reports, as Project Settings > Owned Media shows them. platforms holds one entry per platform with whether it is connected, how many extra profiles it accepts and its profiles: the main one, the extra ones, and on Reddit the claimed subreddits and usernames, each with its URL, handle, store (for apps), name, avatar and the state of the scrape that reads them. settings holds the current value of every field PATCH accepts, ready to edit and send back. Requires the Growth plan or above.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project ID",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Profiles per platform and the editable settings",
            "schema": {
              "$ref": "#/definitions/OwnedMediaProfilesResponse"
            },
            "examples": {
              "application/json": {
                "project_id": 123,
                "platforms": [
                  {
                    "platform": "youtube",
                    "connected": true,
                    "max_extra_profiles": 10,
                    "profiles": [
                      {
                        "url": "https://www.youtube.com/@acme",
                        "handle": "acme",
                        "kind": "profile",
                        "main": true,
                        "store": null,
                        "name": "Acme",
                        "avatar_url": "https://llmpulse.ai/rails/active_storage/blobs/redirect/abc/avatar.jpg",
                        "scrape_status": "ready"
                      }
                    ]
                  },
                  {
                    "platform": "reddit",
                    "connected": true,
                    "max_extra_profiles": null,
                    "profiles": [
                      {
                        "url": "https://www.reddit.com/r/acme",
                        "handle": "acme",
                        "kind": "subreddit",
                        "main": false,
                        "store": null,
                        "name": null,
                        "avatar_url": null,
                        "scrape_status": null
                      }
                    ]
                  }
                ],
                "settings": {
                  "youtube_channel_url": "https://www.youtube.com/@acme",
                  "app_store_url": null,
                  "google_play_url": null,
                  "instagram_profile_url": null,
                  "facebook_page_url": null,
                  "tiktok_profile_url": null,
                  "linkedin_company_url": null,
                  "youtube_extra_profile_urls": [],
                  "mobile_apps_extra_profile_urls": [],
                  "instagram_extra_profile_urls": [],
                  "facebook_extra_profile_urls": [],
                  "tiktok_extra_profile_urls": [],
                  "linkedin_extra_profile_urls": [],
                  "reddit_subreddits": [
                    "acme"
                  ],
                  "reddit_usernames": []
                },
                "request_id": "4f7c2a1e-0d2b-4c1a-9e55-1b2c3d4e5f60"
              }
            }
          },
          "403": {
            "description": "A plan below Growth answers ERR_PLAN_REQUIRED, and a team member whose permissions do not grant Owned Media ERR_INSUFFICIENT_PERMISSION, with the same status",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_PLAN_REQUIRED",
                  "message": "Growth plan or above required"
                },
                "request_id": "abc-123"
              }
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Owned Media & Communities"
        ],
        "summary": "Update a project's owned-media profiles",
        "x-llms-description": "Set or clear the main and extra owned-media profiles of a project and its claimed subreddits and Reddit usernames; a new profile is scraped for its name and avatar (Growth+, read_write key).",
        "operationId": "updateOwnedMediaProfiles",
        "x-mcp-tool": "update_owned_media_profiles",
        "x-plan": "growth",
        "x-write": true,
        "description": "Sets the profiles whose citations count as yours, like Project Settings > Owned Media. Send only the fields to change: a URL field takes a URL, or an empty string or null to clear it, and every list REPLACES the stored one ([] clears it). URLs are normalized like the app (a profile URL is stored in its canonical form, a Google Play package name becomes its listing URL), and anything the app would refuse (a URL from another platform, an extra profile equal to the main one, more than 10 extra profiles, a malformed subreddit) is rejected with ERR_INVALID_PARAM and nothing is applied. A new main or extra profile is scraped in the background for its name and avatar, listed in profile_scrapes_queued. The response is the GET payload after the change. A team member needs the Owned Media create permission, YouTube Intelligence create to change the YouTube channel, and Projects update to change the extra profile or Reddit lists. Requires the Growth plan or above and a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Project ID",
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "properties": {
                "youtube_channel_url": {
                  "type": "string",
                  "description": "The main YouTube channel URL (@handle, /channel/UC..., /c/... or /user/...). An empty string or null clears it",
                  "x-nullable": true
                },
                "app_store_url": {
                  "type": "string",
                  "description": "The App Store app URL, with its id number. An empty string or null clears it",
                  "x-nullable": true
                },
                "google_play_url": {
                  "type": "string",
                  "description": "The Google Play app URL, or its package name (com.acme.app). An empty string or null clears it",
                  "x-nullable": true
                },
                "instagram_profile_url": {
                  "type": "string",
                  "description": "The main Instagram profile URL. An empty string or null clears it",
                  "x-nullable": true
                },
                "facebook_page_url": {
                  "type": "string",
                  "description": "The main Facebook Page URL. An empty string or null clears it",
                  "x-nullable": true
                },
                "tiktok_profile_url": {
                  "type": "string",
                  "description": "The main TikTok profile URL. An empty string or null clears it",
                  "x-nullable": true
                },
                "linkedin_company_url": {
                  "type": "string",
                  "description": "The main LinkedIn page URL: a company (/company/...), school (/school/...) or showcase (/showcase/...) page. An empty string or null clears it",
                  "x-nullable": true
                },
                "youtube_extra_profile_urls": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of extra YouTube channel URLs (at most 10); [] clears it"
                },
                "mobile_apps_extra_profile_urls": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of extra App Store or Google Play app URLs (at most 10); [] clears it"
                },
                "instagram_extra_profile_urls": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of extra Instagram profile URLs (at most 10); [] clears it"
                },
                "facebook_extra_profile_urls": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of extra Facebook Page URLs (at most 10); [] clears it"
                },
                "tiktok_extra_profile_urls": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of extra TikTok profile URLs (at most 10); [] clears it"
                },
                "linkedin_extra_profile_urls": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list of extra LinkedIn company, school or showcase page URLs (at most 10); [] clears it"
                },
                "reddit_subreddits": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list (at most 10) of subreddit names the brand owns, without r/; [] clears it"
                },
                "reddit_usernames": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "FULL replacement list (at most 25) of Reddit usernames the brand owns, without u/; [] clears it"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The profiles after the change",
            "schema": {
              "$ref": "#/definitions/OwnedMediaProfilesUpdateResponse"
            }
          },
          "403": {
            "description": "A plan below Growth answers ERR_PLAN_REQUIRED, a read-only key ERR_INSUFFICIENT_SCOPE, and a team member without the Owned Media create permission (or YouTube Intelligence create for the YouTube channel) ERR_INSUFFICIENT_PERMISSION, with the same status",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_INSUFFICIENT_SCOPE",
                  "message": "insufficient_scope_write_required"
                },
                "request_id": "abc-123"
              }
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/reputation/reports": {
      "get": {
        "tags": [
          "Reputation & Studies"
        ],
        "summary": "List reputation reports",
        "x-llms-description": "List the monthly reputation reports for a project, newest first, with status and which analyst models produced data.",
        "operationId": "listReputationReports",
        "x-mcp-tool": "list_reputation_reports",
        "x-plan": "reputation_access",
        "description": "The monthly multi-model analyst reports scoring the tracked brand and its competitors, newest first. Pending and failed reports are included on purpose: whether this month ran at all is often the question. Each row carries the report id, its status, and which analyst models produced data. Requires reputation monitoring to be enabled on the account.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated report summaries"
          }
        }
      }
    },
    "/reputation/reports/{id}": {
      "get": {
        "tags": [
          "Reputation & Studies"
        ],
        "summary": "Get reputation report scores",
        "x-llms-description": "Reputation report scores as flat rows: one row per analyst model, brand, dimension and attribute, with score and reasoning.",
        "operationId": "getReputationReport",
        "x-mcp-tool": "get_reputation_report",
        "x-plan": "reputation_access",
        "description": "One reputation report's scores as flat rows: one row per analyst model, brand, dimension and attribute, with its 0-100 score and the reasoning the model gave. Scores come from several analyst models independently, so compare models rather than averaging them blindly.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The report id from GET /reputation/reports",
            "type": "string"
          },
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "model",
            "in": "query",
            "required": false,
            "description": "Restrict to one analyst model",
            "type": "string",
            "enum": [
              "chatgpt",
              "perplexity",
              "gemini",
              "deepseek",
              "grok",
              "claude"
            ]
          },
          {
            "name": "brand",
            "in": "query",
            "required": false,
            "description": "Restrict to one brand name, or a comma-separated list",
            "type": "string"
          },
          {
            "name": "dimension",
            "in": "query",
            "required": false,
            "description": "Restrict to one reputation dimension key",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated score rows plus the report summary"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/studies": {
      "get": {
        "tags": [
          "Reputation & Studies"
        ],
        "summary": "List custom AI studies",
        "x-llms-description": "List the custom AI studies defined on the account (account-scoped; project_id is an optional filter).",
        "operationId": "listStudies",
        "x-mcp-tool": "list_studies",
        "x-plan": "reputation_access",
        "description": "The custom AI studies defined on the account: analyst reports over any set of subjects (brands, sectors, topics) and any set of dimensions. Studies belong to the ACCOUNT, not to a project, so project_id is an optional filter here and account-level studies are returned whichever project you filter by. A team member whose project access is restricted sees only the studies of the projects they can reach. Requires reputation monitoring to be enabled on the account.",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Restrict to studies attached to this project (plus account-level ones)",
            "type": "integer"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "type": "string",
            "enum": [
              "active",
              "archived"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated study summaries"
          }
        }
      }
    },
    "/studies/{id}": {
      "get": {
        "tags": [
          "Reputation & Studies"
        ],
        "summary": "Get a custom AI study",
        "x-llms-description": "One custom AI study with its brief, subjects, dimensions and report history.",
        "operationId": "getStudy",
        "x-mcp-tool": "get_study",
        "x-plan": "reputation_access",
        "description": "One study with its brief, the subjects it compares, the dimensions it scores them on, and its report history. Use the ids in `reports` with GET /studies/{id}/reports/{report_id}.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "The study"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/studies/{id}/reports/{report_id}": {
      "get": {
        "tags": [
          "Reputation & Studies"
        ],
        "summary": "Get custom study report scores",
        "x-llms-description": "Custom study report scores as flat rows: one row per analyst model, subject, dimension and attribute, with score and reasoning.",
        "operationId": "getStudyReport",
        "x-mcp-tool": "get_study_report",
        "x-plan": "reputation_access",
        "description": "One custom-study report's scores as flat rows: one row per analyst model, subject, dimension and attribute, with its 0-100 score and the reasoning the model gave.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "report_id",
            "in": "path",
            "required": true,
            "description": "The report id from GET /studies/{id}",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "name": "model",
            "in": "query",
            "required": false,
            "description": "Restrict to one analyst model",
            "type": "string",
            "enum": [
              "chatgpt",
              "perplexity",
              "gemini",
              "deepseek",
              "grok",
              "claude"
            ]
          },
          {
            "name": "subject",
            "in": "query",
            "required": false,
            "description": "Restrict to one subject name, or a comma-separated list",
            "type": "string"
          },
          {
            "name": "dimension",
            "in": "query",
            "required": false,
            "description": "Restrict to one dimension key",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated score rows plus the report summary"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/search_console/summary": {
      "get": {
        "tags": [
          "Search Console"
        ],
        "summary": "Search Console summary (Growth+)",
        "x-llms-description": "Google Search Console headline totals (impressions, clicks, CTR, average position) for a date range, with an optional country, device, page, query or searchAppearance breakdown. With filters and no dimension the totals are the complete filtered figure; breakdown rows are capped and can differ from the totals. Requires a connected GSC property (Growth+).",
        "description": "Google Search Console headline totals (impressions, clicks, ctr as a 0..1 fraction, average position) for the project over a date range. Pass dimension=country, device, page, query or searchAppearance to also receive the breakdown aggregated over the range, capped by limit. With filters and no dimension, totals are the complete figure Google reports for that filter. Breakdown rows are capped by limit and by Google's 25,000-row maximum; breakdown_truncated: true means rows were left out. Query rows exclude anonymized queries, and page and searchAppearance rows are counted per page rather than per property, so breakdown rows can differ from the totals. Requires the project to have a connected Search Console property and the Growth plan or above; otherwise returns ERR_SEARCH_CONSOLE_NOT_CONNECTED or ERR_PLAN_REQUIRED. X-Search-Console-Backend identifies stored or live reads. Stored reads use synced data without contacting Google. Live reads return ERR_SEARCH_CONSOLE_ACCESS_REVOKED (403) for revoked Google access; reconnect the property in Preferences > Project Settings > Data Connections. They return ERR_SEARCH_CONSOLE_UPSTREAM (503) when Google Search Console is unavailable or over quota; wait for the number of seconds in Retry-After before retrying.",
        "operationId": "getSearchConsoleSummary",
        "x-mcp-tool": "get_search_console_summary",
        "x-plan": "growth",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/GscRange"
          },
          {
            "$ref": "#/parameters/GscFrom"
          },
          {
            "$ref": "#/parameters/GscTo"
          },
          {
            "name": "dimension",
            "in": "query",
            "description": "Optional breakdown aggregated over the range. country and device are lowercased; page and query keep the casing Google returns, because a page URL is case sensitive.",
            "type": "string",
            "enum": [
              "country",
              "device",
              "page",
              "query",
              "searchAppearance"
            ]
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum breakdown rows, sorted by impressions descending. Default and maximum 1000. Use /search_console/queries or /search_console/pages to page through up to 25,000 rows.",
            "type": "integer",
            "maximum": 1000,
            "minimum": 1
          },
          {
            "$ref": "#/parameters/GscSearchType"
          },
          {
            "$ref": "#/parameters/GscFilters"
          },
          {
            "$ref": "#/parameters/GscDataState"
          },
          {
            "$ref": "#/parameters/GscAggregationType"
          }
        ],
        "responses": {
          "200": {
            "description": "Search Console summary",
            "schema": {
              "$ref": "#/definitions/SearchConsoleSummaryResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/SearchConsoleForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/search_console/timeseries": {
      "get": {
        "tags": [
          "Search Console"
        ],
        "summary": "Search Console time series (Growth+)",
        "x-llms-description": "Google Search Console series (impressions, clicks, CTR, position) bucketed by day/week/month, property-wide unless filters narrows it (Growth+).",
        "description": "Google Search Console series (impressions, clicks, ctr, position) bucketed by day, week or month, property-wide unless filters narrows it. Requires a connected Search Console property (Growth+). X-Search-Console-Backend identifies stored or live reads. Stored reads use synced data without contacting Google. Live reads return ERR_SEARCH_CONSOLE_ACCESS_REVOKED (403) for revoked Google access; reconnect the property in Preferences > Project Settings > Data Connections. They return ERR_SEARCH_CONSOLE_UPSTREAM (503) when Google Search Console is unavailable or over quota; wait for the number of seconds in Retry-After before retrying.",
        "operationId": "getSearchConsoleTimeseries",
        "x-mcp-tool": "get_search_console_timeseries",
        "x-plan": "growth",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/GscRange"
          },
          {
            "$ref": "#/parameters/GscFrom"
          },
          {
            "$ref": "#/parameters/GscTo"
          },
          {
            "$ref": "#/parameters/Granularity"
          },
          {
            "$ref": "#/parameters/Output"
          },
          {
            "$ref": "#/parameters/GscSearchType"
          },
          {
            "$ref": "#/parameters/GscFilters"
          },
          {
            "$ref": "#/parameters/GscDataState"
          },
          {
            "$ref": "#/parameters/GscAggregationType"
          }
        ],
        "responses": {
          "200": {
            "description": "Search Console time series",
            "schema": {
              "$ref": "#/definitions/SearchConsoleTimeseriesResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/SearchConsoleForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/search_console/queries": {
      "get": {
        "tags": [
          "Search Console"
        ],
        "summary": "Top Search Console queries (Growth+)",
        "x-llms-description": "Top Google Search Console search queries for a date range, ranked by impressions/clicks/CTR/position, paginated; at most 25,000 rows, anonymized queries excluded (Growth+).",
        "description": "Top Google Search Console search queries over a date range, ranked by impressions, clicks, ctr or position, paginated. Excludes anonymized queries; for headline totals use /search_console/summary. Requires a connected Search Console property (Growth+). X-Search-Console-Backend identifies stored or live reads. Stored reads use synced data without contacting Google. Live reads return ERR_SEARCH_CONSOLE_ACCESS_REVOKED (403) for revoked Google access; reconnect the property in Preferences > Project Settings > Data Connections. They return ERR_SEARCH_CONSOLE_UPSTREAM (503) when Google Search Console is unavailable or over quota; wait for the number of seconds in Retry-After before retrying. total counts the rows returned by one Google request, up to 25,000; truncated: true means that limit was reached, so the rows add up to less than the totals, and start_row reads the next block. Sorting and pagination apply to the available set. Stored reads cannot answer this endpoint and return ERR_INVALID_PARAM.",
        "operationId": "getSearchConsoleQueries",
        "x-mcp-tool": "get_search_console_queries",
        "x-plan": "growth",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/GscRange"
          },
          {
            "$ref": "#/parameters/GscFrom"
          },
          {
            "$ref": "#/parameters/GscTo"
          },
          {
            "name": "sort",
            "in": "query",
            "type": "string",
            "default": "impressions",
            "enum": [
              "impressions",
              "clicks",
              "ctr",
              "position"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/GscStartRow"
          },
          {
            "$ref": "#/parameters/Output"
          },
          {
            "$ref": "#/parameters/GscSearchType"
          },
          {
            "$ref": "#/parameters/GscFilters"
          },
          {
            "$ref": "#/parameters/GscDataState"
          },
          {
            "$ref": "#/parameters/GscAggregationType"
          }
        ],
        "responses": {
          "200": {
            "description": "Top queries",
            "schema": {
              "$ref": "#/definitions/SearchConsoleListResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/SearchConsoleForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/search_console/pages": {
      "get": {
        "tags": [
          "Search Console"
        ],
        "summary": "Top Search Console pages (Growth+)",
        "x-llms-description": "Top Google Search Console landing pages for a date range, ranked by impressions/clicks/CTR/position, paginated; at most 25,000 rows (Growth+).",
        "description": "Top Google Search Console landing pages over a date range, ranked by impressions, clicks, ctr or position, paginated. Requires a connected Search Console property (Growth+). X-Search-Console-Backend identifies stored or live reads. Stored reads use synced data without contacting Google. Live reads return ERR_SEARCH_CONSOLE_ACCESS_REVOKED (403) for revoked Google access; reconnect the property in Preferences > Project Settings > Data Connections. They return ERR_SEARCH_CONSOLE_UPSTREAM (503) when Google Search Console is unavailable or over quota; wait for the number of seconds in Retry-After before retrying. total counts the rows returned by one Google request, up to 25,000; truncated: true means that limit was reached, and start_row reads the next block. Page rows are counted per page rather than per property, so they can differ from the totals. Sorting and pagination apply to the available set. Stored reads cannot answer this endpoint and return ERR_INVALID_PARAM.",
        "operationId": "getSearchConsolePages",
        "x-mcp-tool": "get_search_console_pages",
        "x-plan": "growth",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/GscRange"
          },
          {
            "$ref": "#/parameters/GscFrom"
          },
          {
            "$ref": "#/parameters/GscTo"
          },
          {
            "name": "sort",
            "in": "query",
            "type": "string",
            "default": "impressions",
            "enum": [
              "impressions",
              "clicks",
              "ctr",
              "position"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/GscStartRow"
          },
          {
            "$ref": "#/parameters/Output"
          },
          {
            "$ref": "#/parameters/GscSearchType"
          },
          {
            "$ref": "#/parameters/GscFilters"
          },
          {
            "$ref": "#/parameters/GscDataState"
          },
          {
            "$ref": "#/parameters/GscAggregationType"
          }
        ],
        "responses": {
          "200": {
            "description": "Top pages",
            "schema": {
              "$ref": "#/definitions/SearchConsoleListResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/SearchConsoleForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/search_console/rows": {
      "get": {
        "tags": [
          "Search Console"
        ],
        "summary": "Search Console rows by several dimensions (Growth+)",
        "x-llms-description": "Google Search Console rows grouped by up to 3 dimensions (e.g. page and query, or date and page), ranked by impressions/clicks/CTR/position, paginated; at most 25,000 rows per request, start_row reads the next block (Growth+).",
        "description": "Google Search Console rows grouped by 1 to 3 dimensions (date, country, device, page, query, searchAppearance) over a date range, each row carrying its dimension values as fields plus impressions, clicks, ctr (0..1) and position, ranked by impressions, clicks, ctr or position, paginated. Google ranks rows by clicks and returns at most 25,000 per request (truncated: true when reached); start_row moves that window. Query rows exclude anonymized queries, so rows add up to less than the summary totals. Requires a connected Search Console property (Growth+) and live reads: stored reads return ERR_INVALID_PARAM. Live reads return ERR_SEARCH_CONSOLE_ACCESS_REVOKED (403) for revoked Google access and ERR_SEARCH_CONSOLE_UPSTREAM (503) when Google Search Console is unavailable or over quota; wait for the number of seconds in Retry-After before retrying. A combination Google refuses comes back as ERR_INVALID_PARAM with its reason.",
        "operationId": "getSearchConsoleRows",
        "x-mcp-tool": "get_search_console_rows",
        "x-plan": "growth",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "dimensions",
            "in": "query",
            "required": true,
            "description": "Comma-separated dimensions to group by, 1 to 3 of date, country, device, page, query, searchAppearance. Each becomes a field of every row; country and device values are lowercase.",
            "x-example": "page,query",
            "type": "string"
          },
          {
            "$ref": "#/parameters/GscRange"
          },
          {
            "$ref": "#/parameters/GscFrom"
          },
          {
            "$ref": "#/parameters/GscTo"
          },
          {
            "name": "sort",
            "in": "query",
            "type": "string",
            "default": "impressions",
            "enum": [
              "impressions",
              "clicks",
              "ctr",
              "position"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/GscStartRow"
          },
          {
            "$ref": "#/parameters/Output"
          },
          {
            "$ref": "#/parameters/GscSearchType"
          },
          {
            "$ref": "#/parameters/GscFilters"
          },
          {
            "$ref": "#/parameters/GscDataState"
          },
          {
            "$ref": "#/parameters/GscAggregationType"
          }
        ],
        "responses": {
          "200": {
            "description": "Search Console rows",
            "schema": {
              "$ref": "#/definitions/SearchConsoleRowsResponse"
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/SearchConsoleForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "503": {
            "$ref": "#/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/custom_reports": {
      "get": {
        "tags": [
          "Custom Reports"
        ],
        "summary": "List custom reports",
        "x-llms-description": "List a project's custom reports with their date range, delivery schedule, recipient count, shared-link state and widgets.",
        "operationId": "listCustomReports",
        "x-mcp-tool": "list_custom_reports",
        "x-plan": "scale",
        "description": "The custom reports of the project, newest first, as the Reports > Custom Reports list shows them. Recipient addresses, the shared-link token and the password are never returned. Requires the Scale plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated custom reports",
            "schema": {
              "$ref": "#/definitions/CustomReportsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          }
        }
      }
    },
    "/custom_reports/{id}": {
      "get": {
        "tags": [
          "Custom Reports"
        ],
        "summary": "Get a custom report with its widget data",
        "x-llms-description": "One custom report's configuration and the data of every widget, computed as its page shows it now (read-only).",
        "operationId": "getCustomReport",
        "x-mcp-tool": "get_custom_report",
        "x-plan": "scale",
        "description": "The configuration of one custom report (date range, filters, schedule, layout) and the data of every widget computed for today, the same numbers the report page, its shared link and its PDF show. Each widget carries its `kind` and a `data` object shaped by it: `series` for `timeseries`, `slices` for `donut`, `bars` for `bar`, `values` and `changes` for `kpi_grid`, `rows` for `table`. A widget whose data source is not connected has `available: false` and `data: null`. Nothing is saved or sent. Requires the Scale plan or above.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Custom report id, from GET /custom_reports",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Report configuration and widget data",
            "schema": {
              "$ref": "#/definitions/CustomReportDetailResponse"
            }
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/custom_reports/{id}/send": {
      "post": {
        "tags": [
          "Custom Reports"
        ],
        "summary": "Send a custom report now",
        "x-llms-description": "Email a custom report to its recipients now, like the report page's Send now button (published reports with recipients, once every 10 minutes).",
        "operationId": "sendCustomReport",
        "x-mcp-tool": "send_custom_report",
        "x-plan": "scale",
        "x-write": true,
        "description": "Queues the report's PDF for its recipients, like the Send now button on the report page: the report must be published and have at least one recipient (ERR_INVALID_PARAM otherwise), and each report can be sent once every 10 minutes (429 ERR_RATE_LIMITED with Retry-After). Not available from a white-label portal or for a report made inside one (ERR_INTEGRATION_UNAVAILABLE). Requires the Scale plan or above, a `read_write` scope API key and, for team members, update permission on Custom Reports.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Custom report id, from GET /custom_reports",
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                }
              }
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The report is queued for its recipients",
            "schema": {
              "$ref": "#/definitions/CustomReportSendResponse"
            }
          },
          "403": {
            "$ref": "#/responses/PortalWriteForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/responses/ReportSendCooldown"
          }
        }
      }
    },
    "/recommendations": {
      "get": {
        "tags": [
          "Recommendations"
        ],
        "summary": "List recommendation runs",
        "x-llms-description": "List recommendation runs for a project (filter by recommendation_type and status).",
        "operationId": "listRecommendations",
        "x-mcp-tool": "list_recommendations",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "recommendation_type",
            "in": "query",
            "type": "string",
            "enum": [
              "ai_visibility",
              "social_community",
              "brand_building",
              "sentiment_reputation"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "completed",
              "failed"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated recommendations",
            "schema": {
              "$ref": "#/definitions/RecommendationsResponse"
            }
          }
        }
      },
      "post": {
        "tags": [
          "Recommendations"
        ],
        "summary": "Launch a recommendations generation",
        "x-llms-description": "Launch a new recommendations generation (async, 1-3 min; consumes the weekly recommendation budget). Poll GET /recommendations/:id.",
        "operationId": "launchRecommendations",
        "x-mcp-tool": "launch_recommendations",
        "x-write": true,
        "description": "Launches a full-scope recommendations generation (async job, 1-3 minutes; poll GET /recommendations/{id} until status is completed). Consumes the project weekly recommendation-item budget: returns ERR_LIMIT_REACHED when it is exhausted or when a generation of the same type is already pending/processing. sentiment_reputation requires the Scale plan or above. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "recommendation_type": {
                  "type": "string",
                  "enum": [
                    "ai_visibility",
                    "social_community",
                    "brand_building",
                    "sentiment_reputation"
                  ],
                  "default": "ai_visibility"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Generation launched (status pending)"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/recommendations/{id}": {
      "get": {
        "tags": [
          "Recommendations"
        ],
        "summary": "Get recommendation run with items",
        "x-llms-description": "Detailed recommendation run with items, action steps, priorities, and resolved source references.",
        "operationId": "getRecommendation",
        "x-mcp-tool": "get_recommendation",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "item_status",
            "in": "query",
            "type": "string",
            "enum": [
              "active",
              "completed",
              "archived"
            ]
          },
          {
            "name": "resolve_source_refs",
            "in": "query",
            "type": "boolean",
            "default": true
          }
        ],
        "responses": {
          "200": {
            "description": "Recommendation detail with items"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/recommendations/{id}/items/{item_id}": {
      "patch": {
        "tags": [
          "Recommendations"
        ],
        "summary": "Complete, archive or restore a recommendation item",
        "x-llms-description": "Mark one recommendation item as completed, archive it, or move it back to active, like the buttons on the Recommendations page.",
        "operationId": "updateRecommendationItem",
        "x-mcp-tool": "update_recommendation_item",
        "x-write": true,
        "description": "Moves one item of a recommendation run between `active`, `completed` and `archived`, exactly like the complete, archive and restore buttons on the Recommendations page, and stamps `status_changed_at`. Returns the item in the shape `GET /recommendations/{id}` lists items. Requires a `read_write` scope API key and, for team members, create permission on Recommendations (the permission the page uses for these buttons).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Recommendation run id",
            "type": "integer"
          },
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "description": "Item id, from the items of GET /recommendations/{id}",
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "status"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "active",
                    "completed",
                    "archived"
                  ],
                  "description": "completed, archived, or active to restore the item"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The updated item",
            "schema": {
              "$ref": "#/definitions/RecommendationItemUpdateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/intelligence_tasks": {
      "get": {
        "tags": [
          "GEO Writer"
        ],
        "summary": "List GEO Writer tasks",
        "x-llms-description": "List GEO Writer (formerly Content Intelligence) tasks for a project with task_type and status filters.",
        "operationId": "listIntelligenceTasks",
        "x-mcp-tool": "list_intelligence_tasks",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "task_type",
            "in": "query",
            "type": "string",
            "enum": [
              "brief",
              "create",
              "update",
              "pr_insights",
              "custom",
              "product_listing"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated tasks",
            "schema": {
              "$ref": "#/definitions/IntelligenceTasksResponse"
            }
          }
        }
      },
      "post": {
        "tags": [
          "GEO Writer"
        ],
        "summary": "Create a GEO Writer task",
        "x-llms-description": "Create a GEO Writer (formerly Content Intelligence) task (brief, create, update, pr_insights, custom, or product_listing with a product object); processed async.",
        "operationId": "createIntelligenceTask",
        "x-mcp-tool": "create_intelligence_task",
        "x-write": true,
        "description": "Creates a GEO Writer task, processed asynchronously: poll GET /intelligence_tasks/{id} until status is completed. Prompt-based mode takes prompt_id; agentic mode takes custom_topic and/or user_instructions. task_type product_listing is API-only and serves store apps: send a product object (title required) and optionally prompt_ids, and the completed result_data holds ready-to-apply product page copy. Edit and revert it with PATCH /intelligence_tasks/{id} and POST /intelligence_tasks/{id}/revert. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/IntelligenceTaskCreateRequest"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "schema": {
              "$ref": "#/definitions/IntelligenceTask"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/intelligence_tasks/{id}": {
      "get": {
        "tags": [
          "GEO Writer"
        ],
        "summary": "Get a GEO Writer task",
        "x-llms-description": "Get one GEO Writer (formerly Content Intelligence) task with full result_data when completed (accepts numeric id or public_id).",
        "operationId": "getIntelligenceTask",
        "x-mcp-tool": "get_intelligence_task",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric task ID or public_id string token",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Task with result_data when completed",
            "schema": {
              "$ref": "#/definitions/IntelligenceTask"
            }
          }
        }
      },
      "patch": {
        "tags": [
          "GEO Writer"
        ],
        "summary": "Edit GEO Writer task content",
        "x-llms-description": "Edit the text of a completed GEO Writer task in place: edits maps dotted result_data paths (title, sections.0.content) to replacement text; the generated version is kept for revert.",
        "operationId": "updateIntelligenceTaskContent",
        "x-mcp-tool": "update_intelligence_task_content",
        "x-write": true,
        "description": "Edits the text of a completed task in place. `edits` maps dotted paths into result_data (for example `title` or `sections.0.content`) to replacement text. Only string fields that already exist can change: a path that does not resolve to text, a blank `title`, a value over 20,000 characters or an empty `edits` object is rejected with ERR_INVALID_PARAM and nothing is written. Send the `result_version` you last read with the edits: when the task was regenerated since, the request is refused with ERR_STALE_CONTENT (409), nothing is written and `error.meta.result_version` holds the current token. Without `result_version` the edits apply to whatever the task holds now. Values identical to the stored text are ignored, and the response lists the paths that actually changed. The first edit keeps a copy of the generated output so POST /intelligence_tasks/{id}/revert can restore it; regenerating the task replaces the edited content. Requires a `read_write` scope API key and, for team members, update permission on GEO Writer. On a product_listing task, description_html keeps only p, ul, ol, li, strong, em, h3 and br, with no attributes: other markup is stripped, not refused, so read the stored value from the response. A revert restores the generated description cleaned the same way.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric task ID or public_id string token",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/IntelligenceTaskUpdateRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated task with the paths that changed",
            "schema": {
              "$ref": "#/definitions/IntelligenceTaskUpdateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "409": {
            "description": "The task was regenerated since the result_version in the request was read (ERR_STALE_CONTENT). Nothing was written; error.meta.result_version holds the current token. Read the task again and resend the edits",
            "schema": {
              "$ref": "#/definitions/Error"
            },
            "examples": {
              "application/json": {
                "error": {
                  "code": "ERR_STALE_CONTENT",
                  "message": "The task was regenerated since it was read; its current result_version is 1775642580. Read it again and resend your edits.",
                  "meta": {
                    "message": "The task was regenerated since it was read; its current result_version is 1775642580. Read it again and resend your edits.",
                    "result_version": "1775642580"
                  }
                },
                "request_id": "abc-123"
              }
            }
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/intelligence_tasks/{id}/revert": {
      "post": {
        "tags": [
          "GEO Writer"
        ],
        "summary": "Revert GEO Writer task content",
        "x-llms-description": "Discard every manual edit on a GEO Writer task and restore the generated output.",
        "operationId": "revertIntelligenceTaskContent",
        "x-mcp-tool": "revert_intelligence_task_content",
        "x-write": true,
        "description": "Discards every manual edit on the task and restores the output as it was generated. On a product_listing task, description_html comes back with only p, ul, ol, li, strong, em, h3 and br and no attributes, cleaned like an edit. Returns ERR_INVALID_PARAM when the task has no manual edits. Requires a `read_write` scope API key and, for team members, update permission on GEO Writer.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric task ID or public_id string token",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Task restored to its generated output",
            "schema": {
              "$ref": "#/definitions/IntelligenceTask"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/technical_geo_reports": {
      "get": {
        "tags": [
          "Technical GEO Reports"
        ],
        "summary": "List technical GEO reports",
        "x-llms-description": "List technical GEO reports of one type for a project, newest first, with status and score.",
        "operationId": "listTechnicalGeoReports",
        "x-mcp-tool": "list_technical_geo_reports",
        "description": "Lists reports of one technical GEO type for a project, newest first. Use agent_readiness for the AI/Agent Readiness report.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "report_type",
            "in": "query",
            "required": true,
            "type": "string",
            "enum": [
              "crawlability",
              "schema",
              "content_readiness",
              "discoverability",
              "site_structure",
              "robots_txt",
              "agent_readiness",
              "llms_txt",
              "ai_visibility"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "description": "Optional status filter; valid values depend on report_type",
            "type": "string"
          },
          {
            "name": "batch_id",
            "in": "query",
            "description": "Optional batch id returned when the report bundle was created",
            "type": "integer"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated technical GEO report summaries. Every summary carries app_url, the link that opens the report in the app"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "post": {
        "tags": [
          "Technical GEO Reports"
        ],
        "summary": "Run technical GEO analysis",
        "x-llms-description": "Run the full nine-report technical GEO analysis bundle for a URL + country. The bundle starts only when nine daily units remain; each successfully created report uses one unit, and reports that are not created use none. Optional output_language_code (ISO 639-1, or auto to keep the language of the website) sets the language of the llms.txt files.",
        "operationId": "createTechnicalGeoReports",
        "x-mcp-tool": "create_technical_geo_report",
        "x-write": true,
        "description": "Launches the full nine-report technical GEO analysis bundle for a URL + country. The bundle starts only when at least nine daily units remain. Each successfully created report uses one unit; a report that is not created uses none. Daily allocations vary by account. Each report runs in a background job. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "url"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "url": {
                  "type": "string"
                },
                "country_code": {
                  "type": "string",
                  "description": "Defaults to the project country"
                },
                "output_language_code": {
                  "type": "string",
                  "description": "ISO 639-1 code of the language the llms.txt files are written in (for example es), or auto to keep the language detected on the website. Defaults to the project language, else en. Only the llms.txt report of the bundle uses it; the response echoes the code used, or auto. An unsupported code returns 422 ERR_INVALID_PARAM"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created. app_urls maps each created report type to the link that opens that report in the app"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/technical_geo_reports/{id}": {
      "get": {
        "tags": [
          "Technical GEO Reports"
        ],
        "summary": "Get a technical GEO report",
        "x-llms-description": "Get one technical GEO report with current status and full result_data when completed.",
        "operationId": "getTechnicalGeoReport",
        "x-mcp-tool": "get_technical_geo_report",
        "description": "Returns the current status and the full result_data once the report is completed. While it is running, result_data is null and poll_after_seconds tells clients when to check again. Summaries carry output_language_code (the ISO 639-1 code an llms_txt report was requested in; null for an llms_txt report written in the website's own language, requested as auto or chosen in the app, and for every other report type); a completed llms_txt result_data also returns content_version (send it back to PATCH /technical_geo_reports/{id}/content), manually_edited_at, original_llms_txt_content and original_llms_full_txt_content (the generated files, kept from the first manual edit in the app, the API or MCP) and metadata.output_language_code.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "report_type",
            "in": "query",
            "required": true,
            "type": "string",
            "enum": [
              "crawlability",
              "schema",
              "content_readiness",
              "discoverability",
              "site_structure",
              "robots_txt",
              "agent_readiness",
              "llms_txt",
              "ai_visibility"
            ]
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Report id returned by POST /technical_geo_reports or GET /technical_geo_reports",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Report status and completed result data, plus app_url, the link that opens the report in the app"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/technical_geo_reports/{id}/content": {
      "patch": {
        "tags": [
          "Technical GEO Reports"
        ],
        "summary": "Edit llms.txt report content",
        "x-llms-description": "Replace the llms.txt and llms-full.txt files of a completed llms_txt report: edits holds the full new text of one or both files and content_version must match result_data.content_version as last read; the generated files are kept for revert.",
        "operationId": "updateTechnicalGeoReportContent",
        "x-mcp-tool": "update_technical_geo_report_content",
        "x-write": true,
        "description": "Replaces the llms.txt and llms-full.txt files of a completed llms_txt report in place, without generating them again. `edits` maps llms_txt and/or llms_full_txt to the full replacement text. `content_version` must equal result_data.content_version of the report as last read; when the report changed since, the edit is refused as stale and the message names the current version. A missing or stale content_version, a blank file, a file over 200,000 characters, a value that is not text, an unknown file key, an empty `edits` object, a report that has not completed or a report_type other than llms_txt is rejected with ERR_INVALID_PARAM and nothing is written. Files are stored with Unix line endings and one trailing newline. A file identical to the stored one is ignored, and the response lists the files that actually changed. The first edit keeps the generated files in original_llms_txt_content and original_llms_full_txt_content so POST /technical_geo_reports/{id}/revert_content can restore them; running the report again creates a new report without these edits. Requires a `read_write` scope API key and, for team members, create permission on GEO Optimization.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Report id returned by POST /technical_geo_reports or GET /technical_geo_reports",
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/TechnicalGeoReportContentUpdateRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The report with its current files, plus the files that changed",
            "schema": {
              "$ref": "#/definitions/TechnicalGeoReportContentUpdateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/technical_geo_reports/{id}/revert_content": {
      "post": {
        "tags": [
          "Technical GEO Reports"
        ],
        "summary": "Revert llms.txt report content",
        "x-llms-description": "Discard every manual edit on an llms_txt report and restore the generated llms.txt and llms-full.txt files.",
        "operationId": "revertTechnicalGeoReportContent",
        "x-mcp-tool": "revert_technical_geo_report_content",
        "x-write": true,
        "description": "Discards every manual edit on the llms_txt report and restores the llms.txt and llms-full.txt files exactly as they were generated. Returns ERR_INVALID_PARAM when the report has no manual edits or report_type is not llms_txt. Requires a `read_write` scope API key and, for team members, create permission on GEO Optimization.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Report id returned by POST /technical_geo_reports or GET /technical_geo_reports",
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/TechnicalGeoReportContentRevertRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The report with its generated files restored",
            "schema": {
              "$ref": "#/definitions/LlmsTxtTechnicalGeoReport"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/geo_audits": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "List GEO audits",
        "x-llms-description": "List a project's GEO audits (one technical GEO check type on one domain or URL, run once or weekly/monthly) with their latest run score, open issues and next run.",
        "operationId": "listGeoAudits",
        "x-mcp-tool": "list_geo_audits",
        "description": "Lists the project's audits, most recently updated first. Archived audits are left out unless status=archived.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "audit_type",
            "in": "query",
            "type": "string",
            "enum": [
              "agent_readiness",
              "robots_txt",
              "crawlability",
              "schema",
              "content_readiness",
              "discoverability",
              "site_structure"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "type": "string",
            "enum": [
              "active",
              "paused",
              "archived"
            ]
          },
          {
            "name": "cadence",
            "in": "query",
            "type": "string",
            "enum": [
              "once",
              "weekly",
              "monthly"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated audits",
            "schema": {
              "$ref": "#/definitions/GeoAuditList"
            }
          }
        }
      },
      "post": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Create GEO audits",
        "x-llms-description": "Create one GEO audit per audit type for a domain or URL, once or weekly/monthly; each starts its first run at once.",
        "operationId": "createGeoAudits",
        "x-mcp-tool": "create_geo_audit",
        "x-write": true,
        "description": "Creates one audit per entry of audit_types and starts the first run of each (it counts against the manual run limits: 6 per audit per hour, 200 per account per day). cadence weekly or monthly is accepted only for types whose checks are tracked run to run, and counts against the plan limit of active recurring audits (ERR_LIMIT_REACHED). Creating an audit that was archived restores it with its history. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/GeoAuditCreateRequest"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The created audits",
            "schema": {
              "$ref": "#/definitions/GeoAuditCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/geo_audits/{id}": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Get a GEO audit",
        "x-llms-description": "Get one GEO audit with its schedule, status, latest run and open issue counts.",
        "operationId": "getGeoAudit",
        "x-mcp-tool": "get_geo_audit",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "The audit",
            "schema": {
              "$ref": "#/definitions/GeoAuditResponse"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Update a GEO audit",
        "x-llms-description": "Change a GEO audit's cadence, schedule day and hour, email alerts, or status (pause, resume or archive).",
        "operationId": "updateGeoAudit",
        "x-mcp-tool": "update_geo_audit",
        "x-write": true,
        "description": "Updates the schedule, the email alerts or the status. Making an audit recurring or resuming it counts against the plan limit of active recurring audits (ERR_LIMIT_REACHED). Requires a `read_write` scope API key and, for team members, update permission on GEO Optimization.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/GeoAuditUpdateRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The updated audit",
            "schema": {
              "$ref": "#/definitions/GeoAuditResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Delete (archive) a GEO audit",
        "x-llms-description": "Archive a GEO audit; it stops running, and creating it again restores it with its history.",
        "operationId": "deleteGeoAudit",
        "x-mcp-tool": "delete_geo_audit",
        "x-write": true,
        "description": "Archives the audit. Requires a `read_write` scope API key and, for team members, delete permission on GEO Optimization.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Archived",
            "schema": {
              "$ref": "#/definitions/GeoAuditArchived"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/geo_audits/{id}/comparison": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Compare two GEO audit runs",
        "x-llms-description": "Compare two runs of a GEO audit check by check (new, fixed, changed, appeared, disappeared) with score and metric deltas; defaults to the latest run against the previous one.",
        "operationId": "compareGeoAuditRuns",
        "x-mcp-tool": "compare_geo_audit_runs",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "name": "from_run",
            "in": "query",
            "description": "Run number to compare from (default the run before to_run)",
            "type": "integer"
          },
          {
            "name": "to_run",
            "in": "query",
            "description": "Run number to compare to (default the latest completed run)",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "The comparison",
            "schema": {
              "$ref": "#/definitions/GeoAuditComparison"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/geo_audits/{geo_audit_id}/runs": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "List the runs of a GEO audit",
        "x-llms-description": "List a GEO audit's runs, newest first, with score, status, trigger and new, fixed and regressed issue counts; also as flat or CSV output.",
        "operationId": "listGeoAuditRuns",
        "x-mcp-tool": "list_geo_audit_runs",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "geo_audit_id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated runs",
            "schema": {
              "$ref": "#/definitions/GeoAuditRunList"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Run a GEO audit now",
        "x-llms-description": "Start a manual run of a GEO audit (6 per audit per hour, 200 per account per day) and poll it until it finishes.",
        "operationId": "runGeoAudit",
        "x-mcp-tool": "run_geo_audit",
        "x-write": true,
        "description": "Starts a run and returns it with status queued; poll GET /geo_audits/{geo_audit_id}/runs/{sequence} until status is completed, failed or unreachable. Limited to 6 manual runs per audit per rolling hour and 200 per account per day (ERR_LIMIT_REACHED); scheduled runs do not count. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "geo_audit_id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          }
        ],
        "responses": {
          "201": {
            "description": "The new run",
            "schema": {
              "$ref": "#/definitions/GeoAuditRunResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/geo_audits/{geo_audit_id}/runs/{sequence}": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Get a GEO audit run",
        "x-llms-description": "Get one run of a GEO audit with its score, metrics and full result_data.",
        "operationId": "getGeoAuditRun",
        "x-mcp-tool": "get_geo_audit_run",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "geo_audit_id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "name": "sequence",
            "in": "path",
            "required": true,
            "description": "Run number within the audit",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "The run with its result",
            "schema": {
              "$ref": "#/definitions/GeoAuditRunDetail"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/geo_audits/{geo_audit_id}/runs/{sequence}/findings": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "List the findings of a GEO audit run",
        "x-llms-description": "List the findings of one GEO audit run (check, subject, pass/warn/fail/info/unknown, severity, evidence), most severe first; also as flat or CSV output.",
        "operationId": "listGeoAuditFindings",
        "x-mcp-tool": "list_geo_audit_findings",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "geo_audit_id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "name": "sequence",
            "in": "path",
            "required": true,
            "description": "Run number within the audit",
            "type": "integer"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          },
          {
            "$ref": "#/parameters/Output"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated findings",
            "schema": {
              "$ref": "#/definitions/GeoAuditFindingList"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/geo_audits/{geo_audit_id}/issues": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "List the issues of a GEO audit",
        "x-llms-description": "List a GEO audit's issues across runs with severity, state (open, fixed, gone), latest change badge, acceptance and evidence.",
        "operationId": "listGeoAuditIssues",
        "x-mcp-tool": "list_geo_audit_issues",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "geo_audit_id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "name": "state",
            "in": "query",
            "description": "open means open and not accepted; default all",
            "type": "string",
            "enum": [
              "open",
              "accepted",
              "fixed",
              "gone"
            ]
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated issues",
            "schema": {
              "$ref": "#/definitions/GeoAuditIssueList"
            }
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/geo_audits/{geo_audit_id}/issues/{id}": {
      "patch": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "Accept or reopen a GEO audit issue",
        "x-llms-description": "Accept a GEO audit issue (it stays listed but leaves the open count until its evidence changes) or reopen it.",
        "operationId": "updateGeoAuditIssue",
        "x-mcp-tool": "update_geo_audit_issue",
        "x-write": true,
        "description": "Requires a `read_write` scope API key and, for team members, update permission on GEO Optimization.",
        "parameters": [
          {
            "name": "geo_audit_id",
            "in": "path",
            "required": true,
            "description": "Audit id",
            "type": "string"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Issue id",
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/GeoAuditIssueUpdateRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The updated issue",
            "schema": {
              "$ref": "#/definitions/GeoAuditIssueResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/geo_alerts": {
      "get": {
        "tags": [
          "GEO Audits"
        ],
        "summary": "List GEO audit alerts",
        "x-llms-description": "List the alerts a project's GEO audits raised (critical check newly failing, failing again, recovered, score drop, site unreachable), newest first.",
        "operationId": "listGeoAlerts",
        "x-mcp-tool": "list_geo_alerts",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "audit_id",
            "in": "query",
            "description": "Only alerts of this audit",
            "type": "string"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated alerts",
            "schema": {
              "$ref": "#/definitions/GeoAlertList"
            }
          }
        }
      }
    },
    "/annotations": {
      "get": {
        "tags": [
          "Annotations"
        ],
        "summary": "List timeline annotations",
        "x-llms-description": "List project timeline annotations (user + system), with date and category filters. Available on every plan.",
        "operationId": "listAnnotations",
        "x-mcp-tool": "list_annotations",
        "description": "Lists project timeline annotations, newest first. Rows can come from manual notes, project automations, GEO tests, or platform events. The origin field distinguishes them; editable says whether the requesting user may modify the row. Available on every plan.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "from",
            "in": "query",
            "type": "string",
            "format": "date"
          },
          {
            "name": "to",
            "in": "query",
            "type": "string",
            "format": "date"
          },
          {
            "name": "annotation_category_id",
            "in": "query",
            "type": "integer"
          },
          {
            "$ref": "#/parameters/Page"
          },
          {
            "$ref": "#/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated annotations"
          },
          "403": {
            "$ref": "#/responses/InsufficientPermission"
          }
        }
      },
      "post": {
        "tags": [
          "Annotations"
        ],
        "summary": "Create a timeline annotation",
        "x-llms-description": "Add a timeline annotation to a project. Available on every plan.",
        "operationId": "createAnnotation",
        "x-mcp-tool": "create_annotation",
        "x-write": true,
        "description": "Marks a date in the project timeseries with a title + description. Available on every plan. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "title"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "title": {
                  "type": "string"
                },
                "annotation_date": {
                  "type": "string",
                  "format": "date",
                  "description": "ISO YYYY-MM-DD; defaults to today"
                },
                "description": {
                  "type": "string"
                },
                "color": {
                  "type": "string",
                  "description": "Hex color, e.g. #2563eb"
                },
                "annotation_category_id": {
                  "type": "integer"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created",
            "schema": {
              "$ref": "#/definitions/AnnotationCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/annotations/{id}": {
      "patch": {
        "tags": [
          "Annotations"
        ],
        "summary": "Update a timeline annotation",
        "x-llms-description": "Update an own-user annotation (title, description, date, color, category). Available on every plan.",
        "operationId": "updateAnnotation",
        "x-mcp-tool": "update_annotation",
        "x-write": true,
        "description": "Updates title, description, annotation_date, color and/or annotation_category_id. Only user-created annotations belonging to the requesting user can be updated (system annotations never). Available on every plan and requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "title": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "annotation_date": {
                  "type": "string",
                  "format": "date"
                },
                "color": {
                  "type": "string"
                },
                "annotation_category_id": {
                  "type": "integer"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated"
          },
          "403": {
            "$ref": "#/responses/InsufficientPermission"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "delete": {
        "tags": [
          "Annotations"
        ],
        "summary": "Delete a timeline annotation",
        "x-llms-description": "Delete an own-user annotation. Available on every plan.",
        "operationId": "deleteAnnotation",
        "x-mcp-tool": "delete_annotation",
        "x-write": true,
        "description": "Deletes an annotation. Same ownership rule as PATCH. Available on every plan and requires a `read_write` scope API key.",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "403": {
            "$ref": "#/responses/InsufficientPermission"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/team_members": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "List team members",
        "x-llms-description": "The account team, owner first, with each member's access level, project access, status and last sign-in.",
        "operationId": "listTeamMembers",
        "x-mcp-tool": "list_team_members",
        "description": "Lists the account owner first, then every team member who joined, with their access level (permission_role, account role and the resulting permissions), co-owner and billing flags, project access, status (active or blocked) and last sign-in. Pending invitations are listed by GET /team_invitations; GET /account limits.team_members.used counts both. Available to the account owner, co-owners and team members with the Team Members permission. A key limited to some projects answers ERR_KEY_PROJECT_SCOPED: the team belongs to the whole account.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "type": "integer"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Default 20, max 100",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "page": {
                  "type": "integer"
                },
                "per_page": {
                  "type": "integer"
                },
                "total": {
                  "type": "integer"
                },
                "data": {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/TeamMember"
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/TeamForbidden"
          }
        }
      }
    },
    "/team_invitations": {
      "get": {
        "tags": [
          "Team"
        ],
        "summary": "List pending team invitations",
        "x-llms-description": "Pending team invitations with the access they grant, when they were sent and when they expire.",
        "operationId": "listTeamInvitations",
        "x-mcp-tool": "list_team_invitations",
        "description": "Lists invitations that have not been accepted yet: the address, the access and projects the person will get, when the invitation was sent and when its link expires. An accepted invitation keeps its id and moves to GET /team_members. Same access rules as GET /team_members.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "type": "integer"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Default 20, max 100",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "page": {
                  "type": "integer"
                },
                "per_page": {
                  "type": "integer"
                },
                "total": {
                  "type": "integer"
                },
                "data": {
                  "type": "array",
                  "items": {
                    "$ref": "#/definitions/TeamInvitation"
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/TeamForbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Team"
        ],
        "summary": "Invite a team member",
        "x-llms-description": "Invite someone to the team by email, choosing their projects and access; they receive the invitation email.",
        "operationId": "createTeamInvitation",
        "x-mcp-tool": "invite_team_member",
        "x-write": true,
        "description": "Invites someone to the account team with the same rules as the Team Members settings page, and sends them the invitation email (email_sent is false when the email provider refused it; the invitation still exists, so revoke it and invite again once the address is fixed). Send project_ids, or all_projects: true for every project (for a team member inviting, every project they can access). The account owner and co-owners choose the access with permission_role: full_access (default), view_only, custom (with permissions, feature key to verbs) or account_role (with account_role_id). A team member with permission to invite cannot choose access: leave those fields out and the invitation copies their own permissions. Only the account owner can set account_admin (co-owner) or billing_access. An address already registered on the platform, or with an invitation pending, is refused with ERR_INVALID_PARAM (revoke the pending one first). An account that cannot add more team members, or that reached its daily limit of invitations sent through the API and MCP (revoked ones count), answers ERR_LIMIT_REACHED; the message says which. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "email"
              ],
              "properties": {
                "email": {
                  "type": "string"
                },
                "project_ids": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  },
                  "description": "Projects the member can access. Send this or all_projects true."
                },
                "all_projects": {
                  "type": "boolean",
                  "description": "Access to every project, including future ones"
                },
                "permission_role": {
                  "type": "string",
                  "enum": [
                    "full_access",
                    "view_only",
                    "custom",
                    "account_role"
                  ],
                  "description": "Default full_access. Owner and co-owners only."
                },
                "account_role_id": {
                  "type": "integer",
                  "description": "One of the account's roles, with permission_role account_role"
                },
                "permissions": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "description": "With permission_role custom: feature key to the verbs granted (read, create, update, delete), for example {\"overview\": [\"read\"], \"prompts\": [\"read\", \"create\"]}"
                },
                "account_admin": {
                  "type": "boolean",
                  "description": "Make the member a co-owner. Account owner only."
                },
                "billing_access": {
                  "type": "boolean",
                  "description": "Access to Billing and Plans. Account owner only."
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created. The invitation, as GET /team_invitations lists it, and whether the email went out.",
            "schema": {
              "allOf": [
                {
                  "$ref": "#/definitions/TeamInvitation"
                },
                {
                  "type": "object",
                  "required": [
                    "email_sent"
                  ],
                  "properties": {
                    "email_sent": {
                      "type": "boolean",
                      "description": "False when the email provider refused the invitation email"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              ]
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/TeamForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/team_invitations/{id}": {
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Revoke a pending team invitation",
        "x-llms-description": "Revoke a pending team invitation; its link stops working immediately.",
        "operationId": "revokeTeamInvitation",
        "x-mcp-tool": "revoke_team_invitation",
        "x-write": true,
        "description": "Revokes a pending invitation: its link stops working immediately. Account owner or co-owner only, and a co-owner cannot revoke an invitation that makes someone a co-owner. Members who already joined are removed with DELETE /team_members/{id}; an id that is not a pending invitation of the account answers ERR_NOT_FOUND. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The invitation id from GET /team_invitations",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "revoked": {
                  "type": "boolean"
                },
                "id": {
                  "type": "integer"
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/TeamForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/team_members/{id}": {
      "delete": {
        "tags": [
          "Team"
        ],
        "summary": "Remove a team member",
        "x-llms-description": "Remove a team member; their sessions and API keys stop working at once and the account is deleted shortly after.",
        "operationId": "removeTeamMember",
        "x-mcp-tool": "remove_team_member",
        "x-write": true,
        "description": "Removes a member from the team, as the settings page does: their sessions and API keys stop working at once and their user account is deleted in the background shortly after. Account owner or co-owner only; a co-owner cannot remove the owner, another co-owner or themselves. The owner cannot be removed. Pending invitations are revoked with DELETE /team_invitations/{id}; an id that is not a member of the account answers ERR_NOT_FOUND. Requires a `read_write` scope API key.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The member id from GET /team_members",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "removed": {
                  "type": "boolean"
                },
                "id": {
                  "type": "integer"
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/TeamForbidden"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/store_connection": {
      "get": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "Match a store to a project",
        "x-llms-description": "Store app setup: whether the account may use a store integration and which project a store domain maps to, plus candidate projects. Not available to white-label or embed accounts.",
        "operationId": "getStoreConnection",
        "x-rest-only": "Called by the store app (Shopify) to read its own connection, not by agents.",
        "description": "Tells a store app whether the API key's account can use it and which project the store belongs to: the live project whose domain equals the store domain, else one whose domain is a parent or a subdomain of it, else null. candidates lists every live project of the account so the app can offer a picker. Takes no project_id. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "name": "platform",
            "in": "query",
            "required": true,
            "description": "Store platform",
            "type": "string",
            "enum": [
              "shopify"
            ]
          },
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "Store domain, with or without scheme, e.g. acme-store.com",
            "type": "string"
          }
        ],
        "responses": {
          "200": {
            "description": "Eligibility, the matching project and the candidates",
            "schema": {
              "$ref": "#/definitions/StoreConnectionResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/ai_orders": {
      "get": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "Read AI-referred store orders",
        "x-llms-description": "AI-referred store orders and revenue for a project: totals, per AI assistant and a daily series (default last 90 days).",
        "operationId": "listAiOrders",
        "x-mcp-tool": "get_ai_orders",
        "x-mcp-plan": "growth",
        "description": "Reads back the AI-referred orders a store app pushed for a project: totals, one row per AI assistant and a daily series of the days with orders. Revenue values are decimal strings in currency. Team members need read access to AI Traffic. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "platform",
            "in": "query",
            "description": "Store platform",
            "type": "string",
            "default": "shopify",
            "enum": [
              "shopify"
            ]
          },
          {
            "name": "from",
            "in": "query",
            "description": "First day (YYYY-MM-DD). Defaults to 89 days before to",
            "type": "string",
            "format": "date"
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last day (YYYY-MM-DD). Defaults to today; the window is at most 400 days",
            "type": "string",
            "format": "date"
          }
        ],
        "responses": {
          "200": {
            "description": "Totals, per-assistant rows and the daily series",
            "schema": {
              "$ref": "#/definitions/AiOrdersResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      },
      "put": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "Replace AI-referred store orders for a window",
        "x-llms-description": "Replace the daily AI-referred orders and revenue a store app reports for a window; raw referrers are classified and non-AI ones ignored.",
        "operationId": "replaceAiOrders",
        "x-rest-only": "Called by the store app (Shopify) to push AI-referred orders; agents read them with get_ai_orders.",
        "x-write": true,
        "description": "Replaces the daily AI-referred orders and revenue of the from..to window. Send the raw referring host or utm_source of each order's first visit as referrer: LLM Pulse classifies it and ignores anything that is not an AI assistant. Entries for the same day and assistant are summed. Every stored row of that project and platform inside the window is replaced, so pushing the same window again converges instead of counting twice. Rows are kept per project and platform, not per store, so one store reports per project. Requires a `read_write` scope API key and, for team members, update access to AI Traffic. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/AiOrdersUpdateRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rows stored and entries ignored",
            "schema": {
              "$ref": "#/definitions/AiOrdersUpdateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/catalog_prompt_suggestions": {
      "get": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "List catalog prompt suggestions",
        "x-llms-description": "List buyer prompts suggested from a store catalog, filterable by status and product.",
        "operationId": "listCatalogPromptSuggestions",
        "x-rest-only": "Store app (Shopify) catalog flow, not exposed to agents.",
        "description": "Lists the buyer prompts suggested from a store catalog, oldest first, with their status and the product each one came from. Team members need read access to Prompts. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "$ref": "#/parameters/ProjectId"
          },
          {
            "name": "status",
            "in": "query",
            "description": "Only suggestions in this status",
            "type": "string",
            "enum": [
              "pending",
              "accepted",
              "rejected"
            ]
          },
          {
            "name": "product_external_id",
            "in": "query",
            "description": "Only suggestions for this store product id",
            "type": "string"
          },
          {
            "name": "page",
            "in": "query",
            "type": "integer",
            "default": 1,
            "minimum": 1
          },
          {
            "name": "per_page",
            "in": "query",
            "type": "integer",
            "default": 50,
            "maximum": 100,
            "minimum": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated suggestions",
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionsResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "Suggest buyer prompts from catalog products",
        "x-llms-description": "Generate buyer prompt suggestions for up to 20 store catalog products (shares the hourly suggestion allowance).",
        "operationId": "createCatalogPromptSuggestions",
        "x-rest-only": "Store app (Shopify) catalog flow, not exposed to agents.",
        "x-write": true,
        "description": "Writes buyer prompts for up to 20 catalog products and saves them as pending suggestions in the project's Suggested prompts queue, with the product recorded on each. Generation draws on the hourly prompt-suggestion allowance the app also uses (ERR_QUOTA_EXCEEDED once it is used up); a failed generation returns ERR_GENERATION_FAILED (502) and can be retried. Requires a `read_write` scope API key and, for team members, create access to Prompts. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionsCreateRequest"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The suggestions saved for these products",
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionsCreateResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          },
          "502": {
            "$ref": "#/responses/GenerationFailed"
          }
        }
      }
    },
    "/catalog_prompt_suggestions/accept": {
      "post": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "Accept catalog prompt suggestions",
        "x-llms-description": "Track pending catalog suggestions as prompts, each tagged with a collection named after its product.",
        "operationId": "acceptCatalogPromptSuggestions",
        "x-rest-only": "Store app (Shopify) catalog flow, not exposed to agents.",
        "x-write": true,
        "description": "Starts tracking pending suggestions: each one becomes a prompt, tagged with a collection named after its product. Suggestions that are no longer pending come back in skipped. All accepted suggestions must share one country and language. When the new prompts would exceed the plan, the call returns ERR_LIMIT_REACHED and accepts nothing. Requires a `read_write` scope API key and, for team members, create access to Prompts. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionIdsRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Accepted and skipped suggestions",
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionsAcceptResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/catalog_prompt_suggestions/reject": {
      "post": {
        "tags": [
          "Store Integrations"
        ],
        "summary": "Reject catalog prompt suggestions",
        "x-llms-description": "Mark pending catalog suggestions as rejected.",
        "operationId": "rejectCatalogPromptSuggestions",
        "x-rest-only": "Store app (Shopify) catalog flow, not exposed to agents.",
        "x-write": true,
        "description": "Marks pending suggestions as rejected; suggestions that are no longer pending stay as they are. Requires a `read_write` scope API key and, for team members, update access to Prompts. Not available to accounts with a white-label portal or an embed (ERR_INTEGRATION_UNAVAILABLE).",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionIdsRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "How many suggestions were rejected",
            "schema": {
              "$ref": "#/definitions/CatalogPromptSuggestionsRejectResponse"
            }
          },
          "403": {
            "$ref": "#/responses/IntegrationUnavailable"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook subscriptions",
        "x-llms-description": "List active webhook subscriptions for the account (optional project_id filter).",
        "operationId": "listWebhooks",
        "x-mcp-tool": "list_webhook_subscriptions",
        "x-plan": "scale",
        "description": "Lists active webhook subscriptions for the account, optionally filtered by project. Requires the Scale plan or above.",
        "parameters": [
          {
            "name": "project_id",
            "in": "query",
            "required": false,
            "description": "Optional project filter",
            "type": "integer"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "type": "integer"
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Max 100",
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "page": {
                  "type": "integer"
                },
                "per_page": {
                  "type": "integer"
                },
                "total": {
                  "type": "integer"
                },
                "data": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "project_id": {
                        "type": "integer"
                      },
                      "event_type": {
                        "type": "string",
                        "enum": [
                          "mention.created",
                          "competitor_mention.created",
                          "citation.created",
                          "prompt_execution.completed",
                          "sentiment.negative_detected",
                          "recommendation.completed",
                          "intelligence_task.completed",
                          "intelligence_task.updated",
                          "geo_audit_run.completed",
                          "geo_audit_alert.triggered"
                        ]
                      },
                      "target_url": {
                        "type": "string"
                      },
                      "disabled": {
                        "type": "boolean",
                        "description": "True when the subscription receives no more events, including when the API key or OAuth connection that created it was revoked. Creating it again with the same project, event and URL replaces it with a new subscription and secret."
                      },
                      "failure_count": {
                        "type": "integer"
                      },
                      "last_delivered_at": {
                        "type": "string",
                        "format": "date-time",
                        "x-nullable": true
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create a webhook subscription",
        "x-llms-description": "Subscribe a public HTTPS URL to a project event (new mention, citation, execution, negative sentiment, completed report); deliveries are HMAC-signed. Scale plan or above.",
        "operationId": "createWebhook",
        "x-mcp-tool": "create_webhook_subscription",
        "x-plan": "scale",
        "x-write": true,
        "description": "Subscribes a public HTTPS URL to a project event. LLM Pulse POSTs a JSON envelope (`event`, `occurred_at`, `project_id`, `subscription_id`, `data`) to the URL every time the event occurs, signed via the `X-LLMPulse-Signature` header (HMAC-SHA256 of the raw body computed with the subscription secret). Failed deliveries receive up to 5 attempts in total, including the first, with backoff; subscriptions auto-disable after 20 consecutive failed deliveries. Idempotent for the same project + event + URL: re-posting returns the existing subscription, unless the API key or OAuth connection that created it was revoked; then it is replaced by a new subscription with a new secret. The response carries `secret` when it creates the subscription, and on a re-post only for the API key or OAuth connection that created it: any other credential gets the subscription without `secret`. Requires a `read_write` scope API key and the Scale plan or above.",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "required": [
                "project_id",
                "event_type",
                "target_url"
              ],
              "properties": {
                "project_id": {
                  "type": "integer"
                },
                "event_type": {
                  "type": "string",
                  "enum": [
                    "mention.created",
                    "competitor_mention.created",
                    "citation.created",
                    "prompt_execution.completed",
                    "sentiment.negative_detected",
                    "recommendation.completed",
                    "intelligence_task.completed",
                    "intelligence_task.updated",
                    "geo_audit_run.completed",
                    "geo_audit_alert.triggered"
                  ]
                },
                "target_url": {
                  "type": "string",
                  "description": "Public HTTPS URL that will receive signed event payloads"
                }
              }
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created, or the existing subscription on a re-post. The signing secret is only returned by this endpoint, and on a re-post only to the credential that created the subscription.",
            "schema": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "project_id": {
                  "type": "integer"
                },
                "event_type": {
                  "type": "string",
                  "enum": [
                    "mention.created",
                    "competitor_mention.created",
                    "citation.created",
                    "prompt_execution.completed",
                    "sentiment.negative_detected",
                    "recommendation.completed",
                    "intelligence_task.completed",
                    "intelligence_task.updated",
                    "geo_audit_run.completed",
                    "geo_audit_alert.triggered"
                  ]
                },
                "target_url": {
                  "type": "string"
                },
                "disabled": {
                  "type": "boolean",
                  "description": "True when the subscription receives no more events, including when the API key or OAuth connection that created it was revoked. Creating it again with the same project, event and URL replaces it with a new subscription and secret."
                },
                "failure_count": {
                  "type": "integer"
                },
                "last_delivered_at": {
                  "type": "string",
                  "format": "date-time",
                  "x-nullable": true
                },
                "created_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "secret": {
                  "type": "string",
                  "description": "HMAC signing secret (whsec_...). Returned when the subscription is created, and on a re-post only to the API key or OAuth connection that created it; absent for any other credential."
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook subscription",
        "x-llms-description": "Delete a webhook subscription; the target URL stops receiving events immediately.",
        "operationId": "deleteWebhook",
        "x-mcp-tool": "delete_webhook_subscription",
        "x-plan": "scale",
        "x-write": true,
        "description": "Deletes a webhook subscription; the target URL stops receiving events immediately. Requires a `read_write` scope API key and the Scale plan or above.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "deleted": {
                  "type": "boolean"
                },
                "id": {
                  "type": "integer"
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          }
        }
      }
    },
    "/webhooks/sample/{event_type}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Sample event payloads",
        "x-llms-description": "Up to 3 example event payloads for an event type, built from recent project data (used by integration editors).",
        "operationId": "sampleWebhookPayloads",
        "x-mcp-tool": "get_webhook_sample",
        "x-plan": "scale",
        "description": "Returns up to 3 example event payloads for the event type, built from the project's most recent real data (or a static sample when the project has no data). Used by integration editors such as the Zapier sample loader. Requires the Scale plan or above.",
        "parameters": [
          {
            "name": "event_type",
            "in": "path",
            "required": true,
            "type": "string",
            "enum": [
              "mention.created",
              "competitor_mention.created",
              "citation.created",
              "prompt_execution.completed",
              "sentiment.negative_detected",
              "recommendation.completed",
              "intelligence_task.completed",
              "intelligence_task.updated",
              "geo_audit_run.completed",
              "geo_audit_alert.triggered"
            ]
          },
          {
            "name": "project_id",
            "in": "query",
            "required": true,
            "type": "integer"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "schema": {
              "type": "object",
              "properties": {
                "event_type": {
                  "type": "string"
                },
                "data": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "event": {
                        "type": "string"
                      },
                      "occurred_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "project_id": {
                        "type": "integer"
                      },
                      "subscription_id": {
                        "type": "integer"
                      },
                      "data": {
                        "type": "object"
                      }
                    }
                  }
                },
                "request_id": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/responses/PlanRequired"
          },
          "404": {
            "$ref": "#/responses/NotFound"
          },
          "422": {
            "$ref": "#/responses/UnprocessableEntity"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "summary": "Model Context Protocol endpoint",
        "x-llms-description": "Model Context Protocol (MCP) JSON-RPC endpoint for AI clients.",
        "description": "JSON-RPC 2.0 endpoint for AI clients using the Model Context Protocol. Available on every plan and during eligible trials. Two auth options are accepted: (1) **OAuth 2.1 access token** issued via `/oauth/token` (recommended: clients discover the flow at `/.well-known/oauth-authorization-server` and Dynamic Client Registration is supported at `/oauth/register`); (2) **API key** in the `Authorization: Bearer` header (requires Scale plan or above, suitable for headless integrations). The server filters `tools/list` per user plan; calls to gated tools return `ERR_PLAN_REQUIRED`.",
        "operationId": "handleMcpPost",
        "x-rest-only": "The MCP transport itself.",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2": [
              "mcp:read",
              "mcp:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/McpRequest"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "schema": {
              "$ref": "#/definitions/McpResponse"
            }
          },
          "202": {
            "description": "Notification accepted; empty response body"
          }
        }
      },
      "get": {
        "tags": [
          "MCP"
        ],
        "summary": "MCP GET handshake",
        "x-llms-description": "Model Context Protocol (MCP) JSON-RPC endpoint for AI clients (GET handshake).",
        "operationId": "handleMcpGet",
        "x-rest-only": "The MCP transport itself.",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "OAuth2": [
              "mcp:read",
              "mcp:write"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          }
        }
      }
    },
    "/mcp/server-card": {
      "get": {
        "tags": [
          "MCP"
        ],
        "summary": "MCP server card",
        "x-llms-description": "Public MCP server card describing the endpoint, transport and auth. No authentication required.",
        "operationId": "getMcpServerCard",
        "x-rest-only": "MCP discovery metadata, read before a session exists.",
        "description": "Public MCP Server Card describing this server: name, version, the streamable HTTP endpoint, the protocol versions it speaks and the Authorization header a client must send. Requires no authentication, because it is the document a client reads before it holds a credential. The card contains no credential itself; the Authorization entry is a template marked isSecret. Advertised in the Agentic Resource Discovery catalog at /.well-known/ai-catalog.json, and deliberately NOT served at /.well-known/mcp/server-card.json, a location the MCP specification rules out because a single server's card is application-level rather than site-wide metadata.",
        "security": [],
        "produces": [
          "application/mcp-server-card+json"
        ],
        "responses": {
          "200": {
            "description": "MCP server card",
            "schema": {
              "type": "object"
            }
          },
          "304": {
            "description": "Not modified (If-None-Match matched the ETag)"
          }
        }
      }
    }
  },
  "responses": {
    "Unauthorized": {
      "description": "Authentication failed",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_MISSING_AUTH",
            "message": "missing_authorization"
          },
          "request_id": "abc-123"
        }
      }
    },
    "NotFound": {
      "description": "Resource not found",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_PROJECT_NOT_FOUND",
            "message": "project_not_found"
          },
          "request_id": "abc-123"
        }
      }
    },
    "Forbidden": {
      "description": "Access forbidden",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_REVOKED_API_KEY",
            "message": "revoked_api_key"
          },
          "request_id": "abc-123"
        }
      }
    },
    "PlanRequired": {
      "description": "Endpoint requires a higher plan tier",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_PLAN_REQUIRED",
            "message": "Scale plan or above required"
          },
          "request_id": "abc-123"
        }
      }
    },
    "GrowthPlanRequired": {
      "description": "Endpoint requires the Growth plan or above",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_PLAN_REQUIRED",
            "message": "Growth plan or above required"
          },
          "request_id": "abc-123"
        }
      }
    },
    "SearchConsoleForbidden": {
      "description": "Plan below Growth (ERR_PLAN_REQUIRED), or Google revoked the project's Search Console access (ERR_SEARCH_CONSOLE_ACCESS_REVOKED): reconnect the property",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_SEARCH_CONSOLE_ACCESS_REVOKED",
            "message": "Google Search Console access was revoked. Reconnect the property in Preferences > Project Settings > Data Connections."
          },
          "request_id": "abc-123"
        }
      }
    },
    "InsufficientScope": {
      "description": "API key lacks write permission",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_INSUFFICIENT_SCOPE",
            "message": "insufficient_scope_write_required"
          },
          "request_id": "abc-123"
        }
      }
    },
    "KeyProjectScoped": {
      "description": "Writes with a read-only key answer ERR_INSUFFICIENT_SCOPE, and a key limited to some projects ERR_KEY_PROJECT_SCOPED, with the same status: this operation acts on the whole account",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_KEY_PROJECT_SCOPED",
            "message": "api_key_limited_to_projects"
          },
          "request_id": "abc-123"
        }
      }
    },
    "InsufficientPermission": {
      "description": "API key belongs to a team member whose permission matrix does not grant this feature",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_INSUFFICIENT_PERMISSION",
            "message": "insufficient_team_member_permission"
          },
          "request_id": "abc-123"
        }
      }
    },
    "TeamForbidden": {
      "description": "A key limited to some projects answers ERR_KEY_PROJECT_SCOPED, since the team belongs to the whole account. A team member without the Team Members permission, or asking for something only the owner or a co-owner can do, answers ERR_INSUFFICIENT_PERMISSION, and a write with a read-only key ERR_INSUFFICIENT_SCOPE, with the same status",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_INSUFFICIENT_PERMISSION",
            "message": "insufficient_team_member_permission"
          },
          "request_id": "abc-123"
        }
      }
    },
    "IntegrationUnavailable": {
      "description": "The account has a white-label portal or an embed, so store integrations are not available (ERR_INTEGRATION_UNAVAILABLE). Writes with a read-only key answer ERR_INSUFFICIENT_SCOPE, and a team member without the permission ERR_INSUFFICIENT_PERMISSION, with the same status",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_INTEGRATION_UNAVAILABLE",
            "message": "Store integrations are not available for white-label or embed accounts"
          },
          "request_id": "abc-123"
        }
      }
    },
    "GenerationFailed": {
      "description": "The AI generation failed; retry the request",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_GENERATION_FAILED",
            "message": "suggestions could not be generated, try again"
          },
          "request_id": "abc-123"
        }
      }
    },
    "UnprocessableEntity": {
      "description": "Invalid parameters",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_INVALID_PARAM",
            "message": "invalid metrics"
          },
          "request_id": "abc-123"
        }
      }
    },
    "ServiceUnavailable": {
      "description": "Upstream provider unavailable, retry after the number of seconds in the Retry-After header: Google's own value when it sent one, otherwise 900 after a quota error and 60 after an outage",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_SEARCH_CONSOLE_UPSTREAM",
            "message": "Google Search Console is temporarily unavailable. Retry in a few minutes."
          },
          "request_id": "abc-123"
        }
      },
      "headers": {
        "Retry-After": {
          "description": "Seconds to wait before retrying",
          "type": "integer"
        }
      }
    },
    "PortalWriteForbidden": {
      "description": "Writes with a read-only key answer ERR_INSUFFICIENT_SCOPE, a team member without the permission ERR_INSUFFICIENT_PERMISSION, and a request made on a white-label portal host, where the app does not offer this action, ERR_INTEGRATION_UNAVAILABLE, with the same status",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_INSUFFICIENT_SCOPE",
            "message": "insufficient_scope_write_required"
          },
          "request_id": "abc-123"
        }
      }
    },
    "ReportSendCooldown": {
      "description": "The report was sent less than 10 minutes ago; retry after the number of seconds in the Retry-After header",
      "schema": {
        "$ref": "#/definitions/Error"
      },
      "examples": {
        "application/json": {
          "error": {
            "code": "ERR_RATE_LIMITED",
            "message": "This report was just sent; try again in 540 seconds"
          },
          "request_id": "abc-123"
        }
      },
      "headers": {
        "Retry-After": {
          "description": "Seconds to wait before retrying",
          "type": "integer"
        }
      }
    }
  },
  "definitions": {
    "SearchConsoleFilters": {
      "type": "array",
      "maxItems": 10,
      "description": "The value of the filters query parameter, JSON-encoded into a single string.",
      "items": {
        "type": "object",
        "required": [
          "dimension",
          "expression"
        ],
        "properties": {
          "dimension": {
            "type": "string",
            "enum": [
              "country",
              "device",
              "page",
              "query",
              "searchAppearance"
            ]
          },
          "operator": {
            "type": "string",
            "enum": [
              "contains",
              "equals",
              "notContains",
              "notEquals",
              "includingRegex",
              "excludingRegex"
            ],
            "default": "contains"
          },
          "expression": {
            "type": "string",
            "maxLength": 500
          }
        }
      }
    },
    "AccountQuota": {
      "type": "object",
      "description": "A consumable quota. limit and remaining are null when unlimited is true. For a key limited to some projects, prompts and intelligence_tasks carry no limit (and intelligence_tasks no used): only the capacity left.",
      "properties": {
        "limit": {
          "type": "integer",
          "x-nullable": true
        },
        "used": {
          "type": "integer"
        },
        "remaining": {
          "type": "integer",
          "x-nullable": true
        },
        "unlimited": {
          "type": "boolean"
        },
        "period": {
          "type": "string",
          "description": "Reset window for quotas that reset (e.g. month)"
        }
      }
    },
    "AccountCapacity": {
      "type": "object",
      "description": "A ceiling with no usage counter attached. limit is null when unlimited is true.",
      "properties": {
        "limit": {
          "type": "integer",
          "x-nullable": true
        },
        "unlimited": {
          "type": "boolean"
        }
      }
    },
    "Error": {
      "type": "object",
      "properties": {
        "error": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string",
              "enum": [
                "ERR_MISSING_AUTH",
                "ERR_INVALID_API_KEY",
                "ERR_REVOKED_API_KEY",
                "ERR_INSUFFICIENT_SCOPE",
                "ERR_INSUFFICIENT_PERMISSION",
                "ERR_KEY_PROJECT_SCOPED",
                "ERR_PLAN_REQUIRED",
                "ERR_INTEGRATION_UNAVAILABLE",
                "ERR_GENERATION_FAILED",
                "ERR_ACCOUNT_INACTIVE",
                "ERR_DRAFT_NOT_FOUND",
                "ERR_DRAFT_STATE",
                "ERR_PROJECT_NOT_FOUND",
                "ERR_NOT_FOUND",
                "ERR_SEARCH_CONSOLE_NOT_CONNECTED",
                "ERR_SEARCH_CONSOLE_ACCESS_REVOKED",
                "ERR_SEARCH_CONSOLE_UPSTREAM",
                "ERR_AI_TRAFFIC_NOT_CONNECTED",
                "ERR_WEB_ANALYTICS_NOT_CONNECTED",
                "ERR_WEB_ANALYTICS_INVALID_QUERY",
                "ERR_WEB_ANALYTICS_ACCESS_REVOKED",
                "ERR_WEB_ANALYTICS_UPSTREAM",
                "ERR_AGENT_TRAFFIC_NOT_CONNECTED",
                "ERR_INVALID_PARAM",
                "ERR_INVALID_RANGE",
                "ERR_LIMIT_REACHED",
                "ERR_QUOTA_EXCEEDED",
                "ERR_STALE_CONTENT",
                "ERR_RATE_LIMITED"
              ]
            },
            "message": {
              "type": "string"
            },
            "meta": {
              "type": "object"
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "Project": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "description": "Internal project label (sidebar, settings, admin)"
        },
        "brand_name": {
          "type": "string",
          "description": "LLM-facing brand label (used in prompts and customer-facing charts). Null when not set, in which case prompts and charts use `name`.",
          "x-nullable": true
        }
      }
    },
    "ProjectDetails": {
      "allOf": [
        {
          "$ref": "#/definitions/Project"
        },
        {
          "type": "object",
          "properties": {
            "url": {
              "type": "string",
              "format": "uri",
              "x-nullable": true
            },
            "description": {
              "type": "string",
              "x-nullable": true
            },
            "matching_names": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "additional_owned_domains": {
              "$ref": "#/definitions/AdditionalOwnedDomains"
            },
            "industry": {
              "description": "Industry as stored: one key as a string (e.g. SAAS), or an array of key strings when the project was created with a list or the in-app multi-select. Deliberately untyped so generated clients decode either shape"
            },
            "business_model": {
              "type": "string",
              "x-nullable": true
            },
            "business_model_other": {
              "type": "string",
              "description": "Set only when business_model is OTHER",
              "x-nullable": true
            },
            "primary_products": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "target_audience": {
              "type": "string",
              "x-nullable": true
            },
            "brand_voice": {
              "type": "string",
              "x-nullable": true
            },
            "goals": {
              "type": "string",
              "x-nullable": true
            },
            "country_code": {
              "type": "string"
            },
            "language_code": {
              "type": "string"
            },
            "paused": {
              "type": "boolean"
            },
            "google_play_id": {
              "type": "string",
              "x-nullable": true
            },
            "app_store_id": {
              "type": "string",
              "x-nullable": true
            },
            "created_at": {
              "type": "string",
              "format": "date-time"
            },
            "prompt_limit": {
              "type": "integer",
              "description": "The most prompts this project can hold, set by the account owner or a co-owner (Account Settings > Prompt Limits). Null when the project has no limit of its own; the account's plan allowance applies either way",
              "x-nullable": true
            },
            "stats": {
              "type": "object",
              "properties": {
                "prompts_count": {
                  "type": "integer"
                },
                "prompts_by_brand_kind": {
                  "type": "object",
                  "description": "Prompt counts per brand focus",
                  "properties": {
                    "brand": {
                      "type": "integer"
                    },
                    "brand_other": {
                      "type": "integer"
                    },
                    "non_brand": {
                      "type": "integer"
                    }
                  }
                },
                "competitors_count": {
                  "type": "integer"
                },
                "collections_count": {
                  "type": "integer"
                },
                "prompts_available": {
                  "type": "integer",
                  "description": "How many prompts can still be added to this project now, the same number the Prompts page offers. The lower of the project's free slots under prompt_limit and the account's remaining prompts; 0 when either is full (a project over its limit keeps its prompts). Null when neither caps it",
                  "x-nullable": true
                }
              }
            },
            "data_coverage": {
              "type": "object",
              "description": "AI models, countries and languages the project has data for",
              "properties": {
                "models": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "chatgpt",
                      "perplexity",
                      "ai_mode",
                      "ai_overview",
                      "gemini",
                      "copilot",
                      "amazon_rufus",
                      "claude",
                      "grok",
                      "deepseek",
                      "naver_ai",
                      "baidu_ai",
                      "meta_ai"
                    ]
                  }
                },
                "countries": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "languages": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            },
            "search_console": {
              "type": "object",
              "description": "Google Search Console connection of the project. connected means the Search Console endpoints can answer; reconnect_required means Google refused the stored access and the property must be reconnected in Preferences > Project Settings > Data Connections.",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "connected",
                    "reconnect_required",
                    "not_connected"
                  ]
                },
                "property": {
                  "type": "string",
                  "description": "The connected property (e.g. sc-domain:example.com), null when not connected",
                  "x-nullable": true
                }
              }
            },
            "request_id": {
              "type": "string"
            }
          }
        }
      ]
    },
    "AdditionalOwnedDomains": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Other domains the brand owns (at most 10, each a website's own domain covering its subdomains), whose citations count as owned like the project website. Empty when none"
    },
    "ProjectProfile": {
      "type": "object",
      "description": "The project name and profile, as PATCH /projects/{id} and the update_project MCP tool return them",
      "required": [
        "id",
        "name",
        "matching_names",
        "additional_owned_domains",
        "prompt_limit"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        },
        "brand_name": {
          "type": "string",
          "x-nullable": true
        },
        "description": {
          "type": "string",
          "x-nullable": true
        },
        "industry": {
          "description": "Industry as stored: one key as a string (e.g. SAAS), or an array of key strings. Deliberately untyped so generated clients decode either shape"
        },
        "business_model": {
          "type": "string",
          "x-nullable": true
        },
        "business_model_other": {
          "type": "string",
          "description": "Set only when business_model is OTHER",
          "x-nullable": true
        },
        "target_audience": {
          "type": "string",
          "x-nullable": true
        },
        "primary_products": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "brand_voice": {
          "type": "string",
          "x-nullable": true
        },
        "goals": {
          "type": "string",
          "x-nullable": true
        },
        "matching_names": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "additional_owned_domains": {
          "$ref": "#/definitions/AdditionalOwnedDomains"
        },
        "prompt_limit": {
          "type": "integer",
          "description": "The most prompts this project can hold, set by the account owner or a co-owner (Account Settings > Prompt Limits). Null when the project has no limit of its own; the account's plan allowance applies either way",
          "x-nullable": true
        }
      }
    },
    "ProjectUpdateResponse": {
      "type": "object",
      "required": [
        "project",
        "updated_fields",
        "rematching",
        "note",
        "request_id"
      ],
      "properties": {
        "project": {
          "$ref": "#/definitions/ProjectProfile"
        },
        "updated_fields": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The fields the request sent"
        },
        "rematching": {
          "type": "boolean",
          "description": "True when a matching_names or additional_owned_domains change started a background recalculation of past answers; further edits are rejected until it finishes"
        },
        "note": {
          "type": "string",
          "description": "What the change does to mention detection, citation ownership and historical numbers, and, when prompt_limit was sent, how many prompts the project holds and how many more can be added"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "Competitor": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        },
        "domain": {
          "type": "string",
          "description": "Bare (scheme-less) domain. Null only on the own-brand row (include_project_brand=true) when the project has no URL.",
          "x-nullable": true
        },
        "matching_names": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Alternative names matched as this competitor. Absent on the own-brand row"
        },
        "citation_match_mode": {
          "$ref": "#/definitions/CitationMatchMode"
        },
        "citation_match_path": {
          "type": "string",
          "description": "Set only when citation_match_mode is path_prefix",
          "x-nullable": true
        },
        "actor_type": {
          "type": "string",
          "enum": [
            "project",
            "competitor"
          ],
          "description": "Only present when include_project_brand=true"
        },
        "is_own": {
          "type": "boolean",
          "description": "Only present when include_project_brand=true"
        }
      }
    },
    "CompetitorDetails": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "project_id": {
          "type": "integer"
        },
        "brand_name": {
          "type": "string"
        },
        "domain": {
          "type": "string"
        },
        "matching_names": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "x-nullable": true
        },
        "google_play_id": {
          "type": "string",
          "x-nullable": true
        },
        "app_store_id": {
          "type": "string",
          "x-nullable": true
        },
        "citation_match_mode": {
          "$ref": "#/definitions/CitationMatchMode"
        },
        "citation_match_path": {
          "type": "string",
          "description": "Set only when citation_match_mode is path_prefix",
          "x-nullable": true
        },
        "google_play_name": {
          "type": "string",
          "description": "English app name on Google Play, when the competitor has an Android app",
          "x-nullable": true
        },
        "app_store_name": {
          "type": "string",
          "description": "English app name on the App Store, when the competitor has an iOS app",
          "x-nullable": true
        },
        "google_play_icon_url": {
          "type": "string",
          "x-nullable": true
        },
        "app_store_icon_url": {
          "type": "string",
          "x-nullable": true
        },
        "color": {
          "type": "string",
          "x-nullable": true
        },
        "processing": {
          "type": "boolean",
          "description": "True while the competitor's historical mentions are being recalculated"
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CitationMatchMode": {
      "type": "string",
      "description": "How citations are attributed: domain (the domain and its subdomains), host (that exact host) or path_prefix (URLs under citation_match_path)",
      "enum": [
        "domain",
        "host",
        "path_prefix"
      ]
    },
    "Actor": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "project",
            "competitor"
          ]
        },
        "id": {
          "type": "integer"
        },
        "competitor_id": {
          "type": "integer",
          "x-nullable": true
        },
        "name": {
          "type": "string"
        },
        "domain": {
          "type": "string",
          "description": "Bare (scheme-less) domain. Null for the project actor when the project has no URL.",
          "x-nullable": true
        }
      }
    },
    "TimeseriesPoint": {
      "type": "object",
      "properties": {
        "date": {
          "type": "string",
          "format": "date",
          "description": "Calendar day in Europe/Madrid (YYYY-MM-DD). With granularity week or month it is the first day of the bucket (the Monday, or the 1st of the month)."
        },
        "value": {
          "type": "number",
          "description": "Null when the metric has no value for the bucket, e.g. a rate, position or sentiment metric on a day without answers.",
          "x-nullable": true
        }
      }
    },
    "TimeseriesSeries": {
      "type": "object",
      "properties": {
        "actor": {
          "$ref": "#/definitions/Actor"
        },
        "metric": {
          "type": "string"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TimeseriesPoint"
          }
        }
      }
    },
    "TimeseriesResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "granularity": {
          "type": "string",
          "description": "day, week or month"
        },
        "filters": {
          "$ref": "#/definitions/MetricsFiltersEcho"
        },
        "series": {
          "type": "object",
          "additionalProperties": {
            "type": "array",
            "items": {
              "$ref": "#/definitions/TimeseriesSeries"
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SummaryResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/TimeseriesResponse"
        },
        {
          "type": "object",
          "properties": {
            "summary": {
              "type": "object",
              "additionalProperties": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "actor": {
                      "$ref": "#/definitions/Actor"
                    },
                    "metric": {
                      "type": "string"
                    },
                    "total": {
                      "type": "number"
                    },
                    "aggregation": {
                      "type": "string",
                      "enum": [
                        "sum",
                        "average"
                      ],
                      "description": "How total combines the buckets"
                    },
                    "min": {
                      "type": "number"
                    },
                    "max": {
                      "type": "number"
                    },
                    "last": {
                      "type": "number"
                    }
                  }
                }
              }
            },
            "position_distribution": {
              "type": "object",
              "properties": {
                "position_1_count": {
                  "type": "integer"
                },
                "position_2_count": {
                  "type": "integer"
                },
                "position_3_plus_count": {
                  "type": "integer"
                },
                "total_mentions": {
                  "type": "integer"
                },
                "percentages": {
                  "type": "object"
                }
              }
            }
          }
        }
      ]
    },
    "PromptSummaryRow": {
      "type": "object",
      "properties": {
        "prompt_id": {
          "type": "integer"
        },
        "prompt_text": {
          "type": "string"
        },
        "model": {
          "type": "string",
          "description": "Only present when breakdown=model"
        },
        "responses": {
          "type": "integer"
        },
        "mentions": {
          "type": "integer"
        },
        "citations": {
          "type": "integer"
        },
        "visibility": {
          "type": "number"
        },
        "mention_rate": {
          "type": "number"
        },
        "citation_rate": {
          "type": "number"
        },
        "avg_mention_position": {
          "type": "number",
          "x-nullable": true
        },
        "avg_position": {
          "type": "number",
          "x-nullable": true
        },
        "app_url": {
          "type": "string",
          "format": "uri",
          "description": "Opens this prompt in the app. The link names its project, so it opens there for any user with access to that project"
        },
        "country_code": {
          "type": "string",
          "description": "Country of the prompt, as in /dimensions/prompts",
          "x-nullable": true
        },
        "language_code": {
          "type": "string",
          "description": "Language of the prompt, as in /dimensions/prompts",
          "x-nullable": true
        },
        "prompt_type": {
          "type": "string",
          "description": "Search intent: informational, navigational, commercial or transactional. Null until the prompt is classified",
          "x-nullable": true
        },
        "brand_kind": {
          "type": "string",
          "description": "Brand focus: brand, brand_other or non_brand. Null until the prompt is classified",
          "x-nullable": true
        },
        "tags": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TagRef"
          },
          "description": "Every tag the prompt belongs to"
        },
        "sentiment_responses": {
          "type": "integer",
          "description": "Responses in the window with a sentiment verdict for the brand, the base of the sentiment percentages (deduplicated like the sentiment timeseries)"
        },
        "sentiment_very_positive": {
          "type": "number",
          "description": "Percentage (0-100) of sentiment_responses whose brand verdict is very positive; null when sentiment_responses is 0",
          "x-nullable": true
        },
        "sentiment_positive": {
          "type": "number",
          "description": "Percentage (0-100) of sentiment_responses whose brand verdict is positive; null when sentiment_responses is 0",
          "x-nullable": true
        },
        "sentiment_neutral": {
          "type": "number",
          "description": "Percentage (0-100) of sentiment_responses whose brand verdict is neutral; null when sentiment_responses is 0",
          "x-nullable": true
        },
        "sentiment_negative": {
          "type": "number",
          "description": "Percentage (0-100) of sentiment_responses whose brand verdict is negative; null when sentiment_responses is 0",
          "x-nullable": true
        },
        "sentiment_very_negative": {
          "type": "number",
          "description": "Percentage (0-100) of sentiment_responses whose brand verdict is very negative; null when sentiment_responses is 0",
          "x-nullable": true
        },
        "net_sentiment": {
          "type": "number",
          "description": "Positive minus negative percentages (-100 to 100), neutral counted in the base; null when sentiment_responses is 0",
          "x-nullable": true
        }
      }
    },
    "PromptSummaryResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "filters": {
          "$ref": "#/definitions/MetricsFiltersEcho"
        },
        "breakdown": {
          "type": "string",
          "x-nullable": true
        },
        "sort": {
          "type": "string"
        },
        "sort_dir": {
          "type": "string"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/PromptSummaryRow"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SovResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "granularity": {
          "type": "string",
          "description": "day, week or month"
        },
        "filters": {
          "$ref": "#/definitions/MetricsFiltersEcho"
        },
        "periods": {
          "type": "array",
          "description": "Per-bucket sample size and completeness: mentions is the total the shares were computed on (1-3 mentions produce the 100/50/33.33 low-sample patterns); partial marks buckets still collecting data or clipped by the requested window; confidence and margin_of_error read the sample size.",
          "items": {
            "type": "object",
            "properties": {
              "date": {
                "type": "string",
                "format": "date"
              },
              "mentions": {
                "type": "integer"
              },
              "partial": {
                "type": "boolean"
              },
              "confidence": {
                "type": "string",
                "description": "How far the shares of this period can be trusted, from its mentions: none (0), low (under 30), medium (under 100) or high (100 or more).",
                "example": "low"
              },
              "margin_of_error": {
                "type": "number",
                "description": "Worst-case 95% margin of a share in percentage points, 98 / sqrt(mentions); mentions within one answer are not independent, so the real margin is at least this wide. null with no mentions.",
                "example": 21.9,
                "x-nullable": true
              }
            }
          }
        },
        "sample": {
          "type": "object",
          "description": "The period the current shares were computed on (the last one with mentions), same shape as a periods item; null when the window has no mentions.",
          "properties": {
            "date": {
              "type": "string",
              "format": "date"
            },
            "mentions": {
              "type": "integer"
            },
            "partial": {
              "type": "boolean"
            },
            "confidence": {
              "type": "string"
            },
            "margin_of_error": {
              "type": "number",
              "x-nullable": true
            }
          },
          "x-nullable": true
        },
        "over_time": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "actor": {
                "$ref": "#/definitions/Actor"
              },
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/definitions/TimeseriesPoint"
                }
              }
            }
          }
        },
        "current": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "actor": {
                "$ref": "#/definitions/Actor"
              },
              "share": {
                "type": "number"
              },
              "previous_share": {
                "type": "number",
                "description": "The actor's share in the last complete bucket before the current one; null without complete history.",
                "x-nullable": true
              },
              "avg_share": {
                "type": "number",
                "description": "Mean share across complete buckets with data (partial buckets excluded); null without complete history.",
                "x-nullable": true
              }
            }
          }
        },
        "breakdown": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "rank": {
                "type": "integer"
              },
              "actor": {
                "$ref": "#/definitions/Actor"
              },
              "share": {
                "type": "number"
              },
              "others": {
                "type": "boolean"
              }
            }
          }
        },
        "others": {
          "type": "array",
          "description": "Actors ranked fifth and below, folded into the Others share of breakdown",
          "items": {
            "type": "object",
            "properties": {
              "rank": {
                "type": "integer"
              },
              "actor": {
                "$ref": "#/definitions/Actor"
              },
              "share": {
                "type": "number"
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "TopSourcesResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "filters": {
          "$ref": "#/definitions/MetricsFiltersEcho"
        },
        "sort": {
          "type": "string"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "domain": {
                "type": "string"
              },
              "total_responses": {
                "type": "integer"
              },
              "avg_visibility": {
                "type": "number"
              },
              "avg_mention_rate": {
                "type": "number"
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "MetricsFiltersEcho": {
      "type": "object",
      "description": "The filters the response was computed with, as the server resolved them. Each endpoint echoes only the keys it reads; a filter that was not given comes back null (or an empty list).",
      "properties": {
        "metrics": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Requested metrics after alias resolution (mention_rate is echoed as visibility)"
        },
        "granularity": {
          "type": "string",
          "description": "day, week or month"
        },
        "model": {
          "type": "string",
          "description": "The model filter, or null when absent or not enabled for the account",
          "x-nullable": true
        },
        "collection_id": {
          "type": "string",
          "description": "The collection_id parameter as sent (one id or a comma-separated list)",
          "x-nullable": true
        },
        "collection_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "x-nullable": true
        },
        "domains": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "x-nullable": true
        },
        "country_code": {
          "type": "string",
          "description": "Comma-separated country codes",
          "x-nullable": true
        },
        "language_code": {
          "type": "string",
          "description": "Comma-separated language codes",
          "x-nullable": true
        },
        "prompt": {
          "type": "integer",
          "description": "The prompt id filter",
          "x-nullable": true
        },
        "prompt_type": {
          "type": "string",
          "description": "Comma-separated prompt types",
          "x-nullable": true
        },
        "brand_kind": {
          "type": "string",
          "x-nullable": true
        },
        "competitors": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "Competitor ids from the competitors parameter; empty when it was not given"
        },
        "include_project": {
          "type": "boolean"
        },
        "query": {
          "type": "string",
          "description": "Only present when a query filter was given"
        }
      }
    },
    "WebAnalyticsSchemaResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "provider": {
          "type": "string",
          "enum": [
            "google_analytics",
            "adobe_analytics",
            "matomo",
            "posthog",
            "plausible",
            "piano"
          ],
          "description": "The connected web analytics provider."
        },
        "property": {
          "type": "string",
          "description": "The property, site, report suite (rsid:...), data view (dataview:...) or project every query runs on."
        },
        "query_language": {
          "type": "string",
          "description": "The native query format the provider accepts."
        },
        "docs_url": {
          "type": "string",
          "description": "The provider's reference for that format."
        },
        "allowed_fields": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Top-level query fields that are forwarded."
        },
        "rules": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "What the bridge enforces and the provider's main constraints."
        },
        "example": {
          "type": "object",
          "additionalProperties": true,
          "description": "A worked query to adapt."
        },
        "fields": {
          "type": "object",
          "additionalProperties": true,
          "description": "The provider's live field list where it offers one: GA4 dimensions and metrics with custom definitions, Adobe ids, Matomo report methods, PostHog event names, the Plausible catalog. Null when the provider did not return it.",
          "x-nullable": true
        },
        "fields_unavailable": {
          "type": "string",
          "description": "Present when the field list could not be read; the format and example still apply."
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "WebAnalyticsQueryResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "provider": {
          "type": "string",
          "enum": [
            "google_analytics",
            "adobe_analytics",
            "matomo",
            "posthog",
            "plausible",
            "piano"
          ]
        },
        "property": {
          "type": "string"
        },
        "columns": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "description": "dimension or metric, when the provider says."
              },
              "type": {
                "type": "string",
                "description": "The provider's column type, when it says (PostHog)."
              },
              "label": {
                "type": "string",
                "description": "The provider's display label, when it sends one (Piano)."
              }
            }
          }
        },
        "rows": {
          "type": "array",
          "items": {
            "type": "array",
            "items": {}
          },
          "description": "One array per row, values in column order: strings, numbers or null."
        },
        "row_count": {
          "type": "integer",
          "description": "Rows in this response (at most 5,000)."
        },
        "total_rows": {
          "type": "integer",
          "description": "Rows the provider has for the query, when it reports it."
        },
        "truncated": {
          "type": "boolean",
          "description": "True when the provider has more rows than returned; page with its own offset or page field."
        },
        "totals": {
          "type": "object",
          "additionalProperties": true,
          "description": "Metric totals by metric name, when the query asked for them."
        },
        "notes": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Provider caveats: sampling, thresholds, more rows available."
        },
        "meta": {
          "type": "object",
          "additionalProperties": true,
          "description": "Provider metadata such as GA4 time zone, currency and remaining property quota."
        },
        "fetched_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the provider answered."
        },
        "cached": {
          "type": "boolean",
          "description": "True when the answer came from the 10-minute cache instead of the provider."
        },
        "query": {
          "type": "object",
          "additionalProperties": true,
          "description": "The request as sent to the provider, with the connected property forced and limits applied."
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "AgentTrafficResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "from": {
          "type": "string",
          "format": "date"
        },
        "to": {
          "type": "string",
          "format": "date"
        },
        "group_by": {
          "type": "string",
          "enum": [
            "bot",
            "company"
          ]
        },
        "granularity": {
          "type": "string",
          "enum": [
            "day",
            "week",
            "month"
          ]
        },
        "totals": {
          "type": "object",
          "additionalProperties": {
            "type": "integer"
          }
        },
        "timeseries": {
          "type": "object",
          "additionalProperties": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "AgentBot": {
      "type": "object",
      "properties": {
        "slug": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "company": {
          "type": "string"
        },
        "category": {
          "type": "string"
        },
        "cf_verified_category": {
          "type": "string"
        },
        "description": {
          "type": "string"
        }
      }
    },
    "AgentBotsResponse": {
      "type": "object",
      "properties": {
        "bots": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/AgentBot"
          }
        },
        "companies": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "AnswerDetails": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "prompt_id": {
          "type": "integer"
        },
        "prompt_text": {
          "type": "string"
        },
        "model": {
          "type": "string"
        },
        "response": {
          "type": "string",
          "x-nullable": true
        },
        "response_truncated": {
          "type": "boolean"
        },
        "executed_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "duration_ms": {
          "type": "number",
          "description": "Milliseconds, rounded to one decimal place",
          "x-nullable": true
        },
        "success": {
          "type": "boolean",
          "description": "Null while the answer is still pending",
          "x-nullable": true
        },
        "no_result": {
          "type": "boolean",
          "description": "True for a sentinel non-answer (the provider returned nothing after all retries, or the model declined to answer, today only Gemini); excluded from platform metrics"
        },
        "fan_out_queries": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "x-nullable": true
        },
        "mentions": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "citations": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "competitor_mentions": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "competitor_citations": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "sentiments": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "sources": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "shopping_products": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "brand_entities": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "local_businesses": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "locale": {
          "type": "object",
          "properties": {
            "country_code": {
              "type": "string",
              "x-nullable": true
            },
            "language_code": {
              "type": "string",
              "x-nullable": true
            }
          }
        },
        "app_url": {
          "type": "string",
          "format": "uri",
          "description": "Opens this answer in the app. The link names its project, so it opens there for any user with access to that project"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "ProjectCreateRequest": {
      "type": "object",
      "required": [
        "website_url",
        "name",
        "main_country",
        "main_language"
      ],
      "properties": {
        "website_url": {
          "type": "string",
          "format": "uri",
          "description": "Public HTTP(S) URL with a DNS hostname or public IP address. Credentials, private and special IP addresses, localhost and internal hostnames are rejected.",
          "example": "https://acme.com"
        },
        "name": {
          "type": "string",
          "description": "Project name, as plain text. It can be changed later with PATCH /projects/{id}",
          "example": "Acme"
        },
        "main_country": {
          "type": "string",
          "example": "US"
        },
        "main_language": {
          "type": "string",
          "example": "en"
        },
        "brand_name": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "industry": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Industry keys, case-insensitive; a single key string is also accepted. An unknown key returns ERR_INVALID_PARAM listing the valid keys (the same list as the in-app industry picker, e.g. TECHNOLOGY, SAAS, ECOMMERCE)",
          "example": [
            "SAAS"
          ]
        },
        "business_model": {
          "type": "string",
          "description": "Business model key (e.g. B2B_SAAS, MARKETPLACE); unknown keys are rejected",
          "example": "B2B_SAAS"
        },
        "business_model_other": {
          "type": "string",
          "description": "Free-text business model, only accepted when business_model is OTHER; rejected against any other key"
        },
        "target_audience": {
          "type": "string",
          "description": "Who the brand sells to. Context for Recommendations and GEO Writer (Brand Book)"
        },
        "brand_voice": {
          "type": "string",
          "description": "Tone of voice guidance for generated content (Brand Book)"
        },
        "goals": {
          "type": "string",
          "description": "What the brand wants to achieve. Context for GEO Writer and prompt suggestions"
        },
        "primary_products": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Main products or services"
        },
        "matching_names": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "prompts": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "maxItems": 100
        },
        "collections": {
          "type": "array",
          "maxItems": 50,
          "description": "Collections (prompt tags) created with the project, each tagging prompts of this request by their exact text, so no separate tagging calls are needed. A text that is not in prompts returns ERR_INVALID_PARAM. A team member also needs Tags: Create permission.",
          "items": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string",
                "maxLength": 100,
                "example": "Comparisons"
              },
              "prompts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "top crm tools 2026"
                ]
              }
            }
          }
        },
        "competitors": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "domain"
            ],
            "properties": {
              "domain": {
                "type": "string"
              },
              "brand_name": {
                "type": "string"
              },
              "matching_names": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        },
        "owned_media": {
          "type": "object",
          "description": "Requires Growth plan or above",
          "properties": {
            "youtube_channel_url": {
              "type": "string"
            },
            "instagram_profile_url": {
              "type": "string"
            },
            "facebook_page_url": {
              "type": "string"
            },
            "tiktok_profile_url": {
              "type": "string"
            },
            "app_store_url": {
              "type": "string"
            },
            "google_play_url": {
              "type": "string"
            }
          }
        },
        "use_subdomain": {
          "type": "boolean",
          "default": false
        },
        "weekly_email_subscribed": {
          "type": "boolean",
          "default": false
        },
        "external_identifier": {
          "type": "string",
          "pattern": "^[a-z0-9_-]{1,64}$",
          "description": "Embed-enabled (Enterprise) accounts only; other accounts receive ERR_PLAN_REQUIRED. Idempotency key and embed-session join key, unique per account"
        },
        "execute_prompts_immediately": {
          "type": "boolean",
          "default": true
        }
      }
    },
    "ProjectCreateResponse": {
      "type": "object",
      "properties": {
        "draft_id": {
          "type": "string",
          "description": "The finalized draft; only present on POST /project_drafts/{id}/finalize"
        },
        "project": {
          "type": "object",
          "description": "Same shape as GET /dimensions/projects/{id}"
        },
        "prompts": {
          "type": "object",
          "properties": {
            "created": {
              "type": "integer"
            },
            "skipped": {
              "type": "integer"
            },
            "execution": {
              "type": "string",
              "enum": [
                "immediate",
                "deferred_to_next_scheduled_run"
              ]
            }
          }
        },
        "competitors": {
          "type": "object",
          "properties": {
            "created": {
              "type": "integer"
            },
            "processing": {
              "type": "boolean",
              "description": "Always false; competitors are ready when the project transaction commits.",
              "example": false
            }
          }
        },
        "collections": {
          "type": "array",
          "description": "Collections created from the request's collections field (empty when none were sent; absent on an idempotent replay)",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "prompts_attached": {
                "type": "integer"
              }
            }
          }
        },
        "same_domain_projects": {
          "type": "array",
          "description": "Projects the caller can already see on the same domain (absent on an idempotent replay). Informational only: the create is never blocked, since one domain tracked per market is a normal setup.",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "country_code": {
                "type": "string"
              },
              "language_code": {
                "type": "string"
              }
            }
          }
        },
        "email_subscription": {
          "type": "object",
          "properties": {
            "weekly_email_subscribed": {
              "type": "boolean"
            }
          }
        },
        "limits": {
          "type": "object",
          "properties": {
            "projects_remaining": {
              "type": "integer",
              "x-nullable": true
            },
            "prompts_available": {
              "type": "integer",
              "x-nullable": true
            },
            "competitors_remaining": {
              "type": "integer",
              "x-nullable": true
            }
          }
        },
        "idempotent": {
          "type": "boolean",
          "description": "Present and true only on external_identifier replays"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PromptsCreateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "prompts",
        "country_code",
        "language_code"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "prompts": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "maxItems": 100
        },
        "country_code": {
          "type": "string"
        },
        "language_code": {
          "type": "string"
        }
      }
    },
    "PromptsCreateResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "created": {
          "type": "integer"
        },
        "skipped": {
          "type": "integer"
        },
        "total_after": {
          "type": "integer"
        },
        "prompts_available": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "raw_input": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "exists"
                ]
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "StoreConnectionResponse": {
      "type": "object",
      "required": [
        "platform",
        "domain",
        "project",
        "ambiguous",
        "candidates",
        "account",
        "request_id"
      ],
      "properties": {
        "platform": {
          "type": "string",
          "description": "The store platform, e.g. shopify"
        },
        "domain": {
          "type": "string",
          "description": "The store domain as compared: lowercase, without scheme, www or path"
        },
        "project": {
          "type": "object",
          "description": "The live project the store maps to: its domain equals the store's, or is a parent or subdomain of it. Null when no project matches",
          "required": [
            "id",
            "name",
            "domain"
          ],
          "properties": {
            "id": {
              "type": "integer"
            },
            "name": {
              "type": "string"
            },
            "domain": {
              "type": "string"
            },
            "country_code": {
              "type": "string",
              "x-nullable": true
            },
            "language_code": {
              "type": "string",
              "x-nullable": true
            }
          },
          "x-nullable": true
        },
        "ambiguous": {
          "type": "boolean",
          "description": "True when several live projects match the store domain (for example one project per market). project is then null and the app asks the key holder to pick from candidates."
        },
        "candidates": {
          "type": "array",
          "description": "Every live project of the account, for a project picker",
          "items": {
            "type": "object",
            "required": [
              "id",
              "name",
              "domain"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "domain": {
                "type": "string",
                "description": "Empty when the project has no website URL"
              }
            }
          }
        },
        "account": {
          "type": "object",
          "properties": {
            "plan": {
              "type": "string",
              "description": "Internal plan key of the account, e.g. scale or scaleplus; null for a key limited to some projects",
              "x-nullable": true
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "AiOrdersResponse": {
      "type": "object",
      "required": [
        "project_id",
        "platform",
        "currency",
        "from",
        "to",
        "totals",
        "by_source",
        "series",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "platform": {
          "type": "string"
        },
        "currency": {
          "type": "string",
          "description": "ISO 4217 code of the most recent stored day; null when the window holds no stored order",
          "x-nullable": true
        },
        "from": {
          "type": "string",
          "format": "date"
        },
        "to": {
          "type": "string",
          "format": "date"
        },
        "totals": {
          "type": "object",
          "required": [
            "orders",
            "revenue"
          ],
          "properties": {
            "orders": {
              "type": "integer"
            },
            "revenue": {
              "type": "string",
              "description": "Decimal amount with two decimals, e.g. 1834.20"
            }
          }
        },
        "by_source": {
          "type": "array",
          "description": "One row per AI assistant, highest revenue first",
          "items": {
            "type": "object",
            "required": [
              "source",
              "name",
              "orders",
              "revenue"
            ],
            "properties": {
              "source": {
                "type": "string",
                "description": "AI assistant slug, e.g. chatgpt or perplexity"
              },
              "name": {
                "type": "string",
                "description": "Display name, e.g. ChatGPT"
              },
              "orders": {
                "type": "integer"
              },
              "revenue": {
                "type": "string"
              }
            }
          }
        },
        "series": {
          "type": "array",
          "description": "Days that have stored orders, oldest first",
          "items": {
            "type": "object",
            "required": [
              "day",
              "orders",
              "revenue"
            ],
            "properties": {
              "day": {
                "type": "string",
                "format": "date"
              },
              "orders": {
                "type": "integer"
              },
              "revenue": {
                "type": "string"
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "AiOrdersUpdateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "platform",
        "currency",
        "from",
        "to",
        "days"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "platform": {
          "type": "string",
          "enum": [
            "shopify"
          ]
        },
        "currency": {
          "type": "string",
          "description": "ISO 4217 code, e.g. EUR"
        },
        "from": {
          "type": "string",
          "format": "date",
          "description": "First day of the window this push replaces"
        },
        "to": {
          "type": "string",
          "format": "date",
          "description": "Last day of the window; at most 400 days after from"
        },
        "days": {
          "type": "array",
          "maxItems": 400,
          "items": {
            "type": "object",
            "required": [
              "day",
              "referrer",
              "orders",
              "revenue"
            ],
            "properties": {
              "day": {
                "type": "string",
                "format": "date",
                "description": "Must fall inside from..to"
              },
              "referrer": {
                "type": "string",
                "description": "Raw referring host or utm_source of the order's first visit, e.g. chatgpt.com. Entries that are not an AI assistant are ignored"
              },
              "orders": {
                "type": "integer",
                "minimum": 0
              },
              "revenue": {
                "type": "string",
                "description": "Non-negative decimal amount in currency, e.g. 120.50. A JSON number is accepted too"
              }
            }
          }
        }
      }
    },
    "AiOrdersUpdateResponse": {
      "type": "object",
      "required": [
        "project_id",
        "platform",
        "stored",
        "ignored",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "platform": {
          "type": "string"
        },
        "stored": {
          "type": "integer",
          "description": "Rows stored, one per day and AI assistant"
        },
        "ignored": {
          "type": "integer",
          "description": "Entries whose referrer is not an AI assistant"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CatalogPromptSuggestion": {
      "type": "object",
      "required": [
        "id",
        "prompt",
        "status",
        "source",
        "country_code",
        "language_code",
        "product",
        "prompt_id",
        "accepted_at",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "prompt": {
          "type": "string"
        },
        "status": {
          "type": "string",
          "description": "pending, accepted or rejected"
        },
        "source": {
          "type": "string",
          "description": "Always catalog"
        },
        "country_code": {
          "type": "string",
          "x-nullable": true
        },
        "language_code": {
          "type": "string",
          "x-nullable": true
        },
        "product": {
          "type": "object",
          "description": "The catalog product the suggestion was written for",
          "properties": {
            "external_id": {
              "type": "string",
              "description": "The store's product id, e.g. gid://shopify/Product/1",
              "x-nullable": true
            },
            "handle": {
              "type": "string",
              "x-nullable": true
            },
            "title": {
              "type": "string",
              "x-nullable": true
            }
          }
        },
        "prompt_id": {
          "type": "integer",
          "description": "The tracked prompt an accepted suggestion became; null until accepted",
          "x-nullable": true
        },
        "accepted_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the suggestion was accepted; null until then",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "CatalogPromptSuggestionsResponse": {
      "type": "object",
      "required": [
        "project_id",
        "page",
        "per_page",
        "total",
        "data",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CatalogPromptSuggestion"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CatalogProduct": {
      "type": "object",
      "required": [
        "external_id",
        "title"
      ],
      "properties": {
        "external_id": {
          "type": "string",
          "description": "The store's product id, e.g. gid://shopify/Product/1"
        },
        "title": {
          "type": "string"
        },
        "handle": {
          "type": "string"
        },
        "product_type": {
          "type": "string"
        },
        "vendor": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "type": "string"
          }
        },
        "collections": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "type": "string"
          }
        },
        "url": {
          "type": "string"
        }
      }
    },
    "CatalogPromptSuggestionsCreateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "platform",
        "products"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "platform": {
          "type": "string",
          "enum": [
            "shopify"
          ]
        },
        "country_code": {
          "type": "string",
          "description": "Defaults to the project country"
        },
        "language_code": {
          "type": "string",
          "description": "Defaults to the project language"
        },
        "products": {
          "type": "array",
          "minItems": 1,
          "maxItems": 20,
          "items": {
            "$ref": "#/definitions/CatalogProduct"
          }
        }
      }
    },
    "CatalogPromptSuggestionsCreateResponse": {
      "type": "object",
      "required": [
        "project_id",
        "created",
        "skipped",
        "data",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "created": {
          "type": "integer"
        },
        "skipped": {
          "type": "integer",
          "description": "Generated prompts not saved because the project already holds them as a suggestion (from any source or product, in any status)."
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CatalogPromptSuggestion"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CatalogPromptSuggestionIdsRequest": {
      "type": "object",
      "required": [
        "project_id",
        "ids"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "ids": {
          "type": "array",
          "minItems": 1,
          "maxItems": 100,
          "items": {
            "type": "integer"
          }
        }
      }
    },
    "CatalogPromptSuggestionsAcceptResponse": {
      "type": "object",
      "required": [
        "project_id",
        "accepted",
        "skipped",
        "prompts_available",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "accepted": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "suggestion_id",
              "prompt_id",
              "collection_id"
            ],
            "properties": {
              "suggestion_id": {
                "type": "integer"
              },
              "prompt_id": {
                "type": "integer"
              },
              "collection_id": {
                "type": "integer",
                "description": "The collection named after the product; null when the product has no title or the team member cannot create tags",
                "x-nullable": true
              }
            }
          }
        },
        "skipped": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "suggestion_id",
              "reason"
            ],
            "properties": {
              "suggestion_id": {
                "type": "integer"
              },
              "reason": {
                "type": "string",
                "description": "accepted or rejected (the suggestion was no longer pending), or pending_deletion"
              }
            }
          }
        },
        "prompts_available": {
          "type": "integer",
          "description": "Prompt slots left on the plan; null when unlimited",
          "x-nullable": true
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CatalogPromptSuggestionsRejectResponse": {
      "type": "object",
      "required": [
        "project_id",
        "rejected",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "rejected": {
          "type": "integer"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "IntelligenceTaskProduct": {
      "type": "object",
      "required": [
        "title"
      ],
      "description": "The store product a product_listing task rewrites",
      "properties": {
        "external_id": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "description_html": {
          "type": "string",
          "maxLength": 20000
        },
        "seo_title": {
          "type": "string"
        },
        "seo_description": {
          "type": "string"
        },
        "url": {
          "type": "string"
        },
        "product_type": {
          "type": "string"
        },
        "images": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "alt": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "IntelligenceTaskCreateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "task_type"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "task_type": {
          "type": "string",
          "enum": [
            "brief",
            "create",
            "update",
            "pr_insights",
            "custom",
            "product_listing"
          ],
          "description": "product_listing is API-only: it needs product and returns ready-to-apply product page copy"
        },
        "prompt_id": {
          "type": "integer",
          "description": "Not used by product_listing; send null or omit it",
          "x-nullable": true
        },
        "custom_topic": {
          "type": "string"
        },
        "user_instructions": {
          "type": "string"
        },
        "output_language_code": {
          "type": "string"
        },
        "existing_content": {
          "type": "string"
        },
        "existing_content_url": {
          "type": "string",
          "format": "uri"
        },
        "product": {
          "$ref": "#/definitions/IntelligenceTaskProduct"
        },
        "prompt_ids": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "type": "integer"
          },
          "description": "product_listing only: up to 20 project prompts the copy should answer"
        }
      }
    },
    "IntelligenceTaskUpdateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "edits"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "edits": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          },
          "description": "Dotted result_data paths (title, sections.0.content, key_points.2) mapped to their replacement text. Only string fields that already exist are editable; sections cannot be added or removed.",
          "example": {
            "title": "How to measure AI visibility in 2026",
            "sections.0.content": "AI visibility is the share of AI answers that mention your brand."
          }
        },
        "result_version": {
          "type": "string",
          "description": "Optional. The result_version of the task as last read. When sent, the edit is refused with ERR_STALE_CONTENT (409) and nothing is written if the task was regenerated since. Omitted, null or empty, the edits apply to whatever the task holds now",
          "example": "1775642580",
          "x-nullable": true
        }
      }
    },
    "IntelligenceTask": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "public_id": {
          "type": "string"
        },
        "project_id": {
          "type": "integer"
        },
        "task_type": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "status": {
          "type": "string"
        },
        "prompt_id": {
          "type": "integer",
          "x-nullable": true
        },
        "prompt_text": {
          "type": "string",
          "x-nullable": true
        },
        "agentic_mode": {
          "type": "boolean"
        },
        "custom_topic": {
          "type": "string",
          "x-nullable": true
        },
        "user_instructions": {
          "type": "string",
          "x-nullable": true
        },
        "output_language_code": {
          "type": "string",
          "x-nullable": true
        },
        "word_count": {
          "type": "integer",
          "x-nullable": true
        },
        "result_data": {
          "type": "object",
          "description": "The generated content once status is completed; null before that. A product_listing task returns title, summary, description_html (p, ul, ol, li, strong, em, h3 and br only), faq (question and answer pairs), seo_title, seo_description, image_alts (image_id and alt), changes (field and reason) and labels",
          "x-nullable": true
        },
        "error_message": {
          "type": "string",
          "x-nullable": true
        },
        "estimated_time": {
          "type": "string",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "processed_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "manually_edited_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the content was last edited by hand; null while the output is as generated",
          "x-nullable": true
        },
        "edited_by_user_id": {
          "type": "integer",
          "description": "User behind the last manual edit; null for an unedited task or an edit made from an embedded portal",
          "x-nullable": true
        },
        "result_version": {
          "type": "string",
          "description": "Concurrency token for content edits: send it back as result_version on PATCH /intelligence_tasks/{id} so an edit composed before a regeneration is refused with ERR_STALE_CONTENT (409). It is \"0\" until the task first completes, then changes each time a generation or regeneration replaces result_data, never on a manual edit or a revert. Treat it as opaque text",
          "example": "1775642580"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "IntelligenceTaskUpdateResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/IntelligenceTask"
        },
        {
          "type": "object",
          "properties": {
            "changed_paths": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Paths whose text actually changed; empty when every value matched the stored text"
            }
          }
        }
      ]
    },
    "TechnicalGeoReportContentUpdateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "report_type",
        "content_version",
        "edits"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "report_type": {
          "type": "string",
          "enum": [
            "llms_txt"
          ],
          "description": "Only llms_txt reports have editable content"
        },
        "content_version": {
          "type": "string",
          "description": "result_data.content_version of the report as last read. It changes on every save; a value that no longer matches is refused as stale",
          "example": "1790683200123456"
        },
        "edits": {
          "type": "object",
          "additionalProperties": false,
          "description": "The files to replace, each mapped to its full replacement text: never blank, at most 200,000 characters. Send one file or both; a file identical to the stored one is ignored.",
          "properties": {
            "llms_txt": {
              "type": "string",
              "description": "Full text of llms.txt"
            },
            "llms_full_txt": {
              "type": "string",
              "description": "Full text of llms-full.txt"
            }
          },
          "example": {
            "llms_txt": "# example.com\n\n> What example.com does, in one paragraph.\n"
          }
        }
      }
    },
    "TechnicalGeoReportContentRevertRequest": {
      "type": "object",
      "required": [
        "project_id",
        "report_type"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "report_type": {
          "type": "string",
          "enum": [
            "llms_txt"
          ],
          "description": "Only llms_txt reports have editable content"
        }
      }
    },
    "LlmsTxtTechnicalGeoReport": {
      "type": "object",
      "description": "An llms_txt technical GEO report with its files, in the shape GET /technical_geo_reports/{id} returns",
      "properties": {
        "id": {
          "type": "integer"
        },
        "report_type": {
          "type": "string",
          "description": "Always llms_txt"
        },
        "project_id": {
          "type": "integer"
        },
        "batch_id": {
          "type": "integer",
          "description": "Bundle the report was created in; null for a report created on its own",
          "x-nullable": true
        },
        "url": {
          "type": "string",
          "description": "Always null for llms_txt reports; domain names the website",
          "x-nullable": true
        },
        "domain": {
          "type": "string"
        },
        "country_code": {
          "type": "string",
          "x-nullable": true
        },
        "output_language_code": {
          "type": "string",
          "description": "ISO 639-1 code the files were requested in; null when they are written in the website's own language",
          "x-nullable": true
        },
        "status": {
          "type": "string"
        },
        "result_available": {
          "type": "boolean"
        },
        "overall_score": {
          "type": "number",
          "description": "Always null for llms_txt reports",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        },
        "result_data": {
          "type": "object",
          "description": "The files and generation details once the report has completed; null before that",
          "properties": {
            "llms_txt_content": {
              "type": "string",
              "description": "Current llms.txt, manual edits included"
            },
            "llms_full_txt_content": {
              "type": "string",
              "description": "Current llms-full.txt, manual edits included",
              "x-nullable": true
            },
            "manually_edited_at": {
              "type": "string",
              "format": "date-time",
              "description": "When the files were last edited by hand in the app, the API or MCP; null while they are as generated",
              "x-nullable": true
            },
            "content_version": {
              "type": "string",
              "description": "Send it back as content_version when editing the files. It changes on every save"
            },
            "original_llms_txt_content": {
              "type": "string",
              "description": "The generated llms.txt, kept from the first manual edit; null while the files are as generated",
              "x-nullable": true
            },
            "original_llms_full_txt_content": {
              "type": "string",
              "description": "The generated llms-full.txt, kept from the first manual edit; null while the files are as generated",
              "x-nullable": true
            },
            "crawl_data": {
              "type": "object",
              "x-nullable": true
            },
            "metadata": {
              "type": "object",
              "description": "Generation details, including output_language_code, the language the files were written in",
              "x-nullable": true
            },
            "pages_crawled": {
              "type": "integer",
              "x-nullable": true
            },
            "generation_time_ms": {
              "type": "integer",
              "x-nullable": true
            },
            "openai_tokens_used": {
              "type": "integer",
              "x-nullable": true
            }
          },
          "x-nullable": true
        },
        "error_message": {
          "type": "string",
          "x-nullable": true
        },
        "poll_after_seconds": {
          "type": "integer",
          "description": "Seconds to wait before polling again while the report runs; null once it has finished",
          "x-nullable": true
        },
        "app_url": {
          "type": "string",
          "format": "uri",
          "description": "Opens this report in the app"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "TechnicalGeoReportContentUpdateResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/LlmsTxtTechnicalGeoReport"
        },
        {
          "type": "object",
          "properties": {
            "changed_files": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "llms_txt",
                  "llms_full_txt"
                ]
              },
              "description": "Files whose text actually changed; empty when every file matched the stored text"
            }
          }
        }
      ]
    },
    "LocalBusiness": {
      "type": "object",
      "properties": {
        "business_key": {
          "type": "string",
          "description": "Stable grouping key: the lowercased name and address"
        },
        "title": {
          "type": "string"
        },
        "address": {
          "type": "string",
          "x-nullable": true
        },
        "domain": {
          "type": "string",
          "x-nullable": true
        },
        "url": {
          "type": "string",
          "x-nullable": true
        },
        "phone": {
          "type": "string",
          "x-nullable": true
        },
        "avg_rating": {
          "type": "number",
          "x-nullable": true
        },
        "reviews": {
          "type": "integer",
          "x-nullable": true
        },
        "avg_position": {
          "type": "number",
          "description": "Average rank of the business in the answer's list (1 = first)",
          "x-nullable": true
        },
        "prompts": {
          "type": "integer"
        },
        "appearances": {
          "type": "integer"
        },
        "is_client": {
          "type": "boolean"
        },
        "competitor_id": {
          "type": "integer",
          "x-nullable": true
        },
        "competitor_name": {
          "type": "string",
          "x-nullable": true
        }
      }
    },
    "LocalBusinessesTotals": {
      "type": "object",
      "properties": {
        "businesses": {
          "type": "integer"
        },
        "your_businesses": {
          "type": "integer"
        },
        "appearances": {
          "type": "integer"
        },
        "avg_rating": {
          "type": "number",
          "x-nullable": true
        },
        "executions_with_local_businesses": {
          "type": "integer"
        }
      }
    },
    "LocalBusinessesResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "totals": {
          "$ref": "#/definitions/LocalBusinessesTotals"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/LocalBusiness"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "OwnedMediaRow": {
      "type": "object",
      "description": "One row of /dimensions/owned_media. Fields depend on provider and view: videos (title, url, channel_name, channel_handle, channel_url, video_id, thumbnail_url, views, published_at), channels (key, name, handle, url, avatar_url, unique_videos), posts (url, caption, owner_handle, thumbnail_url, content_type, likes, comments, views, posted_at), profiles (handle, name, url, avatar_url, followers, unique_posts), own_citations (url, citation_date, position, model) and apps (domain, total_responses, rank, global_rank). Every view but own_citations carries citations and yours; videos, channels, posts and profiles carry avg_position.",
      "properties": {
        "title": {
          "type": "string",
          "x-nullable": true
        },
        "url": {
          "type": "string",
          "x-nullable": true
        },
        "channel_name": {
          "type": "string",
          "x-nullable": true
        },
        "channel_handle": {
          "type": "string",
          "description": "The video's channel handle, without the @",
          "x-nullable": true
        },
        "channel_url": {
          "type": "string",
          "x-nullable": true
        },
        "video_id": {
          "type": "string",
          "x-nullable": true
        },
        "key": {
          "type": "string",
          "description": "The channel key (handle:..., channel:... or name:...), accepted as profile by /dimensions/owned_media/citing_prompts"
        },
        "name": {
          "type": "string",
          "x-nullable": true
        },
        "handle": {
          "type": "string",
          "x-nullable": true
        },
        "caption": {
          "type": "string",
          "x-nullable": true
        },
        "owner_handle": {
          "type": "string",
          "x-nullable": true
        },
        "thumbnail_url": {
          "type": "string",
          "description": "The post or video image. Our cached copy where one exists, so the link does not expire",
          "x-nullable": true
        },
        "avatar_url": {
          "type": "string",
          "description": "The profile or channel image. Our cached copy where one exists",
          "x-nullable": true
        },
        "content_type": {
          "type": "string",
          "description": "The post format on its platform: reel, post or tv on Instagram, post or pulse on LinkedIn, post elsewhere",
          "x-nullable": true
        },
        "citations": {
          "type": "integer"
        },
        "unique_videos": {
          "type": "integer"
        },
        "unique_posts": {
          "type": "integer"
        },
        "followers": {
          "type": "integer",
          "x-nullable": true
        },
        "likes": {
          "type": "integer",
          "x-nullable": true
        },
        "comments": {
          "type": "integer",
          "x-nullable": true
        },
        "views": {
          "description": "View count as the platform reports it, a number or a numeric string, or null when unknown"
        },
        "published_at": {
          "type": "string",
          "description": "Publication date as the platform reports it",
          "x-nullable": true
        },
        "posted_at": {
          "type": "string",
          "description": "Publication date as the platform reports it (ISO 8601, or Unix seconds on TikTok)",
          "x-nullable": true
        },
        "avg_position": {
          "type": "number",
          "description": "Average position of the citation in the answers (1 = first); null when unknown",
          "x-nullable": true
        },
        "yours": {
          "type": "boolean"
        },
        "citation_date": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "position": {
          "type": "integer",
          "x-nullable": true
        },
        "model": {
          "type": "string",
          "x-nullable": true
        },
        "domain": {
          "type": "string",
          "description": "The app's store listing URL (provider=mobile_apps)"
        },
        "total_responses": {
          "type": "integer"
        },
        "rank": {
          "type": "integer"
        },
        "global_rank": {
          "type": "integer",
          "x-nullable": true
        }
      }
    },
    "OwnedMediaListResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "provider": {
          "type": "string"
        },
        "view": {
          "type": "string"
        },
        "store": {
          "type": "string",
          "description": "provider=mobile_apps only"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OwnedMediaRow"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "OwnedMediaKpis": {
      "type": "object",
      "properties": {
        "total": {
          "type": "integer",
          "description": "Citations of the platform in the window, yours and everyone else's"
        },
        "yours": {
          "type": "integer",
          "description": "Citations of your own profiles and their content"
        },
        "your_profile": {
          "type": "integer",
          "description": "Citations of your profile or channel pages. Null for mobile_apps and overview",
          "x-nullable": true
        },
        "your_content": {
          "type": "integer",
          "description": "Citations of your posts or videos. Null for mobile_apps and overview",
          "x-nullable": true
        },
        "share_pct": {
          "type": "number",
          "description": "yours as a percentage of total (0-100)"
        }
      }
    },
    "OwnedMediaKpiChanges": {
      "type": "object",
      "description": "Change against the previous period. Counts in percent, share_pct in percentage points; null when the previous period has nothing to compare with",
      "properties": {
        "total": {
          "type": "number",
          "x-nullable": true
        },
        "yours": {
          "type": "number",
          "x-nullable": true
        },
        "your_profile": {
          "type": "number",
          "x-nullable": true
        },
        "your_content": {
          "type": "number",
          "x-nullable": true
        },
        "share_pct": {
          "type": "number",
          "x-nullable": true
        }
      }
    },
    "OwnedMediaWeek": {
      "type": "object",
      "properties": {
        "week": {
          "type": "string",
          "format": "date",
          "description": "The Monday that starts the week"
        },
        "citations": {
          "type": "integer"
        },
        "yours": {
          "type": "integer",
          "description": "Null for provider=overview, whose rollups have no weekly split",
          "x-nullable": true
        },
        "other": {
          "type": "integer",
          "x-nullable": true
        }
      }
    },
    "OwnedMediaModelShare": {
      "type": "object",
      "properties": {
        "model": {
          "type": "string"
        },
        "citations": {
          "type": "integer"
        },
        "share_pct": {
          "type": "number"
        }
      }
    },
    "OwnedMediaCountryShare": {
      "type": "object",
      "properties": {
        "country_code": {
          "type": "string"
        },
        "citations": {
          "type": "integer"
        },
        "share_pct": {
          "type": "number"
        }
      }
    },
    "OwnedMediaContentTypeShare": {
      "type": "object",
      "properties": {
        "content_type": {
          "type": "string",
          "description": "reel, post or tv on Instagram, post or pulse on LinkedIn, regular, shorts or live on YouTube"
        },
        "citations": {
          "type": "integer"
        },
        "share_pct": {
          "type": "number"
        }
      }
    },
    "OwnedMediaAgeBucketShare": {
      "type": "object",
      "properties": {
        "bucket": {
          "type": "string",
          "enum": [
            "under_1m",
            "m1_6",
            "m6_12",
            "over_1y"
          ],
          "description": "Age of the cited post or video when the summary is read"
        },
        "citations": {
          "type": "integer"
        },
        "share_pct": {
          "type": "number"
        }
      }
    },
    "OwnedMediaStoreTotals": {
      "type": "object",
      "properties": {
        "store": {
          "type": "string",
          "enum": [
            "google_play",
            "app_store"
          ]
        },
        "total": {
          "type": "integer"
        },
        "yours": {
          "type": "integer"
        },
        "share_pct": {
          "type": "number"
        }
      }
    },
    "OwnedMediaPlatformRollup": {
      "type": "object",
      "properties": {
        "provider": {
          "type": "string",
          "enum": [
            "youtube",
            "mobile_apps",
            "instagram",
            "facebook",
            "tiktok",
            "linkedin",
            "reddit"
          ]
        },
        "connected": {
          "type": "boolean"
        },
        "total": {
          "type": "integer"
        },
        "yours": {
          "type": "integer"
        },
        "share_pct": {
          "type": "number"
        },
        "weekly": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "week": {
                "type": "string",
                "format": "date"
              },
              "citations": {
                "type": "integer"
              }
            }
          }
        }
      }
    },
    "OwnedMediaSummaryResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "provider": {
          "type": "string"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "connected": {
          "type": "boolean",
          "description": "Whether a profile is claimed on the platform (on overview, on any platform)"
        },
        "kpis": {
          "$ref": "#/definitions/OwnedMediaKpis"
        },
        "previous": {
          "type": "object",
          "description": "The period of the same length that ends just before from",
          "properties": {
            "from": {
              "type": "string",
              "format": "date-time"
            },
            "to": {
              "type": "string",
              "format": "date-time"
            },
            "kpis": {
              "$ref": "#/definitions/OwnedMediaKpis"
            }
          }
        },
        "changes": {
          "$ref": "#/definitions/OwnedMediaKpiChanges"
        },
        "weekly": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OwnedMediaWeek"
          }
        },
        "by_model": {
          "type": "array",
          "description": "Null for provider=overview",
          "items": {
            "$ref": "#/definitions/OwnedMediaModelShare"
          },
          "x-nullable": true
        },
        "by_country": {
          "type": "array",
          "description": "Null for provider=overview",
          "items": {
            "$ref": "#/definitions/OwnedMediaCountryShare"
          },
          "x-nullable": true
        },
        "content_types": {
          "type": "array",
          "description": "Social and YouTube providers only",
          "items": {
            "$ref": "#/definitions/OwnedMediaContentTypeShare"
          },
          "x-nullable": true
        },
        "age_buckets": {
          "type": "array",
          "description": "Social and YouTube providers only. Posts or videos without a known publication date are left out",
          "items": {
            "$ref": "#/definitions/OwnedMediaAgeBucketShare"
          },
          "x-nullable": true
        },
        "stores": {
          "type": "array",
          "description": "provider=mobile_apps only",
          "items": {
            "$ref": "#/definitions/OwnedMediaStoreTotals"
          },
          "x-nullable": true
        },
        "platforms": {
          "type": "array",
          "description": "provider=overview only, one row per platform the caller can read",
          "items": {
            "$ref": "#/definitions/OwnedMediaPlatformRollup"
          },
          "x-nullable": true
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "OwnedMediaCitedItem": {
      "type": "object",
      "description": "The post, video, profile or channel. Null when nothing in the window matches it",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "post",
            "profile"
          ],
          "description": "post covers posts and videos, profile covers profiles and channels"
        },
        "title": {
          "type": "string",
          "x-nullable": true
        },
        "subtitle": {
          "type": "string",
          "x-nullable": true
        },
        "url": {
          "type": "string",
          "x-nullable": true
        },
        "image_url": {
          "type": "string",
          "x-nullable": true
        },
        "yours": {
          "type": "boolean"
        },
        "date": {
          "type": "string",
          "format": "date-time",
          "description": "Publication date of a post or video",
          "x-nullable": true
        },
        "stats": {
          "type": "object",
          "description": "The counts the platform reports for the item; absent keys are unknown",
          "properties": {
            "citations": {
              "type": "integer"
            },
            "likes": {
              "type": "integer"
            },
            "comments": {
              "type": "integer"
            },
            "views": {
              "type": "integer"
            },
            "followers": {
              "type": "integer"
            },
            "unique_posts": {
              "type": "integer"
            },
            "unique_videos": {
              "type": "integer"
            }
          }
        }
      },
      "x-nullable": true
    },
    "OwnedMediaCitingAnswer": {
      "type": "object",
      "properties": {
        "answer_id": {
          "type": "integer",
          "description": "The answer (prompt execution) id, readable with GET /answers/{id}"
        },
        "prompt_id": {
          "type": "integer"
        },
        "prompt_text": {
          "type": "string"
        },
        "model": {
          "type": "string"
        },
        "country_code": {
          "type": "string",
          "x-nullable": true
        },
        "language_code": {
          "type": "string",
          "x-nullable": true
        },
        "executed_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "excerpt": {
          "type": "string",
          "description": "The start of the answer as plain text (at most 300 characters)",
          "x-nullable": true
        },
        "app_url": {
          "type": "string",
          "description": "The answer in the app"
        }
      }
    },
    "OwnedMediaCitingPromptsResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "provider": {
          "type": "string"
        },
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "item": {
          "$ref": "#/definitions/OwnedMediaCitedItem"
        },
        "citation_count": {
          "type": "integer",
          "description": "Citations of the item in the window"
        },
        "prompt_count": {
          "type": "integer",
          "description": "Distinct prompts whose answers cited it"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer",
          "description": "Citing answers"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OwnedMediaCitingAnswer"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "OwnedMediaProfile": {
      "type": "object",
      "properties": {
        "url": {
          "type": "string"
        },
        "handle": {
          "type": "string",
          "description": "The handle, page id, store app id, subreddit or username",
          "x-nullable": true
        },
        "kind": {
          "type": "string",
          "enum": [
            "profile",
            "app",
            "subreddit",
            "username"
          ]
        },
        "main": {
          "type": "boolean",
          "description": "True for the main profile of its platform (or store), false for an extra one"
        },
        "store": {
          "type": "string",
          "description": "google_play or app_store for an app; null elsewhere",
          "x-nullable": true
        },
        "name": {
          "type": "string",
          "x-nullable": true
        },
        "avatar_url": {
          "type": "string",
          "x-nullable": true
        },
        "scrape_status": {
          "type": "string",
          "description": "State of the profile scrape that reads its name and avatar: pending, running, ready, failed or skipped; null when none has started or the platform has none",
          "x-nullable": true
        }
      }
    },
    "OwnedMediaPlatformProfiles": {
      "type": "object",
      "properties": {
        "platform": {
          "type": "string",
          "enum": [
            "youtube",
            "mobile_apps",
            "instagram",
            "facebook",
            "tiktok",
            "linkedin",
            "reddit"
          ]
        },
        "connected": {
          "type": "boolean"
        },
        "max_extra_profiles": {
          "type": "integer",
          "description": "How many extra profiles the platform accepts; null where it takes none",
          "x-nullable": true
        },
        "profiles": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OwnedMediaProfile"
          }
        }
      }
    },
    "OwnedMediaProfileSettings": {
      "type": "object",
      "description": "The current value of every field PATCH /projects/{id}/owned_media accepts",
      "properties": {
        "youtube_channel_url": {
          "type": "string",
          "x-nullable": true
        },
        "app_store_url": {
          "type": "string",
          "x-nullable": true
        },
        "google_play_url": {
          "type": "string",
          "x-nullable": true
        },
        "instagram_profile_url": {
          "type": "string",
          "x-nullable": true
        },
        "facebook_page_url": {
          "type": "string",
          "x-nullable": true
        },
        "tiktok_profile_url": {
          "type": "string",
          "x-nullable": true
        },
        "linkedin_company_url": {
          "type": "string",
          "x-nullable": true
        },
        "youtube_extra_profile_urls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "mobile_apps_extra_profile_urls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "instagram_extra_profile_urls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "facebook_extra_profile_urls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "tiktok_extra_profile_urls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "linkedin_extra_profile_urls": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "reddit_subreddits": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "reddit_usernames": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "OwnedMediaProfilesResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "platforms": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/OwnedMediaPlatformProfiles"
          }
        },
        "settings": {
          "$ref": "#/definitions/OwnedMediaProfileSettings"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "OwnedMediaProfilesUpdateResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/OwnedMediaProfilesResponse"
        },
        {
          "type": "object",
          "properties": {
            "updated_fields": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "The fields this request set"
            },
            "profile_scrapes_queued": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Platforms whose new profile is being scraped in the background for its name and avatar"
            }
          }
        }
      ]
    },
    "McpRequest": {
      "type": "object",
      "properties": {
        "jsonrpc": {
          "type": "string",
          "enum": [
            "2.0"
          ]
        },
        "id": {
          "x-oneOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ]
        },
        "method": {
          "type": "string",
          "description": "MCP method (initialize, tools/list, tools/call, etc.)"
        },
        "params": {
          "type": "object"
        }
      }
    },
    "McpResponse": {
      "type": "object",
      "properties": {
        "jsonrpc": {
          "type": "string"
        },
        "id": {
          "x-oneOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            }
          ]
        },
        "result": {
          "type": "object"
        },
        "error": {
          "type": "object"
        }
      }
    },
    "GeoAuditSchedule": {
      "type": "object",
      "description": "Present for weekly and monthly audits. day is 0 (Sunday) to 6 for weekly audits and 1 to 28 for monthly ones; hour is in timezone.",
      "properties": {
        "day": {
          "type": "integer"
        },
        "hour": {
          "type": "integer"
        },
        "timezone": {
          "type": "string"
        }
      },
      "x-nullable": true
    },
    "GeoAuditRun": {
      "type": "object",
      "description": "One run of an audit. Null where an audit has no run yet (latest_run) or a comparison has no earlier run (from_run).",
      "properties": {
        "sequence": {
          "type": "integer",
          "description": "Run number within the audit, starting at 1"
        },
        "status": {
          "type": "string",
          "enum": [
            "queued",
            "running",
            "completed",
            "failed",
            "unreachable"
          ]
        },
        "trigger": {
          "type": "string",
          "enum": [
            "scheduled",
            "manual",
            "api",
            "mcp",
            "legacy_import"
          ]
        },
        "score": {
          "type": "number",
          "x-nullable": true
        },
        "grade": {
          "type": "string",
          "x-nullable": true
        },
        "score_delta": {
          "type": "number",
          "description": "Score change against the previous completed run",
          "x-nullable": true
        },
        "comparable_to_previous": {
          "type": "boolean",
          "description": "False when the checks or the audit settings changed since the previous run, so a diff may reflect that change"
        },
        "new_issues": {
          "type": "integer"
        },
        "fixed_issues": {
          "type": "integer"
        },
        "regressed_issues": {
          "type": "integer"
        },
        "error": {
          "type": "string",
          "x-nullable": true
        },
        "engine_version": {
          "type": "string",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "finished_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "app_url": {
          "type": "string"
        }
      },
      "x-nullable": true
    },
    "GeoAuditRunDetail": {
      "allOf": [
        {
          "$ref": "#/definitions/GeoAuditRun"
        },
        {
          "type": "object",
          "properties": {
            "project_id": {
              "type": "integer"
            },
            "metrics": {
              "type": "object",
              "additionalProperties": {
                "type": "number"
              }
            },
            "result_data": {
              "type": "object",
              "description": "The full report of the run, in the shape of the matching technical GEO report type",
              "x-nullable": true
            },
            "request_id": {
              "type": "string"
            }
          }
        }
      ]
    },
    "GeoAudit": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "Stable audit id"
        },
        "audit_type": {
          "type": "string",
          "enum": [
            "agent_readiness",
            "robots_txt",
            "crawlability",
            "schema",
            "content_readiness",
            "discoverability",
            "site_structure"
          ]
        },
        "target": {
          "type": "string",
          "description": "The audited domain (site-wide types) or page URL, normalized"
        },
        "country_code": {
          "type": "string",
          "x-nullable": true
        },
        "cadence": {
          "type": "string",
          "enum": [
            "once",
            "weekly",
            "monthly"
          ]
        },
        "status": {
          "type": "string",
          "enum": [
            "active",
            "paused",
            "archived"
          ]
        },
        "paused_reason": {
          "type": "string",
          "description": "user, or unreachable when three runs in a row could not reach the site",
          "x-nullable": true
        },
        "schedule": {
          "$ref": "#/definitions/GeoAuditSchedule"
        },
        "next_run_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "email_alerts": {
          "type": "boolean"
        },
        "recurring_available": {
          "type": "boolean",
          "description": "Whether this audit type can run weekly or monthly"
        },
        "checks_tracked": {
          "type": "boolean",
          "description": "Whether runs of this type produce findings and issues, or a score only"
        },
        "latest_run": {
          "$ref": "#/definitions/GeoAuditRun"
        },
        "open_issues": {
          "type": "integer"
        },
        "open_critical_issues": {
          "type": "integer"
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "app_url": {
          "type": "string"
        }
      }
    },
    "GeoAuditResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/GeoAudit"
        },
        {
          "type": "object",
          "properties": {
            "project_id": {
              "type": "integer"
            },
            "request_id": {
              "type": "string"
            }
          }
        }
      ]
    },
    "GeoAuditList": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GeoAudit"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditCreateRequest": {
      "type": "object",
      "required": [
        "project_id",
        "target",
        "audit_types"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "target": {
          "type": "string",
          "description": "The domain (site-wide types) or page URL to audit"
        },
        "audit_types": {
          "type": "array",
          "description": "One or more audit types; each becomes its own audit and starts its first run",
          "items": {
            "type": "string",
            "enum": [
              "agent_readiness",
              "robots_txt",
              "crawlability",
              "schema",
              "content_readiness",
              "discoverability",
              "site_structure"
            ]
          }
        },
        "cadence": {
          "type": "string",
          "enum": [
            "once",
            "weekly",
            "monthly"
          ],
          "description": "once (default), weekly or monthly. Weekly and monthly need a type whose checks are tracked and count against the plan limit of recurring audits"
        }
      }
    },
    "GeoAuditCreateResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GeoAudit"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditUpdateRequest": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "cadence": {
          "type": "string",
          "enum": [
            "once",
            "weekly",
            "monthly"
          ]
        },
        "schedule_day": {
          "type": "integer",
          "description": "Weekly: 0 (Sunday) to 6. Monthly: 1 to 28."
        },
        "schedule_hour": {
          "type": "integer",
          "description": "Hour of the day, 0 to 23, in the audit time zone"
        },
        "status": {
          "type": "string",
          "enum": [
            "active",
            "paused",
            "archived"
          ],
          "description": "paused stops scheduled runs, active resumes them, archived is the same as DELETE"
        },
        "email_alerts": {
          "type": "boolean"
        }
      }
    },
    "GeoAuditArchived": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "id": {
          "type": "string"
        },
        "archived": {
          "type": "boolean"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditRunResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/GeoAuditRun"
        },
        {
          "type": "object",
          "properties": {
            "project_id": {
              "type": "integer"
            },
            "request_id": {
              "type": "string"
            }
          }
        }
      ]
    },
    "GeoAuditRunList": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GeoAuditRun"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditFinding": {
      "type": "object",
      "properties": {
        "check_key": {
          "type": "string",
          "description": "Stable key of the check within its audit type"
        },
        "check_title": {
          "type": "string",
          "x-nullable": true
        },
        "subject_key": {
          "type": "string",
          "description": "What the check is about (site for site-wide checks, a bot slug for robots.txt bot checks)"
        },
        "subject": {
          "type": "string",
          "x-nullable": true
        },
        "status": {
          "type": "string",
          "enum": [
            "pass",
            "warn",
            "fail",
            "info",
            "not_applicable",
            "unknown"
          ]
        },
        "severity": {
          "type": "string",
          "enum": [
            "critical",
            "high",
            "medium",
            "low",
            "info"
          ]
        },
        "evidence": {
          "type": "object"
        }
      }
    },
    "GeoAuditFindingList": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GeoAuditFinding"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditComparison": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "from_run": {
          "$ref": "#/definitions/GeoAuditRun"
        },
        "to_run": {
          "$ref": "#/definitions/GeoAuditRun"
        },
        "comparable": {
          "type": "boolean"
        },
        "score_delta": {
          "type": "number",
          "x-nullable": true
        },
        "metric_deltas": {
          "type": "object",
          "additionalProperties": {
            "type": "number"
          }
        },
        "counts": {
          "type": "object",
          "additionalProperties": {
            "type": "integer"
          }
        },
        "changes": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "check_key": {
                "type": "string"
              },
              "check_title": {
                "type": "string",
                "x-nullable": true
              },
              "subject_key": {
                "type": "string"
              },
              "subject": {
                "type": "string",
                "x-nullable": true
              },
              "severity": {
                "type": "string"
              },
              "from_status": {
                "type": "string",
                "x-nullable": true
              },
              "to_status": {
                "type": "string",
                "x-nullable": true
              },
              "change": {
                "type": "string",
                "enum": [
                  "new",
                  "fixed",
                  "changed",
                  "appeared",
                  "disappeared",
                  "unchanged"
                ]
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditIssue": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "check_key": {
          "type": "string"
        },
        "check_title": {
          "type": "string",
          "x-nullable": true
        },
        "subject_key": {
          "type": "string"
        },
        "subject": {
          "type": "string",
          "x-nullable": true
        },
        "severity": {
          "type": "string"
        },
        "state": {
          "type": "string",
          "enum": [
            "open",
            "fixed",
            "gone"
          ]
        },
        "badge": {
          "type": "string",
          "enum": [
            "new",
            "persisting",
            "regressed",
            "fixed",
            "gone"
          ],
          "description": "How the latest comparable run moved the issue"
        },
        "accepted": {
          "type": "boolean"
        },
        "accepted_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "regression_count": {
          "type": "integer"
        },
        "evidence": {
          "type": "object"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "GeoAuditIssueResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/GeoAuditIssue"
        },
        {
          "type": "object",
          "properties": {
            "project_id": {
              "type": "integer"
            },
            "request_id": {
              "type": "string"
            }
          }
        }
      ]
    },
    "GeoAuditIssueList": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GeoAuditIssue"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "GeoAuditIssueUpdateRequest": {
      "type": "object",
      "required": [
        "accepted"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "accepted": {
          "type": "boolean",
          "description": "true accepts the issue (it stays listed but leaves the open count until its evidence changes); false reopens it"
        }
      }
    },
    "GeoAlert": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "audit_id": {
          "type": "string"
        },
        "audit_type": {
          "type": "string"
        },
        "target": {
          "type": "string"
        },
        "run_sequence": {
          "type": "integer"
        },
        "severity": {
          "type": "string"
        },
        "events": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "new_critical",
                  "regression",
                  "recovered",
                  "score_drop",
                  "unreachable",
                  "paused"
                ]
              },
              "check_key": {
                "type": "string",
                "x-nullable": true
              },
              "subject_key": {
                "type": "string",
                "x-nullable": true
              },
              "severity": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "app_url": {
          "type": "string"
        }
      }
    },
    "GeoAlertList": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/GeoAlert"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PaginatedEnvelope": {
      "type": "object",
      "description": "Envelope shared by the paginated listings; each listing adds its own data rows",
      "required": [
        "project_id",
        "page",
        "per_page",
        "total",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer",
          "description": "Rows matching the filters across every page"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "TagRef": {
      "type": "object",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "MentionRecord": {
      "type": "object",
      "required": [
        "id",
        "name",
        "prompt_id",
        "prompt_execution_id",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "description": "The project's brand name (its name when no brand name is set)"
        },
        "prompt_id": {
          "type": "integer"
        },
        "prompt_execution_id": {
          "type": "integer"
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "MentionsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/MentionRecord"
              }
            }
          }
        }
      ]
    },
    "CitationRecord": {
      "type": "object",
      "required": [
        "id",
        "name",
        "domain",
        "prompt_id",
        "prompt_execution_id",
        "url",
        "position",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "description": "The project's brand name (its name when no brand name is set)"
        },
        "domain": {
          "type": "string",
          "description": "Host of the cited URL without www.; null when the URL has no parsable host",
          "x-nullable": true
        },
        "prompt_id": {
          "type": "integer"
        },
        "prompt_execution_id": {
          "type": "integer"
        },
        "url": {
          "type": "string",
          "description": "Normalized cited URL (tracking parameters and fragment removed)"
        },
        "position": {
          "type": "integer",
          "description": "Rank of the citation in the answer; 0 for a background source reference with no visible rank",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "CitationsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/CitationRecord"
              }
            }
          }
        }
      ]
    },
    "CompetitorMentionRecord": {
      "type": "object",
      "required": [
        "id",
        "competitor_id",
        "name",
        "domain",
        "prompt_execution_id",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "competitor_id": {
          "type": "integer"
        },
        "name": {
          "type": "string",
          "description": "The competitor's brand name"
        },
        "domain": {
          "type": "string",
          "description": "The competitor's bare domain"
        },
        "prompt_execution_id": {
          "type": "integer"
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "CompetitorMentionsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/CompetitorMentionRecord"
              }
            }
          }
        }
      ]
    },
    "PromptRecord": {
      "type": "object",
      "required": [
        "id",
        "prompt_text",
        "collection_id",
        "collection_ids",
        "tags",
        "country_code",
        "language_code",
        "prompt_type",
        "brand_kind",
        "last_executed_at",
        "app_url"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "prompt_text": {
          "type": "string"
        },
        "collection_id": {
          "type": "integer",
          "description": "Primary tag, when the prompt has one",
          "x-nullable": true
        },
        "collection_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "Every tag the prompt belongs to"
        },
        "tags": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TagRef"
          }
        },
        "country_code": {
          "type": "string",
          "x-nullable": true
        },
        "language_code": {
          "type": "string",
          "x-nullable": true
        },
        "prompt_type": {
          "type": "string",
          "description": "Search intent: informational, navigational, commercial or transactional. Null until the prompt is classified",
          "x-nullable": true
        },
        "brand_kind": {
          "type": "string",
          "description": "Brand focus: brand, brand_other or non_brand. Null until the prompt is classified",
          "x-nullable": true
        },
        "last_executed_at": {
          "type": "string",
          "format": "date-time",
          "description": "Null until the prompt has run",
          "x-nullable": true
        },
        "app_url": {
          "type": "string",
          "format": "uri",
          "description": "Opens this prompt in the app. The link names its project, so it opens there for any user with access to that project"
        }
      }
    },
    "PromptsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/PromptRecord"
              }
            }
          }
        }
      ]
    },
    "PromptExecutionRecord": {
      "type": "object",
      "required": [
        "id",
        "prompt_id",
        "executed_at",
        "duration_ms",
        "success",
        "model",
        "fan_out_queries",
        "has_mention",
        "has_citation",
        "mentions_count",
        "citations_count",
        "app_url"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "prompt_id": {
          "type": "integer"
        },
        "executed_at": {
          "type": "string",
          "format": "date-time",
          "description": "Null while the answer is still pending",
          "x-nullable": true
        },
        "duration_ms": {
          "type": "number",
          "x-nullable": true
        },
        "success": {
          "type": "boolean",
          "description": "Null while the answer is still pending",
          "x-nullable": true
        },
        "model": {
          "type": "string",
          "enum": [
            "chatgpt",
            "perplexity",
            "ai_mode",
            "ai_overview",
            "gemini",
            "copilot",
            "amazon_rufus",
            "claude",
            "grok",
            "deepseek",
            "naver_ai",
            "baidu_ai",
            "meta_ai"
          ]
        },
        "fan_out_queries": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Sub-queries the model issued while answering; null when the model reports none",
          "x-nullable": true
        },
        "has_mention": {
          "type": "boolean"
        },
        "has_citation": {
          "type": "boolean"
        },
        "mentions_count": {
          "type": "integer",
          "description": "1 when the answer mentions the brand, otherwise 0"
        },
        "citations_count": {
          "type": "integer",
          "description": "1 when the answer cites the brand, otherwise 0"
        },
        "app_url": {
          "type": "string",
          "format": "uri",
          "description": "Opens this answer in the app. The link names its project, so it opens there for any user with access to that project"
        }
      }
    },
    "PromptExecutionsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/PromptExecutionRecord"
              }
            }
          }
        }
      ]
    },
    "CollectionsResponse": {
      "type": "object",
      "required": [
        "project_id",
        "collections",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "collections": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TagRef"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "ModelsResponse": {
      "type": "object",
      "required": [
        "project_id",
        "models",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "models": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "chatgpt",
              "perplexity",
              "ai_mode",
              "ai_overview",
              "gemini",
              "copilot",
              "amazon_rufus",
              "claude",
              "grok",
              "deepseek",
              "naver_ai",
              "baidu_ai",
              "meta_ai"
            ]
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "LocalesResponse": {
      "type": "object",
      "required": [
        "project_id",
        "countries",
        "languages",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "countries": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Country codes with data"
        },
        "languages": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Language codes with data"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "IntelligenceTaskSummary": {
      "type": "object",
      "required": [
        "id",
        "public_id",
        "task_type",
        "title",
        "status",
        "prompt_id",
        "prompt_text",
        "word_count",
        "manually_edited_at",
        "created_at",
        "processed_at",
        "result_version"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "public_id": {
          "type": "string"
        },
        "task_type": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "status": {
          "type": "string"
        },
        "prompt_id": {
          "type": "integer",
          "x-nullable": true
        },
        "prompt_text": {
          "type": "string",
          "x-nullable": true
        },
        "word_count": {
          "type": "integer",
          "x-nullable": true
        },
        "manually_edited_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the content was last edited by hand; null while the output is as generated",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "processed_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "result_version": {
          "type": "string",
          "description": "Concurrency token for content edits: send it back as result_version on PATCH /intelligence_tasks/{id} so an edit composed before a regeneration is refused with ERR_STALE_CONTENT (409). It is \"0\" until the task first completes, then changes each time a generation or regeneration replaces result_data, never on a manual edit or a revert. Treat it as opaque text",
          "example": "1775642580"
        }
      }
    },
    "IntelligenceTasksResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/IntelligenceTaskSummary"
              }
            }
          }
        }
      ]
    },
    "RecommendationSummary": {
      "type": "object",
      "required": [
        "id",
        "project_id",
        "recommendation_type",
        "status",
        "error_message",
        "generated_at",
        "created_at",
        "updated_at",
        "total_recommendations",
        "high_priority_count",
        "summary",
        "context"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "project_id": {
          "type": "integer"
        },
        "recommendation_type": {
          "type": "string",
          "enum": [
            "ai_visibility",
            "social_community",
            "brand_building",
            "sentiment_reputation"
          ]
        },
        "status": {
          "type": "string",
          "enum": [
            "pending",
            "processing",
            "completed",
            "failed"
          ]
        },
        "error_message": {
          "type": "string",
          "description": "Set only when status is failed",
          "x-nullable": true
        },
        "generated_at": {
          "type": "string",
          "format": "date-time",
          "description": "Null until the generation completes",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        },
        "total_recommendations": {
          "type": "integer"
        },
        "high_priority_count": {
          "type": "integer"
        },
        "summary": {
          "type": "object",
          "description": "Empty until the generation completes",
          "properties": {
            "total_recommendations": {
              "type": "integer"
            },
            "high_priority_count": {
              "type": "integer"
            },
            "categories": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "context": {
          "type": "object",
          "description": "Generation context and run diagnostics as stored; empty until the generation completes. Its keys are not a stable contract"
        }
      }
    },
    "RecommendationsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/RecommendationSummary"
              }
            }
          }
        }
      ]
    },
    "SentimentRecord": {
      "type": "object",
      "required": [
        "id",
        "prompt_execution_id",
        "prompt_text",
        "model",
        "analysis",
        "score",
        "comment",
        "topics",
        "competitor_id",
        "competitor_name",
        "is_brand_sentiment",
        "executed_at",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "prompt_execution_id": {
          "type": "integer"
        },
        "prompt_text": {
          "type": "string"
        },
        "model": {
          "type": "string",
          "enum": [
            "chatgpt",
            "perplexity",
            "ai_mode",
            "ai_overview",
            "gemini",
            "copilot",
            "amazon_rufus",
            "claude",
            "grok",
            "deepseek",
            "naver_ai",
            "baidu_ai",
            "meta_ai"
          ]
        },
        "analysis": {
          "type": "string",
          "enum": [
            "very_positive",
            "positive",
            "neutral",
            "negative",
            "very_negative"
          ]
        },
        "score": {
          "type": "number",
          "description": "From -1 (very negative) to 1 (very positive)",
          "x-nullable": true
        },
        "comment": {
          "type": "string",
          "x-nullable": true
        },
        "topics": {
          "type": "string",
          "description": "Comma-separated topics",
          "x-nullable": true
        },
        "competitor_id": {
          "type": "integer",
          "description": "Null for a sentiment about the project's own brand",
          "x-nullable": true
        },
        "competitor_name": {
          "type": "string",
          "description": "Null for a sentiment about the project's own brand",
          "x-nullable": true
        },
        "is_brand_sentiment": {
          "type": "boolean"
        },
        "executed_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "SentimentsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/SentimentRecord"
              }
            }
          }
        }
      ]
    },
    "AnnotationCreateResponse": {
      "type": "object",
      "required": [
        "project_id",
        "annotation",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "annotation": {
          "type": "object",
          "required": [
            "id",
            "title",
            "annotation_date",
            "description",
            "color",
            "annotation_category_id"
          ],
          "properties": {
            "id": {
              "type": "integer"
            },
            "title": {
              "type": "string"
            },
            "annotation_date": {
              "type": "string",
              "format": "date"
            },
            "description": {
              "type": "string",
              "x-nullable": true
            },
            "color": {
              "type": "string",
              "description": "Hex color such as '#4F46E5'",
              "x-nullable": true
            },
            "annotation_category_id": {
              "type": "integer",
              "x-nullable": true
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CollectionCreateResponse": {
      "type": "object",
      "required": [
        "project_id",
        "collection",
        "prompts_attached",
        "total_collections",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "collection": {
          "type": "object",
          "required": [
            "id",
            "name",
            "description"
          ],
          "properties": {
            "id": {
              "type": "integer"
            },
            "name": {
              "type": "string"
            },
            "description": {
              "type": "string",
              "x-nullable": true
            }
          }
        },
        "prompts_attached": {
          "type": "integer",
          "description": "Existing prompts attached through prompt_ids"
        },
        "total_collections": {
          "type": "integer"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CompetitorCreateResponse": {
      "type": "object",
      "required": [
        "project_id",
        "competitor",
        "competitors_remaining",
        "total_competitors",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "competitor": {
          "type": "object",
          "required": [
            "id",
            "brand_name",
            "domain",
            "citation_match_mode",
            "citation_match_path",
            "color",
            "matching_names"
          ],
          "properties": {
            "id": {
              "type": "integer"
            },
            "brand_name": {
              "type": "string"
            },
            "domain": {
              "type": "string"
            },
            "citation_match_mode": {
              "$ref": "#/definitions/CitationMatchMode"
            },
            "citation_match_path": {
              "type": "string",
              "description": "Set only when citation_match_mode is path_prefix",
              "x-nullable": true
            },
            "color": {
              "type": "string",
              "x-nullable": true
            },
            "matching_names": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "competitors_remaining": {
          "type": "integer",
          "description": "Competitors the plan still allows in this project; null when unlimited",
          "x-nullable": true
        },
        "total_competitors": {
          "type": "integer"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PromptTagsAssignResponse": {
      "type": "object",
      "required": [
        "project_id",
        "prompts_targeted",
        "tags_attached",
        "new_links_created",
        "skipped_already_linked",
        "missing_tag_names",
        "ignored_prompt_ids",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "prompts_targeted": {
          "type": "integer",
          "description": "Prompts of the project among prompt_ids"
        },
        "tags_attached": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TagRef"
          }
        },
        "new_links_created": {
          "type": "integer"
        },
        "skipped_already_linked": {
          "type": "integer"
        },
        "missing_tag_names": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "tag_names that matched no tag and were not created"
        },
        "ignored_prompt_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "prompt_ids that are not prompts of this project"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SearchConsoleProperty": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "Property id, e.g. sc-domain:example.com"
        },
        "name": {
          "type": "string"
        }
      }
    },
    "SearchConsoleMetrics": {
      "type": "object",
      "properties": {
        "impressions": {
          "type": "integer"
        },
        "clicks": {
          "type": "integer"
        },
        "ctr": {
          "type": "number",
          "description": "0..1 fraction"
        },
        "position": {
          "type": "number",
          "description": "Impression-weighted average position"
        }
      }
    },
    "SearchConsoleSummaryResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "property": {
          "$ref": "#/definitions/SearchConsoleProperty"
        },
        "from": {
          "type": "string",
          "format": "date"
        },
        "to": {
          "type": "string",
          "format": "date"
        },
        "data_through": {
          "type": "string",
          "format": "date",
          "description": "Latest date Google has published for the property (it lags 2-3 days). Later days are missing, not zero.",
          "x-nullable": true
        },
        "totals": {
          "$ref": "#/definitions/SearchConsoleMetrics"
        },
        "dimension": {
          "type": "string",
          "description": "Present when a breakdown was requested"
        },
        "breakdown": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "value": {
                "type": "string"
              },
              "impressions": {
                "type": "integer"
              },
              "clicks": {
                "type": "integer"
              },
              "ctr": {
                "type": "number",
                "description": "0..1 fraction"
              },
              "position": {
                "type": "number",
                "description": "Impression-weighted average position"
              }
            }
          },
          "description": "Present when a breakdown was requested, sorted by impressions descending"
        },
        "breakdown_truncated": {
          "type": "boolean",
          "description": "Present (true) when limit or Google's 25,000-row maximum left breakdown rows out, so they add up to less than totals"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SearchConsoleTimeseriesResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "property": {
          "$ref": "#/definitions/SearchConsoleProperty"
        },
        "from": {
          "type": "string",
          "format": "date"
        },
        "to": {
          "type": "string",
          "format": "date"
        },
        "data_through": {
          "type": "string",
          "format": "date",
          "description": "Latest date Google has published for the property (it lags 2-3 days). Later days are missing, not zero.",
          "x-nullable": true
        },
        "granularity": {
          "type": "string"
        },
        "data": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "date": {
                "type": "string",
                "format": "date"
              },
              "impressions": {
                "type": "integer"
              },
              "clicks": {
                "type": "integer"
              },
              "ctr": {
                "type": "number",
                "description": "0..1 fraction"
              },
              "position": {
                "type": "number",
                "description": "Impression-weighted average position"
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SearchConsoleListResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "property": {
          "$ref": "#/definitions/SearchConsoleProperty"
        },
        "from": {
          "type": "string",
          "format": "date"
        },
        "to": {
          "type": "string",
          "format": "date"
        },
        "data_through": {
          "type": "string",
          "format": "date",
          "description": "Latest date Google has published for the property (it lags 2-3 days). Later days are missing, not zero.",
          "x-nullable": true
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer",
          "description": "Rows in this window (one Google request, at most 25,000)"
        },
        "sort": {
          "type": "string"
        },
        "start_row": {
          "type": "integer",
          "description": "Present when start_row was set"
        },
        "truncated": {
          "type": "boolean",
          "description": "Present (true) when Google returned 25,000 rows, so more rows exist past this window"
        },
        "data": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "description": "The query or page URL"
              },
              "impressions": {
                "type": "integer"
              },
              "clicks": {
                "type": "integer"
              },
              "ctr": {
                "type": "number",
                "description": "0..1 fraction"
              },
              "position": {
                "type": "number",
                "description": "Impression-weighted average position"
              }
            }
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SearchConsoleRowsResponse": {
      "type": "object",
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "property": {
          "$ref": "#/definitions/SearchConsoleProperty"
        },
        "from": {
          "type": "string",
          "format": "date"
        },
        "to": {
          "type": "string",
          "format": "date"
        },
        "data_through": {
          "type": "string",
          "format": "date",
          "description": "Latest date Google has published for the property (it lags 2-3 days). Later days are missing, not zero.",
          "x-nullable": true
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer",
          "description": "Rows in this window (one Google request, at most 25,000)"
        },
        "sort": {
          "type": "string"
        },
        "start_row": {
          "type": "integer",
          "description": "Present when start_row was set"
        },
        "truncated": {
          "type": "boolean",
          "description": "Present (true) when Google returned 25,000 rows, so more rows exist past this window"
        },
        "dimensions": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "data": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "date": {
                "type": "string",
                "format": "date"
              },
              "country": {
                "type": "string"
              },
              "device": {
                "type": "string"
              },
              "page": {
                "type": "string"
              },
              "query": {
                "type": "string"
              },
              "searchAppearance": {
                "type": "string"
              },
              "impressions": {
                "type": "integer"
              },
              "clicks": {
                "type": "integer"
              },
              "ctr": {
                "type": "number",
                "description": "0..1 fraction"
              },
              "position": {
                "type": "number",
                "description": "Impression-weighted average position"
              }
            }
          },
          "description": "One row per combination; only the requested dimensions are present"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "TeamPermissions": {
      "type": "object",
      "description": "What the person can do, feature key to the verbs granted (read, create, update, delete), resolved from their role. Null for the account owner, who can do everything.",
      "additionalProperties": {
        "type": "array",
        "items": {
          "type": "string"
        }
      },
      "x-nullable": true
    },
    "TeamAccountRole": {
      "type": "object",
      "description": "The account role the person follows, when permission_role is account_role; null otherwise.",
      "required": [
        "id",
        "name"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        }
      },
      "x-nullable": true
    },
    "TeamMember": {
      "type": "object",
      "required": [
        "id",
        "email",
        "role",
        "status",
        "permission_role",
        "account_role",
        "permissions",
        "account_admin",
        "billing_access",
        "all_projects",
        "project_ids"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "email": {
          "type": "string"
        },
        "name": {
          "type": "string",
          "x-nullable": true
        },
        "role": {
          "type": "string",
          "enum": [
            "owner",
            "member"
          ],
          "description": "The account owner is listed first; everyone else who joined is a member."
        },
        "status": {
          "type": "string",
          "enum": [
            "active",
            "blocked"
          ],
          "description": "A blocked member cannot sign in or use the API until unblocked in the app."
        },
        "permission_role": {
          "type": "string",
          "description": "Access level of a member: full_access, view_only, custom or account_role. Null for the owner.",
          "x-nullable": true
        },
        "account_role": {
          "$ref": "#/definitions/TeamAccountRole"
        },
        "permissions": {
          "$ref": "#/definitions/TeamPermissions"
        },
        "account_admin": {
          "type": "boolean",
          "description": "A co-owner, who can manage the team."
        },
        "billing_access": {
          "type": "boolean",
          "description": "Can open Billing and Plans (always true for the owner)."
        },
        "all_projects": {
          "type": "boolean",
          "description": "Has access to every project of the account, including future ones."
        },
        "project_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "The projects a member is limited to; empty when all_projects is true."
        },
        "last_sign_in_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "joined_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the member accepted the invitation; null for the owner and for members added without one.",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "TeamInvitation": {
      "type": "object",
      "required": [
        "id",
        "email",
        "permission_role",
        "account_role",
        "permissions",
        "account_admin",
        "billing_access",
        "all_projects",
        "project_ids"
      ],
      "properties": {
        "id": {
          "type": "integer",
          "description": "Kept by the member once they accept, when they move to GET /team_members."
        },
        "email": {
          "type": "string"
        },
        "permission_role": {
          "type": "string",
          "enum": [
            "full_access",
            "view_only",
            "custom",
            "account_role"
          ]
        },
        "account_role": {
          "$ref": "#/definitions/TeamAccountRole"
        },
        "permissions": {
          "$ref": "#/definitions/TeamPermissions"
        },
        "account_admin": {
          "type": "boolean"
        },
        "billing_access": {
          "type": "boolean"
        },
        "all_projects": {
          "type": "boolean"
        },
        "project_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          }
        },
        "invited_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the invitation email was last sent.",
          "x-nullable": true
        },
        "expires_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the invitation link stops working.",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "SuggestedPrompt": {
      "type": "object",
      "required": [
        "id",
        "prompt",
        "source",
        "status",
        "country_code",
        "language_code",
        "prompt_id",
        "accepted_at",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "prompt": {
          "type": "string"
        },
        "source": {
          "type": "string",
          "description": "The generator that wrote it: website, magic (keyword runs), topic, reddit, paa, reputational, translate, gsc (Search Console) or catalog"
        },
        "status": {
          "type": "string",
          "description": "pending, accepted or rejected"
        },
        "country_code": {
          "type": "string",
          "x-nullable": true
        },
        "language_code": {
          "type": "string",
          "x-nullable": true
        },
        "prompt_id": {
          "type": "integer",
          "description": "The prompt an accepted suggestion became",
          "x-nullable": true
        },
        "accepted_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "SuggestedPromptsResponse": {
      "type": "object",
      "required": [
        "project_id",
        "page",
        "per_page",
        "total",
        "data",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "page": {
          "type": "integer"
        },
        "per_page": {
          "type": "integer"
        },
        "total": {
          "type": "integer"
        },
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/SuggestedPrompt"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SuggestedPromptIdsRequest": {
      "type": "object",
      "required": [
        "project_id",
        "ids"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "ids": {
          "type": "array",
          "maxItems": 100,
          "items": {
            "type": "integer"
          },
          "description": "Suggestion ids (max 100)"
        }
      }
    },
    "SuggestedPromptsAcceptRequest": {
      "type": "object",
      "required": [
        "project_id",
        "ids"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "ids": {
          "type": "array",
          "maxItems": 100,
          "items": {
            "type": "integer"
          },
          "description": "Suggestion ids to accept (max 100)"
        },
        "tag_ids": {
          "type": "array",
          "maxItems": 1,
          "items": {
            "type": "integer"
          },
          "description": "One tag id to put the new prompts in"
        },
        "tag_names": {
          "type": "array",
          "maxItems": 1,
          "items": {
            "type": "string"
          },
          "description": "One tag name (case-insensitive) to put the new prompts in"
        },
        "create_missing": {
          "type": "boolean",
          "default": false,
          "description": "Create the tag named in tag_names when it does not exist"
        }
      }
    },
    "SuggestedPromptsAcceptResponse": {
      "type": "object",
      "required": [
        "project_id",
        "accepted",
        "skipped",
        "prompts_available",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "accepted": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "suggestion_id",
              "prompt_id",
              "collection_id"
            ],
            "properties": {
              "suggestion_id": {
                "type": "integer"
              },
              "prompt_id": {
                "type": "integer"
              },
              "collection_id": {
                "type": "integer",
                "description": "The tag the prompt was put in; null when no tag was given",
                "x-nullable": true
              }
            }
          }
        },
        "skipped": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "suggestion_id",
              "reason"
            ],
            "properties": {
              "suggestion_id": {
                "type": "integer"
              },
              "reason": {
                "type": "string",
                "description": "accepted or rejected (the suggestion was no longer pending), or pending_deletion"
              }
            }
          }
        },
        "prompts_available": {
          "type": "integer",
          "description": "Prompt slots left on the plan; null when unlimited",
          "x-nullable": true
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SuggestedPromptsRejectResponse": {
      "type": "object",
      "required": [
        "project_id",
        "rejected",
        "ids",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "rejected": {
          "type": "integer"
        },
        "ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "The suggestions that were pending and are now rejected"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PromptGenerationRequest": {
      "type": "object",
      "required": [
        "project_id",
        "source"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "source": {
          "type": "string",
          "enum": [
            "website",
            "keyword",
            "topic",
            "reddit",
            "paa",
            "reputational",
            "translate",
            "search_console"
          ],
          "description": "Which Add prompts generator to run"
        },
        "query": {
          "type": "string",
          "maxLength": 500,
          "description": "keyword: optional keyword or research query. topic and reddit: required topic (reddit: the brand when search_type=brand). paa: one keyword, as an alternative to keywords."
        },
        "count": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20,
          "description": "keyword and topic: how many suggestions to ask for, capped by the prompt slots left"
        },
        "country_code": {
          "type": "string",
          "description": "ISO 3166-1 alpha-2 country of the suggestions (default the project country)"
        },
        "language_code": {
          "type": "string",
          "description": "ISO language code of the suggestions (default the project language)"
        },
        "competitor_id": {
          "type": "integer",
          "description": "keyword: write the suggestions around this competitor of the project"
        },
        "search_type": {
          "type": "string",
          "enum": [
            "topic",
            "brand"
          ],
          "default": "topic",
          "description": "reddit: search discussions of a topic or a brand"
        },
        "keywords": {
          "type": "array",
          "maxItems": 5,
          "items": {
            "type": "string"
          },
          "description": "paa: up to 5 keywords; each is a People Also Ask lookup counted against the account's 24-hour allowance"
        },
        "source_country_code": {
          "type": "string",
          "description": "translate: country of the prompts to translate"
        },
        "source_language_code": {
          "type": "string",
          "description": "translate: language of the prompts to translate"
        },
        "min_words": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10,
          "default": 0,
          "description": "search_console: skip queries shorter than this many words"
        },
        "all_countries": {
          "type": "boolean",
          "default": false,
          "description": "search_console: include queries from every country instead of country_code only"
        }
      }
    },
    "PromptGenerationResponse": {
      "type": "object",
      "required": [
        "id",
        "project_id",
        "source",
        "status",
        "params",
        "created_at",
        "finished_at",
        "suggestions_count",
        "suggestions",
        "error",
        "request_id"
      ],
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "The run id; poll GET /prompt_generations/{id} with it (kept for 24 hours)"
        },
        "project_id": {
          "type": "integer"
        },
        "source": {
          "type": "string"
        },
        "status": {
          "type": "string",
          "enum": [
            "processing",
            "completed",
            "failed"
          ]
        },
        "params": {
          "type": "object",
          "description": "The validated request the run uses, with the project's locale filled in",
          "properties": {
            "country_code": {
              "type": "string"
            },
            "language_code": {
              "type": "string"
            },
            "query": {
              "type": "string"
            },
            "count": {
              "type": "integer"
            },
            "competitor_id": {
              "type": "integer"
            },
            "search_type": {
              "type": "string"
            },
            "keywords": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "source_country_code": {
              "type": "string"
            },
            "source_language_code": {
              "type": "string"
            },
            "min_words": {
              "type": "integer"
            },
            "all_countries": {
              "type": "boolean"
            }
          }
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "finished_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "suggestions_count": {
          "type": "integer"
        },
        "suggestions": {
          "type": "array",
          "description": "The suggestions the run saved to the Suggested prompts queue, with their current status",
          "items": {
            "$ref": "#/definitions/SuggestedPrompt"
          }
        },
        "error": {
          "type": "object",
          "description": "Why a failed run failed",
          "required": [
            "code",
            "message"
          ],
          "properties": {
            "code": {
              "type": "string",
              "description": "ERR_GENERATION_FAILED, ERR_NOT_FOUND (no suggestions came back), ERR_QUOTA_EXCEEDED, or a Search Console error code"
            },
            "message": {
              "type": "string"
            }
          },
          "x-nullable": true
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PromptsImportRequest": {
      "type": "object",
      "required": [
        "project_id",
        "rows"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "rows": {
          "type": "array",
          "maxItems": 500,
          "description": "One object per prompt (max 500)",
          "items": {
            "type": "object",
            "required": [
              "prompt"
            ],
            "properties": {
              "prompt": {
                "type": "string"
              },
              "country_code": {
                "type": "string",
                "description": "ISO 3166-1 alpha-2 code or country name (default the project country)"
              },
              "language_code": {
                "type": "string",
                "description": "ISO language code or language name (default the project language)"
              },
              "tags": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Tag names for this prompt, created when missing"
              }
            }
          }
        }
      }
    },
    "PromptsImportResponse": {
      "type": "object",
      "required": [
        "project_id",
        "processed_rows",
        "created",
        "duplicates",
        "tagged",
        "created_tags",
        "row_errors",
        "length_warning",
        "prompts_available",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "processed_rows": {
          "type": "integer",
          "description": "Distinct rows imported or matched to an existing prompt"
        },
        "created": {
          "type": "integer"
        },
        "duplicates": {
          "type": "integer",
          "description": "Rows that matched an existing prompt (they still get the row's tags)"
        },
        "tagged": {
          "type": "integer",
          "description": "New prompt-tag links"
        },
        "created_tags": {
          "type": "integer"
        },
        "row_errors": {
          "type": "array",
          "description": "Rows that were skipped, by their index in rows",
          "items": {
            "type": "object",
            "required": [
              "row",
              "error"
            ],
            "properties": {
              "row": {
                "type": "integer"
              },
              "error": {
                "type": "string"
              }
            }
          }
        },
        "length_warning": {
          "type": "string",
          "description": "Which tracked AI models will skip new prompts that are too long for them",
          "x-nullable": true
        },
        "prompts_available": {
          "type": "integer",
          "description": "Prompt slots left on the plan; null when unlimited",
          "x-nullable": true
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PromptTagsRemoveResponse": {
      "type": "object",
      "required": [
        "project_id",
        "prompts_targeted",
        "tags_detached",
        "links_removed",
        "skipped_not_linked",
        "missing_tag_names",
        "ignored_prompt_ids",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "prompts_targeted": {
          "type": "integer",
          "description": "Prompts of the project among prompt_ids"
        },
        "tags_detached": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TagRef"
          }
        },
        "links_removed": {
          "type": "integer"
        },
        "skipped_not_linked": {
          "type": "integer"
        },
        "missing_tag_names": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "tag_names that matched no tag"
        },
        "ignored_prompt_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "prompt_ids that are not prompts of this project"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "RecommendationItem": {
      "type": "object",
      "description": "One recommendation item, in the shape GET /recommendations/{id} lists items",
      "required": [
        "id",
        "recommendation_id",
        "recommendation_type",
        "category",
        "title",
        "priority",
        "status",
        "position",
        "action_items",
        "source_refs",
        "created_at",
        "updated_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "recommendation_id": {
          "type": "integer"
        },
        "recommendation_type": {
          "type": "string"
        },
        "category": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "description": {
          "type": "string",
          "x-nullable": true
        },
        "priority": {
          "type": "string",
          "enum": [
            "high",
            "medium",
            "low"
          ]
        },
        "estimated_impact": {
          "type": "string",
          "x-nullable": true
        },
        "action_items": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "source_refs": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Evidence codes (e.g. P1, C2) resolved in resolved_source_refs"
        },
        "status": {
          "type": "string",
          "enum": [
            "active",
            "completed",
            "archived"
          ]
        },
        "status_changed_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        },
        "position": {
          "type": "integer"
        },
        "metadata": {
          "type": "object",
          "additionalProperties": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        },
        "resolved_source_refs": {
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": true
          },
          "description": "The evidence each source_refs code points to (prompt, citation or own page)"
        },
        "unresolved_source_refs": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Present only when a code points to no stored evidence"
        }
      }
    },
    "RecommendationItemUpdateResponse": {
      "type": "object",
      "required": [
        "project_id",
        "item",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "item": {
          "$ref": "#/definitions/RecommendationItem"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "PromptCategorization": {
      "type": "object",
      "required": [
        "id",
        "raw_input",
        "manually_categorized"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "raw_input": {
          "type": "string"
        },
        "prompt_type": {
          "type": "string",
          "x-nullable": true
        },
        "brand_kind": {
          "type": "string",
          "x-nullable": true
        },
        "categorization_source": {
          "type": "string",
          "description": "manual after an edit by hand; the prompt page's revert hands it back to automatic categorization",
          "x-nullable": true
        },
        "manually_categorized": {
          "type": "boolean"
        },
        "categorized_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        }
      }
    },
    "PromptCategorizationResponse": {
      "type": "object",
      "required": [
        "project_id",
        "prompt",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "prompt": {
          "$ref": "#/definitions/PromptCategorization"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CustomReportDateRange": {
      "type": "object",
      "required": [
        "type",
        "range_days"
      ],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "rolling",
            "previous_month"
          ],
          "description": "rolling (the last range_days days) or previous_month (the previous calendar month)"
        },
        "range_days": {
          "type": "integer",
          "description": "Length of a rolling window; null for previous_month",
          "x-nullable": true
        },
        "from": {
          "type": "string",
          "format": "date",
          "description": "First day of the window as of now (detail only)"
        },
        "to": {
          "type": "string",
          "format": "date",
          "description": "Last day of the window as of now (detail only)"
        },
        "label": {
          "type": "string",
          "description": "The window as the report shows it (detail only)"
        }
      }
    },
    "CustomReportSchedule": {
      "type": "object",
      "required": [
        "enabled"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "day_of_week": {
          "type": "integer",
          "description": "0 (Sunday) to 6 (Saturday)",
          "x-nullable": true
        },
        "hour": {
          "type": "integer",
          "x-nullable": true
        },
        "timezone": {
          "type": "string",
          "x-nullable": true
        }
      }
    },
    "CustomReportWidgetRef": {
      "type": "object",
      "required": [
        "key",
        "label",
        "kind"
      ],
      "properties": {
        "key": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "kind": {
          "type": "string",
          "enum": [
            "kpi_grid",
            "timeseries",
            "donut",
            "bar",
            "table"
          ]
        }
      }
    },
    "CustomReportSummary": {
      "type": "object",
      "required": [
        "id",
        "name",
        "date_range",
        "schedule",
        "recipients_count",
        "published",
        "public_link_active",
        "password_protected",
        "portal_only",
        "widget_count",
        "widgets",
        "created_at",
        "updated_at"
      ],
      "properties": {
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        },
        "date_range": {
          "$ref": "#/definitions/CustomReportDateRange"
        },
        "schedule": {
          "$ref": "#/definitions/CustomReportSchedule"
        },
        "recipients_count": {
          "type": "integer"
        },
        "published": {
          "type": "boolean",
          "description": "Whether the shared link is published"
        },
        "public_link_active": {
          "type": "boolean",
          "description": "Whether the shared link opens (published and not made inside a client portal)"
        },
        "password_protected": {
          "type": "boolean"
        },
        "portal_only": {
          "type": "boolean",
          "description": "Made inside a client portal, so it cannot be shared or emailed"
        },
        "widget_count": {
          "type": "integer"
        },
        "widgets": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CustomReportWidgetRef"
          }
        },
        "last_sent_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the report was last emailed, on its schedule or with Send now",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "CustomReportsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/CustomReportSummary"
              }
            }
          }
        }
      ]
    },
    "CustomReportFilters": {
      "type": "object",
      "required": [
        "models",
        "collection_ids",
        "competitor_ids"
      ],
      "properties": {
        "models": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "AI models the report covers; empty means every enabled model"
        },
        "collection_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "Tags the report is limited to; empty means every prompt"
        },
        "brand_kind": {
          "type": "string",
          "x-nullable": true
        },
        "competitor_ids": {
          "type": "array",
          "items": {
            "type": "integer"
          },
          "description": "Competitors compared, the report's own choice or else the Overview selection"
        }
      }
    },
    "CustomReportBlock": {
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "widget",
            "heading",
            "text",
            "separator",
            "image"
          ]
        },
        "key": {
          "type": "string",
          "description": "The widget key, for a widget block",
          "x-nullable": true
        },
        "text": {
          "type": "string",
          "description": "The text of a heading or text block",
          "x-nullable": true
        }
      }
    },
    "CustomReportWidget": {
      "type": "object",
      "required": [
        "key",
        "label",
        "kind",
        "width",
        "available"
      ],
      "properties": {
        "key": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "kind": {
          "type": "string",
          "enum": [
            "kpi_grid",
            "timeseries",
            "donut",
            "bar",
            "table"
          ]
        },
        "width": {
          "type": "string",
          "enum": [
            "half",
            "full"
          ]
        },
        "available": {
          "type": "boolean",
          "description": "false when the widget's data source (web analytics, agent analytics, owned media) is not connected"
        },
        "data": {
          "type": "object",
          "additionalProperties": true,
          "description": "Shaped by kind. timeseries: series [{ name, color, points [{ date, value }] }]. donut: slices [{ name, value, color }]. bar: bars [{ label, value, color }]. kpi_grid: values and changes (null unless the report compares with the previous period). table: rows, one object per row. Null when the widget is not available.",
          "x-nullable": true
        }
      }
    },
    "CustomReportDetailResponse": {
      "type": "object",
      "required": [
        "project_id",
        "id",
        "name",
        "date_range",
        "schedule",
        "filters",
        "blocks",
        "widgets",
        "generated_at",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "id": {
          "type": "integer"
        },
        "name": {
          "type": "string"
        },
        "date_range": {
          "$ref": "#/definitions/CustomReportDateRange"
        },
        "schedule": {
          "$ref": "#/definitions/CustomReportSchedule"
        },
        "recipients_count": {
          "type": "integer"
        },
        "published": {
          "type": "boolean"
        },
        "public_link_active": {
          "type": "boolean"
        },
        "password_protected": {
          "type": "boolean"
        },
        "portal_only": {
          "type": "boolean"
        },
        "last_sent_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the report was last emailed, on its schedule or with Send now",
          "x-nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time"
        },
        "filters": {
          "$ref": "#/definitions/CustomReportFilters"
        },
        "compare_previous": {
          "type": "boolean"
        },
        "attach_excel": {
          "type": "boolean"
        },
        "manual_send_available_at": {
          "type": "string",
          "format": "date-time",
          "description": "When the 10-minute cooldown after the last manual send ends; null when none is running",
          "x-nullable": true
        },
        "generated_at": {
          "type": "string",
          "format": "date-time"
        },
        "blocks": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CustomReportBlock"
          },
          "description": "The report layout in order"
        },
        "widgets": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/CustomReportWidget"
          }
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "CustomReportSendResponse": {
      "type": "object",
      "required": [
        "project_id",
        "id",
        "status",
        "recipients_count",
        "request_id"
      ],
      "properties": {
        "project_id": {
          "type": "integer"
        },
        "id": {
          "type": "integer"
        },
        "status": {
          "type": "string",
          "enum": [
            "queued"
          ]
        },
        "recipients_count": {
          "type": "integer"
        },
        "request_id": {
          "type": "string"
        }
      }
    },
    "SentimentBreakdownBrand": {
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "project",
            "competitor",
            "all"
          ]
        },
        "id": {
          "type": "integer",
          "description": "The competitor id when type is competitor",
          "x-nullable": true
        },
        "name": {
          "type": "string",
          "x-nullable": true
        }
      }
    },
    "SentimentBreakdownCounts": {
      "type": "object",
      "required": [
        "mentions",
        "positive",
        "neutral",
        "negative",
        "positive_pct",
        "neutral_pct",
        "negative_pct",
        "net_score"
      ],
      "properties": {
        "mentions": {
          "type": "integer",
          "description": "Analyzed mentions"
        },
        "positive": {
          "type": "integer",
          "description": "Positive and very positive"
        },
        "neutral": {
          "type": "integer"
        },
        "negative": {
          "type": "integer",
          "description": "Negative and very negative"
        },
        "positive_pct": {
          "type": "number"
        },
        "neutral_pct": {
          "type": "number"
        },
        "negative_pct": {
          "type": "number"
        },
        "net_score": {
          "type": "number",
          "description": "Positive minus negative, divided by mentions, times 100"
        }
      }
    },
    "SentimentTopicRow": {
      "allOf": [
        {
          "type": "object",
          "required": [
            "topic"
          ],
          "properties": {
            "topic": {
              "type": "string"
            }
          }
        },
        {
          "$ref": "#/definitions/SentimentBreakdownCounts"
        }
      ]
    },
    "SentimentTagRow": {
      "allOf": [
        {
          "type": "object",
          "required": [
            "tag_id",
            "tag"
          ],
          "properties": {
            "tag_id": {
              "type": "integer"
            },
            "tag": {
              "type": "string"
            }
          }
        },
        {
          "$ref": "#/definitions/SentimentBreakdownCounts"
        }
      ]
    },
    "SentimentBreakdownEnvelope": {
      "type": "object",
      "required": [
        "from",
        "to",
        "brand",
        "sort",
        "sort_dir"
      ],
      "properties": {
        "from": {
          "type": "string",
          "format": "date-time"
        },
        "to": {
          "type": "string",
          "format": "date-time"
        },
        "brand": {
          "$ref": "#/definitions/SentimentBreakdownBrand"
        },
        "sort": {
          "type": "string"
        },
        "sort_dir": {
          "type": "string"
        }
      }
    },
    "SentimentTopicsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "$ref": "#/definitions/SentimentBreakdownEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/SentimentTopicRow"
              }
            }
          }
        }
      ]
    },
    "SentimentTagsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "$ref": "#/definitions/SentimentBreakdownEnvelope"
        },
        {
          "type": "object",
          "required": [
            "data"
          ],
          "properties": {
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/SentimentTagRow"
              }
            }
          }
        }
      ]
    },
    "CompetitorSuggestion": {
      "type": "object",
      "required": [
        "id",
        "domain",
        "matching_names"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "The suggestion key (brands) or the id of one citing source row (citations)"
        },
        "domain": {
          "type": "string"
        },
        "brand_name": {
          "type": "string",
          "description": "The proposed brand name; null for a cited domain",
          "x-nullable": true
        },
        "matching_names": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The names a tracked competitor would be matched by; empty for a cited domain"
        },
        "mentions": {
          "type": "integer",
          "description": "Answers naming the brand, when the discovery counted them (brands only)",
          "x-nullable": true
        },
        "citations": {
          "type": "integer",
          "description": "Answers citing the domain (citations only)",
          "x-nullable": true
        }
      }
    },
    "CompetitorDiscoveryState": {
      "type": "object",
      "required": [
        "loading"
      ],
      "properties": {
        "status": {
          "type": "string",
          "description": "The last discovery run: pending, processing, completed or failed; null when none ever ran",
          "x-nullable": true
        },
        "loading": {
          "type": "boolean",
          "description": "A discovery run is in progress"
        },
        "generated_at": {
          "type": "string",
          "format": "date-time",
          "x-nullable": true
        }
      }
    },
    "CompetitorSuggestionsResponse": {
      "allOf": [
        {
          "$ref": "#/definitions/PaginatedEnvelope"
        },
        {
          "type": "object",
          "required": [
            "basis",
            "discovery",
            "data"
          ],
          "properties": {
            "basis": {
              "type": "string",
              "enum": [
                "brands",
                "citations"
              ]
            },
            "discovery": {
              "$ref": "#/definitions/CompetitorDiscoveryState"
            },
            "data": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/CompetitorSuggestion"
              }
            }
          }
        }
      ]
    }
  }
}
