> ## 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 Competitor Seo Domain Detail

> One domain's whole organic-search record, for a caller that knows only
the domain.

This is the read behind Brand Details' SEO tab, which is reached from
Explore Brands by domain and has no roster context. It deliberately does
NOT require the domain to be one of ``brand_id``'s tracked competitors:
organic keywords, traffic and authority are properties of the domain, so
requiring roster membership before reporting them refuses data the
platform holds. ``seo_svc.get_domain_seo_measurement`` prefers this
workspace's own captures and falls back to any workspace's, reporting
which in ``basis`` — and the workspace-relative keyword gap is served only
on the ``this_workspace`` branch (see that function and
``CompetitorSeoDomainDetailResponse`` for why).

The ONE identity it does refuse is a domain on the operator blocklist —
see the gate below for why that is not a contradiction of the paragraph
above.

Scoped to the READING BRAND's market on every branch, and ``market`` says
which. Roster context is optional here; market context is not — organic
numbers only mean something once a ``(location_code, language_code)`` is
fixed, so a domain measured in another market reads ``unmeasured`` rather
than lending its numbers to this one.

Read-only over data acquisition already bought: it never enqueues a
refresh and never spends a vendor call, so opening the tab costs nothing.



## OpenAPI

````yaml /openapi.json get /api/v1/brands/{brand_id}/dashboards/competitor-seo/domain-detail
openapi: 3.1.0
info:
  title: AITasker
  version: 0.1.0
