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

# Get share of voice

> Share of voice over a window.

The own brand and its competitors. The field's MEMBERSHIP and its sums come from one database statement, so the numerator and every competitor in its denominator are one snapshot. EVERY MEMBER IS MEASURED IN THIS FIELD'S SCOPE, FROM ONE SOURCE: `mentions` / `visibility` / `days` are counted on THIS own brand's completed, non-preview runs for every entry, the own brand's own included, never off a score row — those carry no record of which own brand it was scored for and keep whichever (own brand, candidate) pairing scored them last, which reaches an own brand too, since a competitor link is a link between BRANDS. `sov` = Σ this entry's `mentions` ÷ Σ over every entry; `visibility` = mean daily presence over `days`; `ais_score` = mean AIS Score over the PERSISTED rows that carry one AND belong to BOTH generations the envelope pins — its `score_version` (the formula that produced the point) and its `panel_version` (the model set that measured it), which are ONE persisted, SCORED point's pair (among the rows carrying an AIS and belonging to this field: latest date, then the highest panel generation, then the highest scoring generation) rather than the newest of each picked separately — a point of a known DIFFERENT generation, of either kind, is excluded from that mean only, never from `mentions`, `visibility`, `sov` or `days`. Null where nothing is persisted, and null for an entry that is ITSELF an own brand: no rival-field AIS Score is recorded for one, so this field has none for it and its rows cannot pin this field's generation — its mentions, visibility and days are this field's all the same. A field wider than 1000 members (the brand plus its tracked competitors) cannot be measured in one snapshot and answers 500 `field_too_large` rather than a share computed against a truncated denominator. Own brands only.



## OpenAPI

````yaml /api-reference/openapi.json get /orgs/{orgId}/brands/{brandId}/share-of-voice
openapi: 3.1.0
info:
  title: Surfais API
  version: 1.0.0
  description: >-
    The Surfais API gives programmatic access to the AI-visibility data Surfais
    measures for your brands: organisations, brands and competitors, tracked
    prompts, per-run results and mentions, scores, share of voice and cited
    sources. It can also provision brands, manage prompts and competitors,
    request on-demand scans, and deliver signed webhooks when a scan completes
    or a score or sentiment threshold is crossed.


    Base URL: `https://api.surfais.com/v1`. Send your API key as a Bearer token
    (`Authorization: Bearer sfs_live_…`). Test keys (`sfs_test_…`) are rejected
    by the production API. Requests and responses are JSON: a success is `{
    "data": … }`, an error is `{ "error": { "code", "message" }, "request_id"
    }`.


    Guides for authentication, pagination, errors, rate limits, idempotency and
    webhooks are at https://docs.surfais.com/api/introduction.
  contact:
    name: Surfais support
    email: support@surfais.com
    url: https://docs.surfais.com/api/support
servers:
  - url: https://api.surfais.com/v1
    description: >-
      Production (the `/v1` prefix is part of the server URL; paths below are
      relative to it).
security:
  - bearerKey: []
tags:
  - name: orgs
    description: Organisations reachable by the key, their usage, and the partner link.
  - name: brands
    description: Own brands (properties) and competitor rows.
  - name: prompts
    description: 'Tracked prompts: reads, CRUD and the declarative sync.'
  - name: results
    description: Finalised run-level data.
  - name: scores
    description: Persisted scores, per-market history and share of voice.
  - name: sources
    description: Cited domains.
  - name: scans
    description: On-demand scan requests.
  - name: webhooks
    description: >-
      Outbound event delivery: endpoints, subscriptions, deliveries (partner
      keys).
externalDocs:
  description: Guides, concepts and webhooks
  url: https://docs.surfais.com/api/introduction
