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

# Get Audit Competitor Benchmark Route

> Benchmark this brand's audited position against the competitors IT named.

R17 of the audits UX plan — the differentiator a generic auditor cannot
copy, because it holds no competitor set for you. Pure read of
already-persisted competitor acquisition output: **no provider spend, no
refresh triggered**.

Gated on the acquisition surface that WRITES the rows being read — the
competitor-dashboard master plus the module sub-flag for the basis this
audit type uses. With acquisition dark, the tables are not being populated
and the only honest answer is that the capability is unavailable (404),
not an empty cohort that reads like the user has no competitors.

An unsupported audit type (Website — no competitor is ever fetched or
scored, so nothing shares its measurement basis) returns 200 with
``available=False`` and a reason. That is a real answer about the data, not
a missing endpoint.



## OpenAPI

````yaml /openapi.json get /api/v1/free-audits/benchmark/{brand_id}
openapi: 3.1.0
info:
  title: AITasker
  version: 0.1.0
servers: []
security: []
paths:
  /api/v1/free-audits/benchmark/{brand_id}:
    get:
      tags:
        - Free Audits
        - Free Audits
      summary: Get Audit Competitor Benchmark Route
      description: >-
        Benchmark this brand's audited position against the competitors IT
        named.


        R17 of the audits UX plan — the differentiator a generic auditor cannot

        copy, because it holds no competitor set for you. Pure read of

        already-persisted competitor acquisition output: **no provider spend, no

        refresh triggered**.


        Gated on the acquisition surface that WRITES the rows being read — the

        competitor-dashboard master plus the module sub-flag for the basis this

        audit type uses. With acquisition dark, the tables are not being
        populated

        and the only honest answer is that the capability is unavailable (404),

        not an empty cohort that reads like the user has no competitors.


        An unsupported audit type (Website — no competitor is ever fetched or

        scored, so nothing shares its measurement basis) returns 200 with

        ``available=False`` and a reason. That is a real answer about the data,
        not

        a missing endpoint.
      operationId: >-
        get_audit_competitor_benchmark_route_api_v1_free_audits_benchmark__brand_id__get
      parameters:
        - name: brand_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Brand Id
        - name: audit_type
          in: query
          required: true
          schema:
            type: string
            maxLength: 50
            title: Audit Type
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditCompetitorBenchmarkResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - HTTPBearer: []
components:
  schemas:
    AuditCompetitorBenchmarkResponse:
      properties:
        audit_type:
          type: string
          title: Audit Type
        available:
          type: boolean
          title: Available
        reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Reason
        basis:
          anyOf:
            - type: string
            - type: 'null'
          title: Basis
        basis_label:
          anyOf:
            - type: string
            - type: 'null'
          title: Basis Label
        brand_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Brand Domain
        coverage:
          anyOf:
            - $ref: '#/components/schemas/ModuleCoverageStrip'
            - type: 'null'
        measured_competitors:
          type: integer
          title: Measured Competitors
          default: 0
        min_measured_competitors:
          type: integer
          title: Min Measured Competitors
          default: 0
        dimensions:
          items:
            $ref: '#/components/schemas/ModuleDimensionRank'
          type: array
          title: Dimensions
        authority_dimensions:
          items:
            $ref: '#/components/schemas/ModuleDimensionRank'
          type: array
          title: Authority Dimensions
        authority_not_measured_reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Authority Not Measured Reason
        keyword_gaps:
          anyOf:
            - $ref: '#/components/schemas/SeoGapsPanel'
            - type: 'null'
        keyword_weaknesses:
          anyOf:
            - $ref: '#/components/schemas/SeoWeaknessesPanel'
            - type: 'null'
        table_stakes:
          anyOf:
            - $ref: '#/components/schemas/SeoTableStakesPanel'
            - type: 'null'
        content_type_mix:
          anyOf:
            - $ref: '#/components/schemas/SeoContentTypeMixPanel'
            - type: 'null'
        keyword_intent_mix:
          anyOf:
            - $ref: '#/components/schemas/SeoKeywordIntentMixPanel'
            - type: 'null'
        cohort_domains:
          items:
            type: string
          type: array
          title: Cohort Domains
        measured_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Measured At
      type: object
      required:
        - audit_type
        - available
      title: AuditCompetitorBenchmarkResponse
      description: >-
        The audited brand's standing against the competitors IT named (R17).


        ``available=False`` is a first-class, renderable answer — ``reason``
        names

        which one so the report can offer the matching next step instead of a

        blank panel. It is NEVER an error: a brand with no competitors yet, or a

        Website audit (which has no same-basis competitor measure at all), is a

        normal state.


        These are MEASURED dimensions on an identical basis for both sides, not

        audit scores — no competitor is ever audited, so no competitor has a

        dimension score to average. See

        ``services/audit_competitor_benchmark.py`` for why.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    ModuleCoverageStrip:
      properties:
        targets_total:
          type: integer
          title: Targets Total
          default: 0
        measured:
          type: integer
          title: Measured
          default: 0
        missing:
          items:
            type: string
          type: array
          title: Missing
        fetch_failed:
          items:
            type: string
          type: array
          title: Fetch Failed
        persist_failed:
          items:
            type: string
          type: array
          title: Persist Failed
        skipped_cost:
          items:
            type: string
          type: array
          title: Skipped Cost
        last_run_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Last Run At
        brand_measured:
          type: boolean
          title: Brand Measured
          default: false
        brand_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Brand Domain
        brand_not_measured_reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Brand Not Measured Reason
        near_duplicate_pairs:
          items:
            $ref: '#/components/schemas/ModuleCoverageNearDuplicatePair'
          type: array
          title: Near Duplicate Pairs
        near_duplicate_scan_truncated:
          type: boolean
          title: Near Duplicate Scan Truncated
          default: false
        near_duplicate_scanned_count:
          type: integer
          title: Near Duplicate Scanned Count
          default: 0
      type: object
      title: ModuleCoverageStrip
      description: >-
        N-of-M acquisition coverage for one module's cohort (the ★S0/★C0 strip).


        ``measured`` counts cohort domains that produced a stored metric row.

        ``missing`` are targets that never did — their fetch or persist failed,
        or

        they have not been acquired yet. For Content, ``fetch_failed`` is a
        typed

        subset of ``missing``, not an additional shortfall, so clients must

        de-duplicate rather than sum those lengths. A competitor with a failed

        acquisition must render as a failure chip, never as a measured zero.
    ModuleDimensionRank:
      properties:
        dimension:
          type: string
          title: Dimension
        label:
          type: string
          title: Label
        higher_is_better:
          type: boolean
          title: Higher Is Better
          default: true
        brand_value:
          anyOf:
            - type: number
            - type: 'null'
          title: Brand Value
        brand_rank:
          anyOf:
            - type: integer
            - type: 'null'
          title: Brand Rank
        tied_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Tied Count
        measured_count:
          type: integer
          title: Measured Count
          default: 0
        cohort_total:
          type: integer
          title: Cohort Total
          default: 0
        best_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Best Domain
        best_tied_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Best Tied Count
        best_value:
          anyOf:
            - type: number
            - type: 'null'
          title: Best Value
        median_value:
          anyOf:
            - type: number
            - type: 'null'
          title: Median Value
        not_measured_reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Not Measured Reason
      type: object
      required:
        - dimension
        - label
      title: ModuleDimensionRank
      description: >-
        The user brand's standing on ONE measured dimension (★S1 / ★C1).


        ``brand_rank`` is 1-based over the domains that carry a value on this

        dimension — ``measured_count``. It is ``None`` when the brand itself is
        not

        measured on it, which is different from ranking last.


        It is a COMPETITION rank ("1224"): only strictly-better domains push the

        brand down, so tied domains share a place. ``tied_count`` is how many

        measured domains hold the brand's exact value, INCLUDING the brand — so
        it

        is 1 when the brand stands alone. Rank alone cannot carry a "you lead"

        claim (a brand tied at the top ranks 1, and so does a brand tied at zero

        with a cohort that measured zero); check ``tied_count == 1`` for that.


        ``best_tied_count`` is the same disclosure for ``best_domain``, which is

        ONE name chosen alphabetically out of the leaders. It counts inclusively

        too, so 1 means the named leader stands alone and a UI may safely name
        it.

        Both are ``None`` — never 0 — when there is nothing measured to count.
    SeoGapsPanel:
      properties:
        keywords:
          items:
            $ref: '#/components/schemas/SeoGapKeyword'
          type: array
          title: Keywords
        measured_domains:
          type: integer
          title: Measured Domains
          default: 0
        max_gap_population:
          anyOf:
            - type: integer
            - type: 'null'
          title: Max Gap Population
        semantics:
          type: string
          title: Semantics
          default: ''
        measured:
          type: boolean
          title: Measured
          default: false
      type: object
      title: SeoGapsPanel
      description: >-
        ★S9 — the cohort ranks, you don't.


        Rows come from DataForSEO's own server-side exclusion query

        (``domain_intersection`` with ``intersections: false``), so a keyword
        the

        brand ranks for at position 240 is not reported as a gap. ``semantics``

        names that contract; an empty string means the stored snapshot predates
        it.
    SeoWeaknessesPanel:
      properties:
        keywords:
          items:
            $ref: '#/components/schemas/SeoWeaknessKeyword'
          type: array
          title: Keywords
        competitors_measured:
          type: integer
          title: Competitors Measured
          default: 0
        shared_keywords_evaluated:
          type: integer
          title: Shared Keywords Evaluated
          default: 0
        max_competitor_position:
          type: integer
          title: Max Competitor Position
          default: 0
        min_position_delta:
          type: integer
          title: Min Position Delta
          default: 0
        measured:
          type: boolean
          title: Measured
          default: false
      type: object
      title: SeoWeaknessesPanel
      description: |-
        ★S10 — shared keywords where a competitor MATERIALLY outranks the brand.

        The thresholds are the free SEO audit's, imported rather than copied, so
        BI and the audit can never disagree on what "materially outranks" means.
    SeoTableStakesPanel:
      properties:
        cohort_domains_measured:
          type: integer
          title: Cohort Domains Measured
          default: 0
        threshold_domains:
          type: integer
          title: Threshold Domains
          default: 0
        keywords:
          items:
            $ref: '#/components/schemas/SeoTableStakesKeyword'
          type: array
          title: Keywords
        brand_missing_count:
          type: integer
          title: Brand Missing Count
          default: 0
        measured:
          type: boolean
          title: Measured
          default: false
        basis_notice:
          type: string
          title: Basis Notice
          default: >-
            Computed over the stored keyword samples for each competitor, not
            the full domain-wide keyword population.
      type: object
      title: SeoTableStakesPanel
      description: '★S3 — keywords the WHOLE measured cohort ranks for: the must-haves.'
    SeoContentTypeMixPanel:
      properties:
        rows:
          items:
            $ref: '#/components/schemas/SeoContentTypeMixRow'
          type: array
          title: Rows
        cohort_urls_classified:
          type: integer
          title: Cohort Urls Classified
          default: 0
        brand_urls_classified:
          type: integer
          title: Brand Urls Classified
          default: 0
        measured:
          type: boolean
          title: Measured
          default: false
        basis_notice:
          type: string
          title: Basis Notice
          default: >-
            Computed over the stored keyword samples for each competitor, not
            the full domain-wide keyword population.
      type: object
      title: SeoContentTypeMixPanel
      description: ★S6 — what KIND of page wins in this niche (classified ranking URLs).
    SeoKeywordIntentMixPanel:
      properties:
        rows:
          items:
            $ref: '#/components/schemas/SeoKeywordIntentMixRow'
          type: array
          title: Rows
        keywords_sampled:
          type: integer
          title: Keywords Sampled
          default: 0
        keywords_classified:
          type: integer
          title: Keywords Classified
          default: 0
        keywords_unclassified:
          type: integer
          title: Keywords Unclassified
          default: 0
        domains_measured:
          type: integer
          title: Domains Measured
          default: 0
        domains_not_measured:
          items:
            type: string
          type: array
          title: Domains Not Measured
        measured:
          type: boolean
          title: Measured
          default: false
        basis_notice:
          type: string
          title: Basis Notice
          default: >-
            Computed over the stored keyword samples for each competitor, not
            the full domain-wide keyword population.
      type: object
      title: SeoKeywordIntentMixPanel
      description: >-
        ★S5 — classified intent mix over the stored cohort keyword samples.


        ``keywords_unclassified`` is a separate denominator: missing labels are

        not folded into a bucket and domains whose classification call failed
        are

        named under ``domains_not_measured`` rather than represented as zeros.
    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
    ModuleCoverageNearDuplicatePair:
      properties:
        domain_a:
          type: string
          title: Domain A
        domain_b:
          type: string
          title: Domain B
        edit_distance:
          type: integer
          title: Edit Distance
      type: object
      required:
        - domain_a
        - domain_b
        - edit_distance
      title: ModuleCoverageNearDuplicatePair
      description: |-
        One "these might be the same brand" hint (audit Tier 0.3).

        Two cohort domains within ``edit_distance`` of each other on their
        REGISTRABLE (eTLD+1) form — e.g. ``acme.com``/``acm3.com`` (a typo), or
        ``blog.acme.com``/``shop.acme.com`` (two subdomains of the same apex,
        both normalizing to ``acme.com``, distance 0). This is what closed the
        ``warbybarker.com`` vs ``warbyparker.com`` gap the audit's D5 finding
        named (distance 1, exact-string dedup never caught it).

        **Advisory only.** The audit's own phrasing is "surface, don't
        auto-merge" — no code path may read this field to combine, drop, or
        otherwise alter which domains belong to a cohort. It exists purely so a
        coverage strip can ask the operator/user "are these two the same
        brand?" instead of silently double-counting or silently guessing.
    SeoGapKeyword:
      properties:
        keyword:
          type: string
          title: Keyword
        competitor_domains:
          type: integer
          title: Competitor Domains
          default: 0
        best_position:
          anyOf:
            - type: integer
            - type: 'null'
          title: Best Position
        search_volume:
          anyOf:
            - type: integer
            - type: 'null'
          title: Search Volume
        cpc:
          anyOf:
            - type: number
            - type: 'null'
          title: Cpc
        example_domain:
          anyOf:
            - type: string
            - type: 'null'
          title: Example Domain
      type: object
      required:
        - keyword
      title: SeoGapKeyword
    SeoWeaknessKeyword:
      properties:
        keyword:
          type: string
          title: Keyword
        competitor_domain:
          type: string
          title: Competitor Domain
        competitor_position:
          anyOf:
            - type: integer
            - type: 'null'
          title: Competitor Position
        brand_position:
          anyOf:
            - type: integer
            - type: 'null'
          title: Brand Position
        position_delta:
          anyOf:
            - type: integer
            - type: 'null'
          title: Position Delta
        search_volume:
          anyOf:
            - type: integer
            - type: 'null'
          title: Search Volume
      type: object
      required:
        - keyword
        - competitor_domain
      title: SeoWeaknessKeyword
    SeoTableStakesKeyword:
      properties:
        keyword:
          type: string
          title: Keyword
        cohort_domains:
          type: integer
          title: Cohort Domains
          default: 0
        search_volume:
          anyOf:
            - type: integer
            - type: 'null'
          title: Search Volume
        brand_ranks:
          type: boolean
          title: Brand Ranks
          default: false
        brand_position:
          anyOf:
            - type: integer
            - type: 'null'
          title: Brand Position
      type: object
      required:
        - keyword
      title: SeoTableStakesKeyword
    SeoContentTypeMixRow:
      properties:
        content_type:
          type: string
          title: Content Type
        cohort_count:
          type: integer
          title: Cohort Count
          default: 0
        cohort_share_pct:
          anyOf:
            - type: number
            - type: 'null'
          title: Cohort Share Pct
        brand_count:
          type: integer
          title: Brand Count
          default: 0
        brand_share_pct:
          anyOf:
            - type: number
            - type: 'null'
          title: Brand Share Pct
      type: object
      required:
        - content_type
      title: SeoContentTypeMixRow
    SeoKeywordIntentMixRow:
      properties:
        intent:
          type: string
          enum:
            - informational
            - navigational
            - commercial
            - transactional
          title: Intent
        keyword_count:
          type: integer
          title: Keyword Count
          default: 0
        share_pct:
          anyOf:
            - type: number
            - type: 'null'
          title: Share Pct
      type: object
      required:
        - intent
      title: SeoKeywordIntentMixRow
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````