> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aitasker.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Review Feed Route

> The cross-brand review feed behind Explore Reviews.

Every filter is applied in SQL. The pre-existing client-side filters
narrowed only the loaded page, so "3 one-star reviews" could be reported
when the real answer was thirty.

Explore is competitor discovery: the caller's OWN brands are excluded
(their reviews live in the owned-brand list, and mixing the two makes the
brand column ambiguous). Cross-tenant exposure was reversed by operator
ruling 2026-08-17 — another tenant's own brand is no longer discoverable
here unless this caller independently owns, discovered, or tracks it.
Enforced in review_service via visible_to_caller_predicate.



## OpenAPI

````yaml /openapi.json get /api/v1/brand-reviews/feed
openapi: 3.1.0
info:
  title: AITasker
  version: 0.1.0
servers: []
security: []
paths:
  /api/v1/brand-reviews/feed:
    get:
      tags:
        - Brand Reviews
        - Brand Reviews
      summary: Review Feed Route
      description: >-
        The cross-brand review feed behind Explore Reviews.


        Every filter is applied in SQL. The pre-existing client-side filters

        narrowed only the loaded page, so "3 one-star reviews" could be reported

        when the real answer was thirty.


        Explore is competitor discovery: the caller's OWN brands are excluded

        (their reviews live in the owned-brand list, and mixing the two makes
        the

        brand column ambiguous). Cross-tenant exposure was reversed by operator

        ruling 2026-08-17 — another tenant's own brand is no longer discoverable

        here unless this caller independently owns, discovered, or tracks it.

        Enforced in review_service via visible_to_caller_predicate.
      operationId: review_feed_route_api_v1_brand_reviews_feed_get
      parameters:
        - name: platform
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                minLength: 1
                maxLength: 20
              - type: 'null'
            title: Platform
        - name: advertiser_id
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                format: uuid
              - type: 'null'
            title: Advertiser Id
        - name: industry
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                maxLength: 1000
                minLength: 1
              - type: 'null'
            title: Industry
        - name: business_model
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                minLength: 1
                maxLength: 400
              - type: 'null'
            title: Business Model
        - name: min_rating
          in: query
          required: false
          schema:
            anyOf:
              - type: number
                maximum: 5
                minimum: 1
              - type: 'null'
            title: Min Rating
        - name: max_rating
          in: query
          required: false
          schema:
            anyOf:
              - type: number
                maximum: 5
                minimum: 1
              - type: 'null'
            title: Max Rating
        - name: has_response
          in: query
          required: false
          schema:
            anyOf:
              - type: boolean
              - type: 'null'
            title: Has Response
        - name: verified
          in: query
          required: false
          schema:
            anyOf:
              - type: boolean
              - type: 'null'
            title: Verified
        - name: language
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                maxLength: 200
                minLength: 1
              - type: 'null'
            title: Language
        - name: has_text
          in: query
          required: false
          schema:
            anyOf:
              - type: boolean
              - type: 'null'
            title: Has Text
        - name: q
          in: query
          required: false
          schema:
            anyOf:
              - type: string
                maxLength: 200
                minLength: 1
              - type: 'null'
            title: Q
        - name: sort
          in: query
          required: false
          schema:
            type: string
            default: recent
            title: Sort
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            maximum: 100
            minimum: 1
            default: 50
            title: Limit
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            title: Offset
            default: 0
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BrandReviewFeedResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - HTTPBearer: []
components:
  schemas:
    BrandReviewFeedResponse:
      properties:
        generated_at:
          type: string
          format: date-time
          title: Generated At
        total:
          type: integer
          minimum: 0
          title: Total
          default: 0
        limit:
          type: integer
          minimum: 1
          title: Limit
        offset:
          type: integer
          minimum: 0
          title: Offset
          default: 0
        has_more:
          type: boolean
          title: Has More
          default: false
        reviews:
          items:
            $ref: '#/components/schemas/BrandReviewFeedItem'
          type: array
          title: Reviews
      type: object
      required:
        - generated_at
        - limit
      title: BrandReviewFeedResponse
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    BrandReviewFeedItem:
      properties:
        id:
          type: string
          format: uuid
          title: Id
        platform:
          type: string
          enum:
            - trustpilot
            - google
          title: Platform
        rating:
          type: number
          maximum: 5
          minimum: 1
          title: Rating
        title:
          anyOf:
            - type: string
            - type: 'null'
          title: Title
        body:
          anyOf:
            - type: string
            - type: 'null'
          title: Body
        reviewed_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Reviewed At
        verified:
          anyOf:
            - type: boolean
            - type: 'null'
          title: Verified
        business_response:
          anyOf:
            - type: string
            - type: 'null'
          title: Business Response
        business_responded_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Business Responded At
        source_url:
          anyOf:
            - type: string
            - type: 'null'
          title: Source Url
        aspects:
          items:
            $ref: '#/components/schemas/BrandReviewAspectScore'
          type: array
          title: Aspects
        advertiser_id:
          type: string
          format: uuid
          title: Advertiser Id
        brand_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Brand Name
        brand_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Brand Domain
        industry:
          anyOf:
            - type: string
            - type: 'null'
          title: Industry
        business_model:
          anyOf:
            - type: string
            - type: 'null'
          title: Business Model
      type: object
      required:
        - id
        - platform
        - rating
        - advertiser_id
      title: BrandReviewFeedItem
      description: |-
        A review plus the brand it belongs to — the cross-brand feed's row.

        Explore Reviews is a competitor-discovery surface: owned brands are
        excluded from it at the query, and reviewer identity is off the wire
        (operator ruling 2026-08-09).
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    BrandReviewAspectScore:
      properties:
        aspect:
          type: string
          maxLength: 100
          minLength: 1
          title: Aspect
        aspect_label:
          type: string
          maxLength: 100
          minLength: 1
          title: Aspect Label
        sentiment:
          type: string
          enum:
            - positive
            - negative
            - mixed
            - neutral
          title: Sentiment
      type: object
      required:
        - aspect
        - aspect_label
        - sentiment
      title: BrandReviewAspectScore
      description: >-
        One classifier-assigned aspect on a single review.


        The vocabulary slug, its display label (recovered from the ONE taxonomy

        map — never a second vocabulary), and the per-aspect sentiment verdict.

        That is the whole of it, and the omissions are the point:


        * **No ``evidence`` span.** ``brand_reviews.review_classification_json``
          stores a verbatim fragment of ``brand_reviews.body`` beside every
          aspect. This model hangs off :class:`BrandReviewCore`, which
          COMPETITOR-owned rows inherit, and competitor verbatim text does not
          reach that wire today. The licensed excerpt surface stays
          ``ReviewAspectEvidence`` (the group-insights evidence drawer), which
          bounds the span, requires a source URL and is separately gated.
        * **No ``sub_theme``.** Free-form model output rather than vocabulary,
        so
          the same reasoning applies — and it is a diagnosis layer no detail panel
          reads.
        * **No star score.** ``sentiment`` is what was MEASURED; stars are a
          rendering of it. Mapping here would put a presentation choice on the
          wire and make a second, divergent scale possible next to the one
          ``ReviewDetailPanel`` already applies to evidence rows.
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````