> ## 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.

# Advertiser Review Metrics Route

> Measured review metrics for one advertiser.

Read-only and free: every figure is SQL over stored columns, so this is
not gated behind the collection switch (which exists because acquisition
SPENDS money) and needs no throttle.

Served on two paths. ``/reputation`` is the one the client calls; the
older ``/metrics`` stays registered because a URL ending in that segment
is a standing target for ad-blocker and privacy filter lists, which reject
the request in the browser before it is ever sent. The reader then sees a
failure indistinguishable from an outage on a page whose sibling requests
all succeed. The legacy path is kept — and hidden from the schema — so
that browser tabs still running a cached bundle keep working through a
deploy; it can be deleted once no client references it.

Brand Library tenancy (operator ruling 2026-08-17) applies the same as
every other single-advertiser Brand Details read on this router.



## OpenAPI

````yaml /openapi.json get /api/v1/brand-reviews/advertisers/{advertiser_id}/reputation
openapi: 3.1.0
info:
  title: AITasker
  version: 0.1.0
servers: []
security: []
paths:
  /api/v1/brand-reviews/advertisers/{advertiser_id}/reputation:
    get:
      tags:
        - Brand Reviews
        - Brand Reviews
      summary: Advertiser Review Metrics Route
      description: >-
        Measured review metrics for one advertiser.


        Read-only and free: every figure is SQL over stored columns, so this is

        not gated behind the collection switch (which exists because acquisition

        SPENDS money) and needs no throttle.


        Served on two paths. ``/reputation`` is the one the client calls; the

        older ``/metrics`` stays registered because a URL ending in that segment

        is a standing target for ad-blocker and privacy filter lists, which
        reject

        the request in the browser before it is ever sent. The reader then sees
        a

        failure indistinguishable from an outage on a page whose sibling
        requests

        all succeed. The legacy path is kept — and hidden from the schema — so

        that browser tabs still running a cached bundle keep working through a

        deploy; it can be deleted once no client references it.


        Brand Library tenancy (operator ruling 2026-08-17) applies the same as

        every other single-advertiser Brand Details read on this router.
      operationId: >-
        advertiser_review_metrics_route_api_v1_brand_reviews_advertisers__advertiser_id__reputation_get
      parameters:
        - name: advertiser_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Advertiser Id
        - name: platform
          in: query
          required: false
          schema:
            anyOf:
              - type: string
              - type: 'null'
            title: Platform
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BrandReviewMetricsResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - HTTPBearer: []
components:
  schemas:
    BrandReviewMetricsResponse:
      properties:
        advertiser_id:
          type: string
          format: uuid
          title: Advertiser Id
        platform:
          anyOf:
            - type: string
            - type: 'null'
          title: Platform
        sample_size:
          type: integer
          minimum: 0
          title: Sample Size
          default: 0
        sample_is_platform_total:
          type: boolean
          title: Sample Is Platform Total
          default: false
        depth_capped:
          type: boolean
          title: Depth Capped
          default: false
        average_rating:
          anyOf:
            - type: number
            - type: 'null'
          title: Average Rating
        rating_distribution:
          items:
            $ref: '#/components/schemas/BrandReviewRatingBucket'
          type: array
          title: Rating Distribution
        response:
          $ref: '#/components/schemas/BrandReviewResponseMetrics'
        platforms:
          items:
            $ref: '#/components/schemas/BrandReviewPlatformSplit'
          type: array
          title: Platforms
        trend:
          items:
            $ref: '#/components/schemas/BrandReviewTrendPoint'
          type: array
          title: Trend
        undated_reviews:
          type: integer
          minimum: 0
          title: Undated Reviews
          default: 0
        generated_at:
          type: string
          format: date-time
          title: Generated At
      type: object
      required:
        - advertiser_id
        - generated_at
      title: BrandReviewMetricsResponse
      description: Measured review metrics. Every field is counted, none inferred.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    BrandReviewRatingBucket:
      properties:
        stars:
          type: integer
          maximum: 5
          minimum: 1
          title: Stars
        count:
          type: integer
          minimum: 0
          title: Count
          default: 0
      type: object
      required:
        - stars
      title: BrandReviewRatingBucket
      description: |-
        One whole-star bucket. Always all five, zeros included.

        A missing bucket and a zero bucket render identically in most chart
        libraries but mean different things; emitting all five makes "no 1-star
        reviews in the sample" an explicit measured statement.
    BrandReviewResponseMetrics:
      properties:
        answered:
          type: integer
          minimum: 0
          title: Answered
          default: 0
        response_rate:
          anyOf:
            - type: number
              maximum: 1
              minimum: 0
            - type: 'null'
          title: Response Rate
        median_response_hours:
          anyOf:
            - type: number
            - type: 'null'
          title: Median Response Hours
        latency_measured_over:
          type: integer
          minimum: 0
          title: Latency Measured Over
          default: 0
        negatives:
          type: integer
          minimum: 0
          title: Negatives
          default: 0
        unanswered_negatives:
          type: integer
          minimum: 0
          title: Unanswered Negatives
          default: 0
      type: object
      title: BrandReviewResponseMetrics
      description: Reply behaviour, counted from stored columns only.
    BrandReviewPlatformSplit:
      properties:
        platform:
          type: string
          title: Platform
        sample_size:
          type: integer
          minimum: 0
          title: Sample Size
          default: 0
        average_rating:
          anyOf:
            - type: number
            - type: 'null'
          title: Average Rating
        answered:
          type: integer
          minimum: 0
          title: Answered
          default: 0
        last_collected_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Last Collected At
        at_depth_cap:
          type: boolean
          title: At Depth Cap
          default: false
        platform_reported_total:
          anyOf:
            - $ref: '#/components/schemas/BrandReviewPlatformReportedTotal'
            - type: 'null'
      type: object
      required:
        - platform
      title: BrandReviewPlatformSplit
    BrandReviewTrendPoint:
      properties:
        period:
          type: string
          format: date-time
          title: Period
        count:
          type: integer
          minimum: 0
          title: Count
          default: 0
        average_rating:
          anyOf:
            - type: number
            - type: 'null'
          title: Average Rating
        answered:
          type: integer
          minimum: 0
          title: Answered
          default: 0
      type: object
      required:
        - period
      title: BrandReviewTrendPoint
    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
    BrandReviewPlatformReportedTotal:
      properties:
        provider_rating:
          anyOf:
            - type: number
            - type: 'null'
          title: Provider Rating
        provider_votes_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Provider Votes Count
        listing_count:
          type: integer
          minimum: 0
          title: Listing Count
          default: 0
      type: object
      title: BrandReviewPlatformReportedTotal
      description: >-
        What the PLATFORM itself reports, as opposed to what we collected.


        Every other number on this contract is measured over our own
        depth-capped

        sample (see ``BrandReviewMetricsResponse.sample_is_platform_total``,
        pinned

        False). This object is the one exception, and it is deliberately a
        separate

        nested field rather than extra keys alongside the sample figures so the
        two

        can never be read as the same class of number.


        Source:
        ``brand_review_sources.raw_data.location_sample.{provider_rating,

        provider_votes_count}``, written by the Google Places discovery step

        (``review_acquisition._location_sample``). Zero new collection — this is

        pure serialization of a value that has been stored all along.


        **Google only, by construction.** Trustpilot's ``task_post`` payload
        carries

        no location sample at all, so a Trustpilot split's

        ``platform_reported_total`` is ``None`` — the whole object is absent,
        never

        an object full of zeros. A missing platform total and a platform total
        of

        zero are different statements; only the first is true here.


        ``listing_count`` is how many tracked listings on this platform reported
        a

        total. A Google "place" is a physical business, so one advertiser can
        hold

        several (MECCA AU and MECCA US are different listings with different
        review

        sets). When more than one reported, ``provider_rating`` and

        ``provider_votes_count`` are both ``None``: averaging two places'
        platform

        ratings, or summing their vote counts, produces a figure no platform
        ever

        reported, which is exactly the class of confident lie this module
        refuses

        to emit. ``listing_count`` still travels so a reader can say "2
        listings"

        instead of silently showing nothing.
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````