paths:
  /orgs/{orgId}/brands/{brandId}/share-of-voice:
    get:
      tags:
        - scores
      summary: Get share of voice
      description: >-
        Share of voice over a window.


        The own brand and its competitors. The field's MEMBERSHIP and its sums
        come from one database statement, so the numerator and every competitor
        in its denominator are one snapshot. EVERY MEMBER IS MEASURED IN THIS
        FIELD'S SCOPE, FROM ONE SOURCE: `mentions` / `visibility` / `days` are
        counted on THIS own brand's completed, non-preview runs for every entry,
        the own brand's own included, never off a score row — those carry no
        record of which own brand it was scored for and keep whichever (own
        brand, candidate) pairing scored them last, which reaches an own brand
        too, since a competitor link is a link between BRANDS. `sov` = Σ this
        entry's `mentions` ÷ Σ over every entry; `visibility` = mean daily
        presence over `days`; `ais_score` = mean AIS Score over the PERSISTED
        rows that carry one AND belong to BOTH generations the envelope pins —
        its `score_version` (the formula that produced the point) and its
        `panel_version` (the model set that measured it), which are ONE
        persisted, SCORED point's pair (among the rows carrying an AIS and
        belonging to this field: latest date, then the highest panel generation,
        then the highest scoring generation) rather than the newest of each
        picked separately — a point of a known DIFFERENT generation, of either
        kind, is excluded from that mean only, never from `mentions`,
        `visibility`, `sov` or `days`. Null where nothing is persisted, and null
        for an entry that is ITSELF an own brand: no rival-field AIS Score is
        recorded for one, so this field has none for it and its rows cannot pin
        this field's generation — its mentions, visibility and days are this
        field's all the same. A field wider than 1000 members (the brand plus
        its tracked competitors) cannot be measured in one snapshot and answers
        500 `field_too_large` rather than a share computed against a truncated
        denominator. Own brands only.
      operationId: getBrandShareOfVoice
      parameters:
        - in: path
          name: orgId
          schema:
            type: string
            format: uuid
            pattern: >-
              ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
            description: >-
              Organisation id. Org keys: the key's own org. Partner keys: any
              org with an active link. Anything else is 404.
          required: true
          description: >-
            Organisation id. Org keys: the key's own org. Partner keys: any org
            with an active link. Anything else is 404.
        - in: path
          name: brandId
          schema:
            type: string
            format: uuid
            pattern: >-
              ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
            description: >-
              Brand id within the org (own brand or competitor row); 404 when
              not in the org. Writes that add tracking need an own (400
              `validation_error` with `details[].code = "own_brand_only"`),
              non-archived (409 `brand_archived`) brand.
          required: true
          description: >-
            Brand id within the org (own brand or competitor row); 404 when not
            in the org. Writes that add tracking need an own (400
            `validation_error` with `details[].code = "own_brand_only"`),
            non-archived (409 `brand_archived`) brand.
        - in: query
          name: from
          schema:
            description: 'Window start (inclusive). Default: `to` minus 30 days.'
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
            format: date
          description: 'Window start (inclusive). Default: `to` minus 30 days.'
        - in: query
          name: to
          schema:
            description: >-
              Window end (inclusive). Default: today (UTC). Max span 366 days;
              `from` after `to` is 400 `validation_error`. On a paginated
              endpoint the resolved window is pinned by the first page and
              carried in the cursor — continue with the cursor alone; naming a
              different `from`/`to` on a continuation is 400 `invalid_cursor`
              (`details.reason = "window_changed"`).
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
            format: date
          description: >-
            Window end (inclusive). Default: today (UTC). Max span 366 days;
            `from` after `to` is 400 `validation_error`. On a paginated endpoint
            the resolved window is pinned by the first page and carried in the
            cursor — continue with the cursor alone; naming a different
            `from`/`to` on a continuation is 400 `invalid_cursor`
            (`details.reason = "window_changed"`).
      responses:
        '200':
          description: Share of voice.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/ShareOfVoice'
                required:
                  - data
        '400':
          description: >-
            `body_not_allowed` — a GET or HEAD that declared a body, refused by
            the intake guard BEFORE the parser, which is the only 400 a read
            operation reaches by that route — or `validation_error` (with
            `details`), `invalid_cursor`, `invalid_json` or `invalid_body`, the
            last two reachable only where a body is accepted at all. All are
            raised after authentication and the minute charge (the body is
            parsed only for an authenticated, admitted request), so every 400
            carries the `X-RateLimit-*` trio and is metered.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: '`invalid_api_key` — uniform for every authentication failure.'
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: >-
            `tier_not_entitled` — the org key's organisation left an
            API-entitled tier. The request is charged to the key's minute
            allowance (so it carries the `X-RateLimit-*` trio, and an exhausted
            allowance answers 429 first) and is metered.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: >-
            `not_found` — the org is not reachable by this key or a nested id is
            not in it. Raised after authentication and the minute charge, so it
            carries the `X-RateLimit-*` trio and is metered.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '413':
          description: >-
            `payload_too_large` — a JSON body over 1 MB. The body is parsed only
            for an authenticated, admitted request, so this carries the
            `X-RateLimit-*` trio and is metered.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            `rate_limited` (per-minute limit, or too many failed authentications
            from this address) or `quota_exceeded`.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            Retry-After:
              required: true
              description: Seconds to wait before retrying — present on every 429 and 503.
              schema:
                type: string
                description: >-
                  Seconds to wait before retrying — present on every 429 and
                  503.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: >-
            `field_too_large` — the brand's field (itself plus its tracked
            competitors) is wider than 1000 members and cannot be measured in
            one snapshot: reduce the tracked competitors, or read the brands
            individually through `/scores`. Or `internal_error` — quote
            `request_id`. Both are raised inside the route, after authentication
            and the minute charge, so this 500 carries the `X-RateLimit-*` trio
            and is metered.
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            X-RateLimit-Limit:
              required: true
              description: >-
                Requests allowed per minute for this key. On a 429
                `rate_limited` from the failed-authentication gate: failed
                attempts allowed per minute for the client address.
              schema:
                type: string
                description: >-
                  Requests allowed per minute for this key. On a 429
                  `rate_limited` from the failed-authentication gate: failed
                  attempts allowed per minute for the client address.
            X-RateLimit-Remaining:
              required: true
              description: >-
                Requests left in the current minute window (0 on the
                failed-authentication 429).
              schema:
                type: string
                description: >-
                  Requests left in the current minute window (0 on the
                  failed-authentication 429).
            X-RateLimit-Reset:
              required: true
              description: Unix seconds at which the current minute window ends.
              schema:
                type: string
                description: Unix seconds at which the current minute window ends.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: >-
            `api_unavailable` (switched off; `Retry-After` is 60),
            `rate_limit_unavailable` (fail-closed limiter — its store, or a
            saturated failed-authentication gate; before the minute charge, so
            no `X-RateLimit-*` trio), or `store_unavailable` (a tenant-store
            call behind an authenticated request exceeded 15000 ms — raised
            after the minute charge, so it is counted and carries the trio).
          headers:
            X-Request-Id:
              required: true
              description: >-
                Correlation id for this request; equals `request_id` in an error
                envelope.
              schema:
                type: string
                description: >-
                  Correlation id for this request; equals `request_id` in an
                  error envelope.
            Retry-After:
              required: true
              description: Seconds to wait before retrying — present on every 429 and 503.
              schema:
                type: string
                description: >-
                  Seconds to wait before retrying — present on every 429 and
                  503.
            X-RateLimit-Limit:
              description: >-
                Present when the failure occurred after the minute charge
                (inside a route); absent on a failure before the limiter.
                Requests allowed per minute for this key.
              schema:
                description: >-
                  Present when the failure occurred after the minute charge
                  (inside a route); absent on a failure before the limiter.
                  Requests allowed per minute for this key.
                type: string
            X-RateLimit-Remaining:
              description: >-
                Present when the failure occurred after the minute charge.
                Requests left in the current minute window.
              schema:
                description: >-
                  Present when the failure occurred after the minute charge.
                  Requests left in the current minute window.
                type: string
            X-RateLimit-Reset:
              description: >-
                Present when the failure occurred after the minute charge. Unix
                seconds at which the current minute window ends.
              schema:
                description: >-
                  Present when the failure occurred after the minute charge.
                  Unix seconds at which the current minute window ends.
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
components:
  schemas:
    ShareOfVoice:
      type: object
      properties:
        window:
          type: object
          properties:
            from:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
              description: Calendar date, `YYYY-MM-DD` (UTC).
              format: date
            to:
              type: string
              pattern: ^\d{4}-\d{2}-\d{2}$
              description: Calendar date, `YYYY-MM-DD` (UTC).
              format: date
          required:
            - from
            - to
        score_version:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Scoring model the `ais_score` means are pinned to. It and
            `panel_version` are ONE PERSISTED POINT'S pair, read off that one
            point together, so the envelope always names a generation pair some
            point in the window actually carries. The point is chosen from the
            rows that CARRY an AIS and BELONG TO THIS FIELD — a row with no
            score contributes nothing to the mean whose generation it would
            choose (the ordinary shape of a competitor shared by two own
            brands), and neither does an own brand tracked here as a rival,
            whose row is scored in its own field and is scanned on its own
            schedule — by: latest date; then the highest `panel_version` (by the
            generation's number, so v10 outranks v9); then the highest
            `score_version`; then the tags' text as a final tiebreak — the panel
            first because it is upstream of the formula, which scores the runs
            the panel produced. So the pinned pair always belongs to a scored
            point, and a window holding one reports at least one AIS day. Points
            whose generation is persisted and DIFFERENT are excluded from
            `ais_score` (never from `mentions`, `visibility`, `sov` or `days` —
            those are measurements, and dropping one brand's days while keeping
            another's would misstate the share). A point whose generation was
            never recorded is included and no version is claimed for it. Null
            when the pinned point records no scoring generation, in which case
            nothing is excluded on that side.
        panel_version:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Measurement panel (model set) the `ais_score` means are pinned to —
            the other half of `score_version`'s pair, off the same one point,
            and the key the pinned point is chosen by first. Points of a KNOWN
            different panel are excluded from `ais_score` and from nothing else.
            Both pins apply together — a point counts towards a mean only if it
            matches each one it carries — and because they come from one point,
            a partly-rolled-out window (one brand rescored, another re-measured)
            cannot produce a pin pair that excludes every real point. A panel
            rollout does not change the scoring formula, so a window that spans
            one holds points sharing a `score_version` whose scores are still
            incomparable. Null when the pinned point records no panel.
        entries:
          type: array
          items:
            $ref: '#/components/schemas/ShareOfVoiceEntry'
          description: >-
            The own brand first (`is_self: true`), then its competitors by sov
            descending.
      required:
        - window
        - score_version
        - panel_version
        - entries
    ErrorEnvelope:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Stable machine code — see the error list in the description.
            message:
              type: string
            details: {}
          required:
            - code
            - message
        request_id:
          type: string
          description: Echoed in the `X-Request-Id` header.
      required:
        - error
        - request_id
    ShareOfVoiceEntry:
      type: object
      properties:
        brand_id:
          type: string
          format: uuid
          pattern: >-
            ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
          description: UUID.
        name:
          type: string
        is_self:
          type: boolean
        sov:
          anyOf:
            - type: number
            - type: 'null'
          description: >-
            Share of voice over the window, % (0–100): Σ this brand's `mentions`
            ÷ Σ every entry's `mentions`. Null when the field has no mentions.
        visibility:
          anyOf:
            - type: number
            - type: 'null'
          description: >-
            Mean of this brand's per-day presence over `days` — the share of
            that day's measurements that named it; null with no days. One
            definition and one source for every entry, this own brand's runs
            (see `mentions`).
        ais_score:
          anyOf:
            - type: number
            - type: 'null'
          description: >-
            Mean AIS Score over the PERSISTED aggregate rows that carry one and
            belong to BOTH pinned generations on the envelope — its
            `score_version` and its `panel_version`; null with none. Unlike
            `mentions` / `visibility` / `days`, this is not re-counted per
            field: the AIS is a whole-field composite rather than a count, and a
            competitor tracked by several own brands has none persisted at all
            (one row cannot hold a per-field value), so its `ais_score` is null.
            Null too for an own brand tracked as ANOTHER own brand's competitor,
            and for a stronger reason: no rival-field AIS Score is recorded for
            such a member at all — its score comes from its OWN field pass,
            against ITS rivals — so this field has none for it and its rows
            cannot pin this field's generation either. Its `mentions`,
            `visibility` and `days` are unaffected: those were measured by this
            own brand's prompts. The one exposure a read cannot close runs the
            other way — an own brand reading ITS OWN field publishes its
            persisted row, which may hold a sibling-perspective write.
        mentions:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
          description: >-
            Mentions over the window, counted in THIS field's scope for EVERY
            member — the own brand included: the mentions on THIS own brand's
            completed, non-preview runs, never a persisted score row. That row
            has no record of which own brand it was scored for and a brand is
            scored once per (own brand, candidate) pairing, so it keeps
            whichever pairing wrote last — which reaches an OWN brand too, since
            a competitor link is a link between BRANDS and an agency may track
            one of its own brands as another's rival. Reading it put another own
            brand's prompt set into this field's numerator and into every
            member's denominator. Both halves of `sov` are therefore counted the
            same way, in one statement.
        days:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
          description: >-
            Days the brand was measured in this field's scope: the dates this
            own brand's prompts measured it, for every member including the own
            brand itself. The denominator of `visibility`, so it moves with it.
            It is NOT the denominator of `ais_score`, which counts persisted
            rows and can therefore differ in either direction.
      required:
        - brand_id
        - name
        - is_self
        - sov
        - visibility
        - ais_score
        - mentions
        - days
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      bearerFormat: sfs_live_… / sfs_test_…
      description: >-
        A Surfais API key — an organisation key or a partner key. Issued by
        Surfais; shown once. Rotate by creating a new key, then revoking the old
        one.

````