{
  "openapi": "3.1.0",
  "info": {
    "title": "MentionsAPI",
    "version": "1.0.0",
    "description": "Unified API for tracking brand mentions across ChatGPT, Claude, Gemini, and Perplexity. Query all 4 LLMs with one call. Get back normalized responses with extracted brand mentions, cited domains, and rank positions. Built for GEO tool builders, SEO agencies, and brand monitoring teams.",
    "contact": {
      "name": "MentionsAPI Support",
      "email": "hello@mentionsapi.com",
      "url": "https://mentionsapi.com/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://mentionsapi.com/legal/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.mentionsapi.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Ask",
      "description": "Multi-provider LLM queries with brand and citation extraction."
    },
    {
      "name": "Extract Brands",
      "description": "Standalone brand-mention extraction from arbitrary text."
    },
    {
      "name": "Mentions",
      "description": "Query and crawl the LLM brand-mentions database."
    },
    {
      "name": "Usage",
      "description": "Historical usage reporting for the calling account."
    }
  ],
  "paths": {
    "/v1/ask": {
      "post": {
        "tags": [
          "Ask"
        ],
        "operationId": "createAsk",
        "summary": "Multi-provider LLM query",
        "description": "Fans out a single prompt to ChatGPT, Claude, Gemini, and Perplexity (or a subset), deduplicates citations, extracts brand mentions, and returns one normalized payload. Cache tiers (l1/l2/l3) are automatic; the `cache_scope` parameter controls whether the response can be shared across accounts. Set `async: true` with a `webhook_id` to defer execution and receive the result via webhook. Set `mode: \"standard\"` for cheaper 15-minute asynchronous delivery polled via GET /v1/ask/{id}.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Synchronous response \u2014 at least one provider returned content.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          },
          "202": {
            "description": "Async or standard-mode request queued. Poll `GET /v1/ask/{id}` or wait for the webhook delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskQueuedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits to fulfill the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Per-key rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ask/{id}": {
      "get": {
        "tags": [
          "Ask"
        ],
        "operationId": "getAskById",
        "summary": "Retrieve a past Ask response",
        "description": "Fetches a previously executed Ask response by request id. Responses are retained for 30 days. Returns 202 while async or standard-mode jobs are still processing and 410 once the retention window has expired.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Request id returned by POST /v1/ask.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Response available \u2014 either succeeded (full payload) or failed (error detail).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/AskResponse"
                    },
                    {
                      "$ref": "#/components/schemas/AskFailedResponse"
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Request is still queued or processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskPendingResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid id parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "No Ask with this id exists for the calling account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "410": {
            "description": "Archive retention expired (responses are kept for 30 days).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/extract_brands": {
      "post": {
        "tags": [
          "Extract Brands"
        ],
        "operationId": "extractBrands",
        "summary": "Extract brand mentions from arbitrary text",
        "description": "Runs the brand-extraction pipeline against any text you supply \u2014 useful for post-processing LLM responses you obtained elsewhere. Returns normalized brand mentions with rank, sentiment, and context.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractBrandsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extracted brand mentions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractBrandsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/mentions/search": {
      "get": {
        "tags": [
          "Mentions"
        ],
        "operationId": "searchMentions",
        "summary": "Search the brand-mentions database",
        "description": "Query the historical brand-mentions index populated by the crawler. Filter by brand, provider, and time window. Returns suggestions (via pg_trgm) when no exact matches exist.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "brand",
            "in": "query",
            "required": true,
            "description": "Brand name to look up.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            }
          },
          {
            "name": "provider",
            "in": "query",
            "description": "Restrict to a single provider.",
            "schema": {
              "$ref": "#/components/schemas/ProviderId"
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "ISO 8601 datetime lower bound (snapshot_at \u2265 since).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Max rows to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching mentions plus optional suggestions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MentionsSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits (10\u00a2 per query).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/mentions/crawl": {
      "post": {
        "tags": [
          "Mentions"
        ],
        "operationId": "crawlMentions",
        "summary": "Trigger an on-demand brand crawl",
        "description": "Fires a live 4-provider fanout with web search enabled, extracts brand mentions from each response, and writes them into the `brand_mentions` table. Automatically adds the queried brand to `brand_catalog`. Price: flat $2.00.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MentionsCrawlRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Crawl completed; provider results and inserted mentions returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MentionsCrawlResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient credits (200\u00a2 per crawl).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "operationId": "getUsage",
        "summary": "Account usage report",
        "description": "Return rollups of usage_events for the calling account, bucketed by day, hour, or a single total. Default window is the last 30 days.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "start",
            "in": "query",
            "description": "ISO 8601 start of the window.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "end",
            "in": "query",
            "description": "ISO 8601 end of the window.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "granularity",
            "in": "query",
            "description": "Bucket size for the returned series.",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "hour",
                "total"
              ],
              "default": "day"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage report.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageReport"
                }
              }
            }
          },
          "400": {
            "description": "Invalid window (e.g. start \u2265 end).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "Send your API key as 'Authorization: Bearer <token>'. Create and manage keys in your dashboard."
      }
    },
    "schemas": {
      "ProviderId": {
        "type": "string",
        "enum": [
          "openai",
          "anthropic",
          "gemini",
          "perplexity"
        ],
        "description": "The LLM provider that produced a result."
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant"
            ]
          },
          "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20000
          }
        }
      },
      "JsonSchemaSpec": {
        "type": "object",
        "required": [
          "name",
          "schema"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "schema": {
            "type": "object",
            "additionalProperties": true
          },
          "strict": {
            "type": "boolean"
          }
        }
      },
      "Citation": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "snippet": {
            "type": "string"
          }
        }
      },
      "AggregatedCitation": {
        "type": "object",
        "required": [
          "canonical_url",
          "domains",
          "providers_cited"
        ],
        "properties": {
          "canonical_url": {
            "type": "string",
            "format": "uri"
          },
          "domains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "providers_cited": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProviderId"
            }
          },
          "title": {
            "type": "string"
          }
        }
      },
      "TokenUsage": {
        "type": "object",
        "required": [
          "input",
          "output"
        ],
        "properties": {
          "input": {
            "type": "integer",
            "minimum": 0
          },
          "output": {
            "type": "integer",
            "minimum": 0
          },
          "reasoning": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ProviderResult": {
        "type": "object",
        "required": [
          "provider",
          "model",
          "content",
          "citations",
          "tokens",
          "latency_ms"
        ],
        "properties": {
          "provider": {
            "$ref": "#/components/schemas/ProviderId"
          },
          "model": {
            "type": "string",
            "example": "gpt-4o-2024-08-06"
          },
          "content": {
            "type": "string"
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "tokens": {
            "$ref": "#/components/schemas/TokenUsage"
          },
          "latency_ms": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ProviderError": {
        "type": "object",
        "required": [
          "provider",
          "error"
        ],
        "properties": {
          "provider": {
            "$ref": "#/components/schemas/ProviderId"
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "provider_timeout"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "BrandMention": {
        "type": "object",
        "required": [
          "brand",
          "provider",
          "rank",
          "sentiment",
          "context"
        ],
        "properties": {
          "brand": {
            "type": "string"
          },
          "provider": {
            "$ref": "#/components/schemas/ProviderId"
          },
          "rank": {
            "type": "integer",
            "minimum": 1,
            "description": "1-indexed position of the first mention inside the provider response."
          },
          "sentiment": {
            "type": "string",
            "enum": [
              "positive",
              "neutral",
              "negative"
            ]
          },
          "context": {
            "type": "string"
          }
        }
      },
      "ProviderCostEstimate": {
        "type": "object",
        "required": [
          "provider",
          "model",
          "tokens",
          "upstream_cost_cents_estimate"
        ],
        "properties": {
          "provider": {
            "$ref": "#/components/schemas/ProviderId"
          },
          "model": {
            "type": "string"
          },
          "tokens": {
            "$ref": "#/components/schemas/TokenUsage"
          },
          "upstream_cost_cents_estimate": {
            "type": "integer",
            "minimum": 0
          },
          "web_search_used": {
            "type": "boolean"
          }
        }
      },
      "CostBreakdown": {
        "type": "object",
        "required": [
          "our_price_cents",
          "upstream_cost_cents_estimate",
          "margin_cents",
          "by_provider"
        ],
        "properties": {
          "our_price_cents": {
            "type": "integer",
            "minimum": 0
          },
          "upstream_cost_cents_estimate": {
            "type": "integer",
            "minimum": 0
          },
          "margin_cents": {
            "type": "integer"
          },
          "by_provider": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProviderCostEstimate"
            }
          }
        }
      },
      "AskUsage": {
        "type": "object",
        "required": [
          "billable_units",
          "latency_ms"
        ],
        "properties": {
          "billable_units": {
            "type": "integer",
            "minimum": 0
          },
          "latency_ms": {
            "type": "integer",
            "minimum": 0
          },
          "cost_cents": {
            "type": "integer",
            "minimum": 0
          },
          "cost_breakdown": {
            "$ref": "#/components/schemas/CostBreakdown"
          }
        }
      },
      "AskRequest": {
        "type": "object",
        "description": "Exactly one of `prompt` or `messages` must be provided.",
        "properties": {
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000,
            "description": "Legacy single-turn prompt."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            },
            "description": "Multi-turn conversation. Each provider proxy supports turn arrays natively."
          },
          "system_message": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20000,
            "description": "Top-level system message. Ignored if `messages` contains a system turn."
          },
          "providers": {
            "type": "array",
            "minItems": 1,
            "maxItems": 4,
            "items": {
              "$ref": "#/components/schemas/ProviderId"
            }
          },
          "model": {
            "type": "object",
            "description": "Per-provider model override. Each value must be on the proxy allowlist.",
            "additionalProperties": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          "params": {
            "type": "object",
            "properties": {
              "temperature": {
                "type": "number",
                "minimum": 0,
                "maximum": 2
              },
              "max_tokens": {
                "type": "integer",
                "minimum": 1,
                "maximum": 8000
              }
            }
          },
          "json_schema": {
            "$ref": "#/components/schemas/JsonSchemaSpec"
          },
          "cache_scope": {
            "type": "string",
            "enum": [
              "shared",
              "private"
            ],
            "default": "shared",
            "description": "`shared` allows cache hits across accounts; `private` namespaces by account."
          },
          "track_brands": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            }
          },
          "web_search": {
            "type": "boolean",
            "default": true
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO 3166-1 alpha-2 country code."
          },
          "async": {
            "type": "boolean",
            "description": "When true, the call is deferred to a queue and delivered to `webhook_id`."
          },
          "webhook_id": {
            "type": "string",
            "format": "uuid",
            "description": "Required when `async: true`. Identifies the webhook endpoint that receives the ask.completed event."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "standard"
            ],
            "default": "live",
            "description": "`live` = synchronous. `standard` = async delivery within 15 min, 30% cheaper."
          }
        }
      },
      "AskResponse": {
        "type": "object",
        "required": [
          "request_id",
          "id",
          "cached",
          "cache_tier",
          "providers",
          "brand_mentions",
          "citations",
          "usage"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "cached": {
            "type": "boolean"
          },
          "cache_tier": {
            "type": "string",
            "enum": [
              "l1",
              "l2",
              "l3",
              "miss"
            ]
          },
          "providers": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ProviderResult"
                },
                {
                  "$ref": "#/components/schemas/ProviderError"
                }
              ]
            }
          },
          "brand_mentions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BrandMention"
            }
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AggregatedCitation"
            }
          },
          "usage": {
            "$ref": "#/components/schemas/AskUsage"
          }
        }
      },
      "AskQueuedResponse": {
        "type": "object",
        "required": [
          "request_id",
          "ask_id",
          "status"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "ask_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "standard"
            ]
          },
          "poll_url": {
            "type": "string"
          },
          "estimated_cost_cents": {
            "type": "integer",
            "minimum": 0
          },
          "price_label": {
            "type": "string"
          }
        }
      },
      "AskPendingResponse": {
        "type": "object",
        "required": [
          "request_id",
          "id",
          "status"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "processing"
            ]
          }
        }
      },
      "AskFailedResponse": {
        "type": "object",
        "required": [
          "request_id",
          "id",
          "status",
          "error"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "failed"
            ]
          },
          "error": {
            "$ref": "#/components/schemas/ErrorObject"
          }
        }
      },
      "ExtractBrandsRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50000
          },
          "track_brands": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            }
          },
          "source_provider": {
            "type": "string",
            "enum": [
              "openai",
              "anthropic",
              "gemini",
              "perplexity",
              "unknown"
            ],
            "default": "unknown"
          }
        }
      },
      "ExtractBrandsResponse": {
        "type": "object",
        "required": [
          "request_id",
          "mentions",
          "usage"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "mentions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BrandMention"
            }
          },
          "usage": {
            "type": "object",
            "required": [
              "billable_units",
              "latency_ms"
            ],
            "properties": {
              "billable_units": {
                "type": "integer",
                "minimum": 0
              },
              "latency_ms": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "Mention": {
        "type": "object",
        "required": [
          "id",
          "brand",
          "provider",
          "model",
          "prompt",
          "snapshot_at",
          "citations"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "brand": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "prompt_category": {
            "type": "string",
            "nullable": true
          },
          "snapshot_at": {
            "type": "string",
            "format": "date-time"
          },
          "content": {
            "type": "string",
            "nullable": true
          },
          "rank": {
            "type": "integer",
            "nullable": true
          },
          "sentiment": {
            "type": "string",
            "enum": [
              "positive",
              "neutral",
              "negative"
            ],
            "nullable": true
          },
          "context": {
            "type": "string",
            "nullable": true
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "raw_response_id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "BrandSuggestion": {
        "type": "object",
        "required": [
          "brand",
          "normalized_brand",
          "similarity"
        ],
        "properties": {
          "brand": {
            "type": "string"
          },
          "normalized_brand": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "nullable": true
          },
          "similarity": {
            "type": "number"
          }
        }
      },
      "MentionsSearchResponse": {
        "type": "object",
        "required": [
          "request_id",
          "query",
          "mentions",
          "suggestions",
          "usage"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "query": {
            "type": "object",
            "properties": {
              "brand": {
                "type": "string"
              },
              "normalized_brand": {
                "type": "string"
              },
              "provider": {
                "type": "string",
                "nullable": true
              },
              "since": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "limit": {
                "type": "integer"
              }
            }
          },
          "mentions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Mention"
            }
          },
          "suggestions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BrandSuggestion"
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "billable_units": {
                "type": "integer"
              },
              "latency_ms": {
                "type": "integer"
              },
              "cost_cents": {
                "type": "integer"
              }
            }
          }
        }
      },
      "MentionsCrawlRequest": {
        "type": "object",
        "required": [
          "brand"
        ],
        "properties": {
          "brand": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "prompt": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000,
            "description": "Custom crawl prompt. Defaults to an 'alternatives to <brand>' question."
          },
          "category": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          }
        }
      },
      "MentionsCrawlResponse": {
        "type": "object",
        "required": [
          "request_id",
          "brand",
          "normalized_brand",
          "prompt",
          "mentions",
          "total_mentions_found",
          "providers",
          "usage"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "brand": {
            "type": "string"
          },
          "normalized_brand": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "mentions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "brand": {
                  "type": "string"
                },
                "provider": {
                  "type": "string"
                },
                "model": {
                  "type": "string"
                },
                "snapshot_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "rank": {
                  "type": "integer",
                  "nullable": true
                },
                "sentiment": {
                  "type": "string",
                  "nullable": true
                },
                "context": {
                  "type": "string",
                  "nullable": true
                },
                "content_excerpt": {
                  "type": "string",
                  "nullable": true
                }
              }
            }
          },
          "total_mentions_found": {
            "type": "integer",
            "minimum": 0
          },
          "providers": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ProviderResult"
                },
                {
                  "$ref": "#/components/schemas/ProviderError"
                }
              ]
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "billable_units": {
                "type": "integer"
              },
              "latency_ms": {
                "type": "integer"
              },
              "cost_cents": {
                "type": "integer"
              }
            }
          }
        }
      },
      "UsageBucket": {
        "type": "object",
        "required": [
          "requests",
          "billable_units",
          "cache_hits",
          "errors",
          "provider_breakdown"
        ],
        "properties": {
          "requests": {
            "type": "integer",
            "minimum": 0
          },
          "billable_units": {
            "type": "integer",
            "minimum": 0
          },
          "cache_hits": {
            "type": "integer",
            "minimum": 0
          },
          "errors": {
            "type": "integer",
            "minimum": 0
          },
          "provider_breakdown": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        }
      },
      "UsageReport": {
        "type": "object",
        "required": [
          "request_id",
          "account_id",
          "window",
          "total",
          "series"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "account_id": {
            "type": "string",
            "format": "uuid"
          },
          "plan": {
            "type": "string"
          },
          "window": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "format": "date-time"
              },
              "end": {
                "type": "string",
                "format": "date-time"
              },
              "granularity": {
                "type": "string",
                "enum": [
                  "day",
                  "hour",
                  "total"
                ]
              }
            }
          },
          "total": {
            "$ref": "#/components/schemas/UsageBucket"
          },
          "series": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "type": "object",
                  "properties": {
                    "bucket": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "bucket"
                  ]
                },
                {
                  "$ref": "#/components/schemas/UsageBucket"
                }
              ]
            }
          },
          "monthly_quota": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "ErrorObject": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "insufficient_credits"
          },
          "message": {
            "type": "string",
            "example": "This call costs 5\u00a2 but your balance is only 2\u00a2. Top up at https://mentionsapi.com/app/billing."
          },
          "balance_cents": {
            "type": "integer"
          },
          "required_cents": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "$ref": "#/components/schemas/ErrorResponse"
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorObject"
          },
          "request_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      }
    }
  }
}