servers: []
security: []
paths:
  /api/v1/brands/{brand_id}/dashboards/competitor-seo/domain-detail:
    get:
      tags:
        - Dashboards
        - Dashboards
      summary: Get Competitor Seo Domain Detail
      description: >-
        One domain's whole organic-search record, for a caller that knows only

        the domain.


        This is the read behind Brand Details' SEO tab, which is reached from

        Explore Brands by domain and has no roster context. It deliberately does

        NOT require the domain to be one of ``brand_id``'s tracked competitors:

        organic keywords, traffic and authority are properties of the domain, so

        requiring roster membership before reporting them refuses data the

        platform holds. ``seo_svc.get_domain_seo_measurement`` prefers this

        workspace's own captures and falls back to any workspace's, reporting

        which in ``basis`` — and the workspace-relative keyword gap is served
        only

        on the ``this_workspace`` branch (see that function and

        ``CompetitorSeoDomainDetailResponse`` for why).


        The ONE identity it does refuse is a domain on the operator blocklist —

        see the gate below for why that is not a contradiction of the paragraph

        above.


        Scoped to the READING BRAND's market on every branch, and ``market``
        says

        which. Roster context is optional here; market context is not — organic

        numbers only mean something once a ``(location_code, language_code)`` is

        fixed, so a domain measured in another market reads ``unmeasured``
        rather

        than lending its numbers to this one.


        Read-only over data acquisition already bought: it never enqueues a

        refresh and never spends a vendor call, so opening the tab costs
        nothing.
      operationId: >-
        get_competitor_seo_domain_detail_api_v1_brands__brand_id__dashboards_competitor_seo_domain_detail_get
      parameters:
        - name: brand_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
            title: Brand Id
        - name: competitor
          in: query
          required: true
          schema:
            type: string
            minLength: 1
            description: competitor domain
            title: Competitor
          description: competitor domain
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompetitorSeoDomainDetailResponse'
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - HTTPBearer: []
components:
  schemas:
    CompetitorSeoDomainDetailResponse:
      properties:
        competitor_domain:
          type: string
          title: Competitor Domain
        basis:
          type: string
          enum:
            - this_workspace
            - platform
            - unmeasured
          title: Basis
        market:
          $ref: '#/components/schemas/SeoMarketDescriptor'
        authority_metrics_enabled:
          type: boolean
          title: Authority Metrics Enabled
          default: false
        metric:
          anyOf:
            - $ref: '#/components/schemas/CompetitorSeoMetricRow'
            - type: 'null'
        keywords:
          items:
            $ref: '#/components/schemas/SeoKeywordRow'
          type: array
          title: Keywords
        traffic_series:
          anyOf:
            - $ref: '#/components/schemas/CompetitorSeoTrafficSeries'
            - type: 'null'
        gap_available:
          type: boolean
          title: Gap Available
          default: false
        shared_keywords:
          items:
            additionalProperties: true
            type: object
          type: array
          title: Shared Keywords
        gap_keywords:
          items:
            additionalProperties: true
            type: object
          type: array
          title: Gap Keywords
        keyword_gap_sample_size:
          anyOf:
            - type: integer
            - type: 'null'
          title: Keyword Gap Sample Size
        gap_semantics:
          type: string
          title: Gap Semantics
          default: ''
        capture_count:
          type: integer
          title: Capture Count
          default: 0
      type: object
      required:
        - competitor_domain
        - basis
        - market
      title: CompetitorSeoDomainDetailResponse
      description: >-
        One domain's whole organic-search record, for a caller that knows only

        the domain.


        Organic visibility is a property of a DOMAIN, so this response is keyed
        by

        the requested domain rather than by the reading brand's competitor
        roster:

        a domain the platform has measured for anyone reports real numbers here.

        ``basis`` says whose acquisition paid for them and therefore what is in

        the payload:


        - ``this_workspace`` — the reading brand's own captures. The
          ``keyword_gap*`` family is populated, because "ranks where the brand is
          absent" is measured against THIS brand's own website.
        - ``platform`` — measured by another workspace. Only domain-intrinsic
          facts are carried; ``gap_keywords`` / ``shared_keywords`` /
          ``keyword_gap_count`` stay empty and ``gap_available`` is ``False``,
          because a foreign workspace's gap is measured against ITS website and is
          not this reader's to see.
        - ``unmeasured`` — nobody has measured this domain. ``metric`` is null
        and
          every collection is empty. **Render that as "not measured", never as
          zero** (ADR 0026): every numeric here is nullable and null means NOT
          MEASURED.
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    SeoMarketDescriptor:
      properties:
        location_code:
          type: integer
          title: Location Code
        language_code:
          type: string
          title: Language Code
        country_code:
          anyOf:
            - type: string
            - type: 'null'
          title: Country Code
        country_name:
          anyOf:
            - type: string
            - type: 'null'
          title: Country Name
        label:
          type: string
          title: Label
      type: object
      required:
        - location_code
        - language_code
        - label
      title: SeoMarketDescriptor
      description: >-
        WHICH market an organic-search series describes.


        Organic rank, traffic and keyword counts are measured per

        ``(location_code, language_code)``: the same domain measured in the US
        and

        in the UK produces two unrelated numbers. Every SEO reader here is
        scoped

        to exactly one market (see ``get_traffic_series`` /

        ``get_historical_traffic_series`` / ``get_domain_seo_measurement``), and
        a

        brand can change its market at any time from the market modal — so a

        series that does not NAME its market silently changes basis under the

        reader, who has no way to tell that the axis moved.


        Carried alongside ``basis`` and for the same reason: state what was

        measured rather than render a plausible number. ``basis`` names WHOSE

        acquisition paid; this names WHAT MARKET it bought.


        ``country_code`` / ``country_name`` are null for a ``location_code``

        outside ``brand_market_service.MARKET_COUNTRIES`` — a market can be

        persisted (or configured as the fallback default) without appearing in
        the

        picker's catalogue. ``label`` is always populated and always renderable:

        it degrades to the bare code rather than to an empty string, because the

        whole point of this object is that the reader is never left guessing.
    CompetitorSeoMetricRow:
      properties:
        competitor_domain:
          type: string
          title: Competitor Domain
        is_brand:
          type: boolean
          title: Is Brand
          default: false
        organic_keyword_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Organic Keyword Count
        keyword_sample_size:
          anyOf:
            - type: integer
            - type: 'null'
          title: Keyword Sample Size
        est_organic_traffic:
          anyOf:
            - type: number
            - type: 'null'
          title: Est Organic Traffic
        est_traffic_value_usd:
          anyOf:
            - type: number
            - type: 'null'
          title: Est Traffic Value Usd
        domain_rank:
          anyOf:
            - type: number
            - type: 'null'
          title: Domain Rank
        referring_domains:
          anyOf:
            - type: integer
            - type: 'null'
          title: Referring Domains
        backlinks:
          anyOf:
            - type: integer
            - type: 'null'
          title: Backlinks
        keyword_gap_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Keyword Gap Count
        keyword_gap_sample_size:
          anyOf:
            - type: integer
            - type: 'null'
          title: Keyword Gap Sample Size
        captured_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Captured At
        previous_captured_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
          title: Previous Captured At
        previous_est_organic_traffic:
          anyOf:
            - type: number
            - type: 'null'
          title: Previous Est Organic Traffic
        previous_organic_keyword_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Previous Organic Keyword Count
        previous_keyword_gap_count:
          anyOf:
            - type: integer
            - type: 'null'
          title: Previous Keyword Gap Count
        previous_counts_withheld:
          type: boolean
          title: Previous Counts Withheld
          default: false
        previous_organic_keyword_count_withheld:
          type: boolean
          title: Previous Organic Keyword Count Withheld
          default: false
        previous_keyword_gap_count_withheld:
          type: boolean
          title: Previous Keyword Gap Count Withheld
          default: false
      type: object
      required:
        - competitor_domain
      title: CompetitorSeoMetricRow
      description: >-
        One competitor's organic row.


        **Every numeric here is nullable and null means NOT MEASURED**, never
        zero.

        A DataForSEO read that failed persists NULL so the UI renders its

        NOT_MEASURED em-dash instead of an authoritative-looking 0.


        ``organic_keyword_count`` / ``keyword_gap_count`` are DOMAIN-WIDE

        populations. ``keyword_sample_size`` / ``keyword_gap_sample_size`` are
        how

        many rows the Keywords and Gap panels actually hold — so the UI can
        label

        them "top N of M" rather than presenting a capped API page as the count.
    SeoKeywordRow:
      properties:
        keyword:
          type: string
          title: Keyword
        position:
          anyOf:
            - type: integer
            - type: 'null'
          title: Position
        search_volume:
          anyOf:
            - type: integer
            - type: 'null'
          title: Search Volume
        keyword_difficulty:
          anyOf:
            - type: number
            - type: 'null'
          title: Keyword Difficulty
        etv:
          anyOf:
            - type: number
            - type: 'null'
          title: Etv
        url:
          anyOf:
            - type: string
            - type: 'null'
          title: Url
        rank_changes:
          anyOf:
            - $ref: '#/components/schemas/SeoKeywordRankChange'
            - type: 'null'
        monthly_searches:
          items:
            $ref: '#/components/schemas/SeoMonthlySearch'
          type: array
          title: Monthly Searches
      type: object
      required:
        - keyword
      title: SeoKeywordRow
    CompetitorSeoTrafficSeries:
      properties:
        competitor_domain:
          type: string
          title: Competitor Domain
        points:
          items:
            $ref: '#/components/schemas/SeoTrafficSeriesPoint'
          type: array
          title: Points
      type: object
      required:
        - competitor_domain
        - points
      title: CompetitorSeoTrafficSeries
    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
    SeoKeywordRankChange:
      properties:
        is_up:
          type: boolean
          title: Is Up
          default: false
        is_down:
          type: boolean
          title: Is Down
          default: false
        is_new:
          type: boolean
          title: Is New
          default: false
        previous_rank:
          anyOf:
            - type: integer
            - type: 'null'
          title: Previous Rank
      type: object
      title: SeoKeywordRankChange
      description: >-
        Movement vs the previous DataForSEO observation of this keyword.


        Mirrors ``seo_data_service._ranked_keyword_rank_changes`` — absence of
        the

        whole object means the provider returned no movement block ("no movement

        data"), which must stay distinct from "no movement".
    SeoMonthlySearch:
      properties:
        year:
          type: integer
          title: Year
        month:
          type: integer
          maximum: 12
          minimum: 1
          title: Month
        search_volume:
          anyOf:
            - type: integer
            - type: 'null'
          title: Search Volume
      type: object
      required:
        - year
        - month
      title: SeoMonthlySearch
      description: One month of demand for a keyword (trailing ~12 months per row).
    SeoTrafficSeriesPoint:
      properties:
        captured_at:
          type: string
          format: date-time
          title: Captured At
        est_organic_traffic:
          anyOf:
            - type: number
            - type: 'null'
          title: Est Organic Traffic
        source:
          type: string
          enum:
            - historical
            - live
          title: Source
          default: live
      type: object
      required:
        - captured_at
      title: SeoTrafficSeriesPoint
  securitySchemes:
    HTTPBearer:
      type: http
      scheme: bearer

````