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

# Get scan status / result

> Poll a scan by id. Returns 200 with a payload in three cases: completed, preliminary (a mid-run snapshot with live geo_score/band — more useful than an empty acknowledgement), and failed (the terminal `PartnerScanFailedOutput`, `error` block included). 202 is reserved for a scan that is genuinely still running with zero observations, and carries `poll_after_ms`.

Stop polling when you receive `completed` or `failed`. Scans are only visible to the partner that created them.



## OpenAPI

````yaml https://www.getsurfaced.ai/api/partner/openapi.json get /api/partner/competitor-scan/{id}
openapi: 3.1.0
info:
  title: Surfaced Partner API
  version: 1.0.0
  description: >-
    Competitor Scan API for Surfaced partners. Start a scan of a company's
    AI-search visibility, poll for the result, and (optionally) receive signed
    webhooks via progressive delivery.


    ## Progressive delivery


    A scan can produce up to two payload generations, distinguished by
    `revision`:


    - **Preliminary** (`status: "preliminary"`, `revision: 1`) — a best-effort
    mid-run snapshot with a live-computed `geo_score`/`band` from whatever
    observations exist so far. Returned inline on the 202 POST response (and 200
    GET polls) once at least one provider has reported, and pushed once to your
    `callback_url` as `scan.preliminary`. There is no retry backstop for the
    preliminary push — it is opportunistic.

    - **Final** (`status: "completed"`) — guaranteed for every scan that
    produces a result. Delivered as `scan.completed` with `revision: 2` when a
    preliminary was delivered first, otherwise `revision: 1`.


    Deduplicate on `(scan_id, revision)`.


    ## Failed scans


    A scan that produces no result ends as `status: "failed"`. It is terminal:
    nothing about it will change. `error.code` says which way it failed —
    `all_providers_failed` (we queried every provider you asked for and every
    call errored) or `analysis_failed` (the scan died before any provider was
    queried).


    Every scan that reaches an end state gets exactly ONE terminal signal, so
    you never have to wait on a timeout to find out which happened:


    - **`scan.completed`** — a result exists. `data` is a `PartnerScanOutput`.

    - **`scan.failed`** — no result exists. `data` is a
    `PartnerScanFailedOutput`, and it is a DIFFERENT shape: `geo_score`, `band`,
    `competitors` and `score_confidence` are ABSENT (not null), and a required
    `error` block carries a stable `code` (e.g. `all_providers_failed`), a human
    `message`, and `providers_failed`.


    That omission is deliberate and worth designing around. A failed scan means
    *we could not reach the models*, which is a different statement from *this
    company has no AI visibility* — and a `null` score is far too easily
    coalesced into `0` and rendered as the second. If your integration turns a
    scan into copy, branch on the event name (or on the presence of
    `data.error`) before reading any result field.


    **Every channel carries the same failure payload.** If you poll instead of
    using webhooks, `GET /api/partner/competitor-scan/{id}` returns **200** with
    that identical `PartnerScanFailedOutput` — `error` block included — and so
    does the create endpoint when a scan fails inside its sync-wait window. You
    never need a `callback_url` to find out why a scan failed.


    So HTTP 200 on either endpoint means *a terminal payload is enclosed*, not
    *the scan succeeded*. Check `status` (or the presence of `error`) before
    reading any score field. 202 now means the scan is still running, and
    carries `poll_after_ms`. Keep the legacy rule too: an absent `poll_after_ms`
    means stop, which now only occurs on a degraded path where we know a scan is
    terminal but could not assemble its payload.


    A failed scan is not billed and does not consume your monthly allowance.


    ## Score confidence


    `score_confidence` reflects provider coverage behind `geo_score`: the full
    basic trio (chatgpt + gemini + perplexity) reporting = `high`; partial
    coverage degrades it to `reduced` or `low`.


    ## Sandbox


    Test keys (`gsk_test_...`) exercise the full request validation, then
    short-circuit to deterministic mock responses: no scan runs, nothing is
    persisted, no rate limit is consumed, and responses carry `sandbox: true`.
    Same input always yields the same output, so integration tests can assert
    exact values.
servers:
  - url: https://www.getsurfaced.ai
    description: Production
security:
  - partnerApiKey: []
tags:
  - name: Scans
    description: Start and poll competitor scans.
  - name: History
    description: Scan history and reconciliation.
  - name: Usage
    description: Monthly usage rollups, quota headroom, and billing.
paths:
  /api/partner/competitor-scan/{id}:
    get:
      tags:
        - Scans
      summary: Get scan status / result
      description: >-
        Poll a scan by id. Returns 200 with a payload in three cases: completed,
        preliminary (a mid-run snapshot with live geo_score/band — more useful
        than an empty acknowledgement), and failed (the terminal
        `PartnerScanFailedOutput`, `error` block included). 202 is reserved for
        a scan that is genuinely still running with zero observations, and
        carries `poll_after_ms`.


        Stop polling when you receive `completed` or `failed`. Scans are only
        visible to the partner that created them.
      operationId: getCompetitorScan
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The scan_id returned by the create endpoint.
      responses:
        '200':
          description: >-
            A scan payload — status "completed" (final), "preliminary" (mid-run
            snapshot; keep polling until completed) or "failed" (terminal, with
            a required `error` block; the SAME payload the `scan.failed` webhook
            carries). A 200 does not by itself mean the scan succeeded — check
            `status`, or the presence of `error`, before reading any score
            field. Test keys always return a completed mock echoing the
            requested id.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PartnerScanOutput'
                  - $ref: '#/components/schemas/PartnerScanFailedOutput'
              examples:
                completed:
                  value:
                    scan_id: 6f1d2c3b-4a5e-4f60-9b7c-8d9e0f1a2b3c
                    tier: basic
                    status: completed
                    company:
                      url: https://example.nl
                      name: Example B.V.
                      country: NL
                    generated_at: '2026-07-14T10:32:11.000Z'
                    llms_used:
                      - openai
                      - gemini
                      - perplexity
                    prompts_count: 6
                    geo_score: 42
                    band: emerging
                    competitors:
                      - name: Bol.com
                        domain: bol.com
                        priority_score: 92
                        mentions: 18
                        prompt_consistency: 0.87
                        llm_consistency: 0.91
                        avg_rank: 1.4
                    coverage:
                      providers_requested:
                        - openai
                        - gemini
                        - perplexity
                      providers_reported:
                        - openai
                        - gemini
                        - perplexity
                      providers_missing: []
                    revision: 1
                    score_confidence: high
                    meta:
                      latency_ms: 48211
                preliminary:
                  value:
                    scan_id: 6f1d2c3b-4a5e-4f60-9b7c-8d9e0f1a2b3c
                    tier: basic
                    status: preliminary
                    company:
                      url: https://example.nl
                      name: Example B.V.
                      country: NL
                    generated_at: null
                    llms_used:
                      - openai
                      - gemini
                      - perplexity
                    prompts_count: 6
                    geo_score: 38
                    band: emerging
                    competitors:
                      - name: Bol.com
                        domain: bol.com
                        priority_score: 92
                        mentions: 18
                        prompt_consistency: 0.87
                        llm_consistency: 0.91
                        avg_rank: 1.4
                    coverage:
                      providers_requested:
                        - openai
                        - gemini
                        - perplexity
                      providers_reported:
                        - openai
                        - perplexity
                      providers_missing:
                        - gemini
                    revision: 1
                    score_confidence: reduced
                    meta:
                      latency_ms: null
                failed:
                  value:
                    scan_id: 6f1d2c3b-4a5e-4f60-9b7c-8d9e0f1a2b3c
                    tier: basic
                    status: failed
                    company:
                      url: https://example.nl
                      name: Example B.V.
                      country: NL
                    generated_at: '2026-07-14T10:34:52.000Z'
                    llms_used:
                      - openai
                      - gemini
                      - perplexity
                    prompts_count: 6
                    coverage:
                      providers_requested:
                        - openai
                        - gemini
                        - perplexity
                      providers_reported: []
                      providers_missing:
                        - openai
                        - gemini
                        - perplexity
                    revision: 1
                    error:
                      code: all_providers_failed
                      message: >-
                        Every provider call for this scan failed, so no
                        visibility result could be produced. This scan is not
                        billed and does not consume your monthly allowance.
                      providers_failed:
                        - openai
                        - gemini
                        - perplexity
                    meta:
                      latency_ms: 61044
        '202':
          description: >-
            Still running, with zero successful observations so far — nothing to
            snapshot yet. Carries `poll_after_ms`; poll again after it — and
            stop if it is ever absent, which signals a terminal scan whose
            payload could not be built. A terminal scan (completed or failed) is
            always a 200 with a payload. See "Failed scans".
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProcessingStub'
              example:
                scan_id: 6f1d2c3b-4a5e-4f60-9b7c-8d9e0f1a2b3c
                tier: basic
                status: processing
                poll_after_ms: 3000
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Suspended'
        '404':
          description: Unknown scan id, or the scan belongs to a different partner.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: not_found
                code: not_found
        '503':
          $ref: '#/components/responses/NotEnabled'
components:
  schemas:
    PartnerScanOutput:
      type: object
      description: >-
        The single scan result contract. Returned by POST (sync completion), GET
        status polls, and inside both webhook events — partners always see this
        one shape regardless of entry point. Sandbox (test-key) responses add a
        top-level `sandbox: true` extension field.
      required:
        - scan_id
        - tier
        - status
        - company
        - generated_at
        - llms_used
        - prompts_count
        - geo_score
        - band
        - competitors
        - coverage
        - revision
        - score_confidence
        - meta
      properties:
        scan_id:
          type: string
          format: uuid
          description: Stable scan identifier.
        tier:
          type: string
          enum:
            - basic
            - full
          description: >-
            Effective tier of the scan. A request for both packages reports as
            "full" (the run includes the Full-tier surfaces). See the request
            `tier` field for how this can differ from the tier you requested
            (EXPX-1582 billing-follows-what-ran).
        status:
          type: string
          description: >-
            Scan lifecycle status. "completed" = final result. "preliminary" =
            mid-run snapshot: geo_score/band are computed live from the
            observations collected so far (best-effort, may change). "failed" =
            terminal: no result will be produced (the scan is not billed and
            does not consume your monthly allowance) — the body is a
            PartnerScanFailedOutput and its required `error` block carries the
            machine-readable reason, on the status endpoint and the
            `scan.failed` webhook alike. Other values (e.g. "processing") mirror
            internal lead state.
          examples:
            - completed
            - preliminary
            - processing
            - failed
        company:
          type: object
          required:
            - url
            - name
            - country
            - industry_code
          properties:
            url:
              type: string
              description: The scanned company URL.
            name:
              type:
                - string
                - 'null'
              description: Resolved company/brand name.
            country:
              type:
                - string
                - 'null'
              description: Resolved company country.
            industry_code:
              type:
                - string
                - 'null'
              description: >-
                The request's company.industry_code, or company.industry when
                that was a numeric classification code rather than a label —
                echoed back unchanged. Such codes are kept but never fed to
                analysis. Null when neither was sent.
            identity_status:
              type: string
              enum:
                - site_verified
                - externally_correlated
                - manual_confirmed
              description: >-
                How business identity was established. externally_correlated
                covers fetched external evidence and the gated domain-only
                provider-research fallback used when first-party and cited pages
                could not be retrieved. Absent on immutable legacy deliveries.
            identity_confidence:
              type: string
              enum:
                - high
                - medium
                - low
              description: >-
                Confidence in the resolved business identity and activity
                context. This is independent from score_confidence, which
                measures scan-provider coverage.
            context:
              $ref: '#/components/schemas/PartnerCompanyContext'
        generated_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            When the final result was assembled. Null while the scan is in
            flight.
        llms_used:
          type: array
          items:
            type: string
          description: >-
            Provider IDs dispatched for this scan. NOTE: these are provider IDs,
            not request slugs — the request slug "chatgpt" appears here as
            "openai"; all other slugs are identical to their provider ID.
        prompts_count:
          type: integer
          description: Number of prompts run per provider.
        geo_score:
          type:
            - number
            - 'null'
          description: >-
            Overall GEO visibility score (0-100). On a preliminary payload this
            is a live mid-run computation and may change in the final result.
            Null until at least one provider has reported.
        band:
          type:
            - string
            - 'null'
          description: Qualitative band derived from geo_score (e.g. "emerging").
        competitors:
          type: array
          items:
            $ref: '#/components/schemas/PartnerScanCompetitor'
          description: >-
            At most 10 eligible competitor groups on newly generated results,
            strongest first. Eligibility, grouping and priority scores use the
            full assessed pool and all observation denominators before this
            presentation limit. Fewer than 10 may qualify; all accepted member
            scopes within each returned group are retained. competitor_quality
            counts describe the full assessment, not this list length.
            Previously saved deliveries remain unchanged and may contain more
            than 10 groups.
        competitor_quality:
          type: object
          description: >-
            Business identity and competitor eligibility, separate from provider
            coverage. New-version unavailable or unresolved assessments never
            return unassessed competitors. Legacy results preserve historical
            lists and are explicitly unassessed. Preliminary results may have no
            assessment yet.
          required:
            - version
            - state
            - identity
            - accepted
            - rejected
            - unresolved
            - assessment_id
          properties:
            version:
              type:
                - string
                - 'null'
            state:
              type: string
              enum:
                - legacy_unassessed
                - available
                - partial
                - unavailable
                - identity_unresolved
                - historical_unavailable
            identity:
              type: string
              enum:
                - resolved
                - unresolved
                - unassessed
            accepted:
              type: integer
              minimum: 0
            rejected:
              type: integer
              minimum: 0
            unresolved:
              type: integer
              minimum: 0
            assessment_id:
              type:
                - string
                - 'null'
            historical:
              type: string
              enum:
                - preserved
                - unavailable
        coverage:
          $ref: '#/components/schemas/Coverage'
        revision:
          type: integer
          description: >-
            Delivery revision identifying the payload generation. 1 =
            preliminary (or a final with no prior preliminary delivery); 2 =
            final after a preliminary was already delivered. Deduplicate
            deliveries on (scan_id, revision) — the same scan_id can
            legitimately arrive twice with different revisions.
        score_confidence:
          type: string
          enum:
            - high
            - reduced
            - low
          description: >-
            Provider coverage behind geo_score; competitor_quality separately
            reports identity and relevance. The scan achieved. "high" — the scan
            covered the full set of surfaces the score is calibrated on.
            "reduced" — coverage was partial; treat the score as indicative and
            consider re-scanning. "low" — significant coverage gaps; the score
            should not be relied on. See coverage.providers_missing for exactly
            which requested surfaces did not report. On a preliminary payload
            this reflects what has reported so far and can only improve in the
            final revision.
        meta:
          type: object
          required:
            - latency_ms
          properties:
            latency_ms:
              type:
                - integer
                - 'null'
              description: >-
                End-to-end scan wall-clock in milliseconds: from the moment the
                scan began dispatching to the moment it was finalized — i.e. how
                long you waited. This is NOT a per-provider or vendor API
                latency and should not be compared with one. The surfaces run in
                parallel, so the value is set by the slowest surface plus our
                own orchestration, and it is always greater than or equal to any
                single surface's own response time. When a scan is missing one
                of the two timestamps this falls back to the slowest single
                surface response observed in the run, which is a LOWER BOUND on
                the wall-clock rather than the wall-clock itself. Null while a
                scan has produced no observations yet.
            degraded_surfaces:
              type: array
              items:
                type: string
              description: >-
                DEPRECATED in favor of coverage.providers_missing (which covers
                every provider, not just Full-tier extras). Full-tier surfaces
                that returned zero non-errored observations. Absent for
                basic-tier scans and when empty.
    PartnerScanFailedOutput:
      type: object
      description: >-
        Terminal-failure payload — what a scan that produced NO result looks
        like. Delivered on EVERY channel: the `scan.failed` webhook, a 200 from
        the status endpoint, and a 200 from the create endpoint when a scan
        fails inside its sync-wait window.


        This is a DIFFERENT shape from PartnerScanOutput, not a
        PartnerScanOutput with empty values. `geo_score`, `band`, `competitors`
        and `score_confidence` are ABSENT, not null: a scan that failed has no
        score, and a null score is too easily rendered as `0` — i.e. as the
        confident claim "this company has no AI visibility", when the truth is
        "we could not reach the models". If your integration produces copy from
        a scan, branch on `error` (or on the event name) before reading any
        result field.


        `score_confidence` is absent for a second reason: it qualifies how far
        to trust `geo_score`, and there is no `geo_score` here to qualify.


        Everything retained is either identifying (`scan_id`, `tier`, `company`,
        `revision`) or diagnostic: `llms_used` + `prompts_count` are what we
        attempted, `coverage` is what came back, `generated_at` is when we gave
        up, and `meta.latency_ms` is how long you waited for the failure.
      required:
        - scan_id
        - tier
        - status
        - company
        - generated_at
        - llms_used
        - prompts_count
        - coverage
        - revision
        - error
        - meta
      properties:
        scan_id:
          type: string
          format: uuid
          description: Stable scan identifier.
        tier:
          type: string
          enum:
            - basic
            - full
          description: >-
            Effective tier of the scan — derived the same way as on a successful
            scan.
        status:
          type: string
          const: failed
          description: >-
            Always the literal "failed". The same value the status endpoint
            reports for this scan, so the polling and webhook channels agree.
        company:
          type: object
          required:
            - url
            - name
            - country
            - industry_code
          properties:
            url:
              type: string
              description: The scanned company URL.
            name:
              type:
                - string
                - 'null'
              description: Resolved company/brand name.
            country:
              type:
                - string
                - 'null'
              description: Resolved company country.
            industry_code:
              type:
                - string
                - 'null'
              description: >-
                The request's company.industry_code, or company.industry when
                that was a numeric classification code rather than a label —
                echoed back unchanged. Such codes are kept but never fed to
                analysis. Null when neither was sent.
            context:
              $ref: '#/components/schemas/PartnerCompanyContext'
        generated_at:
          type:
            - string
            - 'null'
          format: date-time
          description: When the scan was given up on — NOT when a result was assembled.
        llms_used:
          type: array
          items:
            type: string
          description: >-
            Provider IDs we REQUESTED for this scan. Named for symmetry with the
            success payload; on a failed scan none of them delivered a usable
            answer.
        prompts_count:
          type: integer
          description: Number of prompts attempted per provider.
        coverage:
          $ref: '#/components/schemas/Coverage'
        revision:
          type: integer
          description: >-
            Same monotonic generation counter as the success payload, so you can
            deduplicate every fire — including this terminal one — on (scan_id,
            revision) with one rule. 2 if a scan.preliminary payload had already
            reached you, otherwise 1.
        error:
          type: object
          description: >-
            Machine-readable failure verdict. Always present and never null on
            this payload — the reason `scan.failed` is a distinct event. Safe to
            branch on `code` alone.
          required:
            - code
            - message
            - providers_failed
          properties:
            code:
              type: string
              enum:
                - all_providers_failed
                - analysis_failed
                - needs_business_context
              description: >-
                Stable failure code. `all_providers_failed` — every provider
                call for this scan errored, so no result could be produced.
                Treat an unrecognised code as a generic failure rather than as a
                result.
            message:
              type: string
              description: >-
                Human-readable elaboration for your logs and support flows.
                Stable per `code`; do not parse it — branch on `code`.
            providers_failed:
              type: array
              items:
                type: string
              description: >-
                Provider IDs that were requested and never reported
                successfully. Equal to coverage.providers_missing for an
                all_providers_failed scan; duplicated here so an error handler
                that reads only `error` is self-contained.
            reason:
              type: string
              enum:
                - website_unreachable
                - website_access_denied
                - website_content_unreadable
                - offering_unclear
                - identity_unverified
                - evidence_insufficient
                - temporary_analysis_failure
                - unknown
              description: >-
                Optional safe diagnostic, separate from the stable code.
                Retrieval problems (unreachable, access denied, unreadable) are
                distinct from insufficient business evidence or unclear
                offering. unknown makes no claim about the cause. Historical
                saved payloads may omit guidance.
            action:
              type: string
              enum:
                - check_company_url
                - check_website_access
                - check_website_content
                - retry_scan
                - contact_support
              description: >-
                Suggested next step. Check the URL, public access or business
                content as indicated; contact_support should include the scan
                ID. retry_scan means start a new scan after a short delay, not
                keep polling this terminal scan.
            retryable:
              type: boolean
              description: >-
                True only when retained diagnostics verify
                temporary_analysis_failure. A generic analysis_failed code,
                missing evidence, unknown cause or failure of every provider
                does not by itself establish that retrying is appropriate. False
                means an automatic retry is not recommended, not that recovery
                is impossible.
            detail:
              type: string
              description: >-
                Fixed public explanation of the reason and suggested action.
                Never raw internal errors. The existing code and message remain
                unchanged; branch on code/reason/action, not this text.
        meta:
          type: object
          required:
            - latency_ms
          properties:
            latency_ms:
              type:
                - integer
                - 'null'
              description: >-
                End-to-end wall-clock in milliseconds from the start of dispatch
                to the moment the scan was given up on — i.e. how long you
                waited for this failure. Null when the run produced no
                observations at all. No `degraded_surfaces` here: with nothing
                reported, every requested surface is degraded (see coverage).
    ProcessingStub:
      type: object
      description: >-
        Minimal acknowledgement: the scan is still running and has zero
        successful observations so far, so there is nothing to snapshot yet.
        Poll GET /api/partner/competitor-scan/{id} after poll_after_ms.


        In normal operation a terminal state — completed or failed — arrives as
        a 200 with a payload, so this stub means the scan is still going. Keep
        honouring the one legacy signal anyway: if `poll_after_ms` is ABSENT,
        stop polling. That happens only on a degraded path (we know the scan is
        terminal but could not assemble its payload), and it is still the honest
        instruction when it does.
      required:
        - scan_id
        - tier
        - status
      properties:
        scan_id:
          type: string
          format: uuid
        tier:
          type: string
          enum:
            - basic
            - full
        status:
          type: string
          examples:
            - processing
            - failed
        poll_after_ms:
          type: integer
          description: >-
            Suggested minimum delay before the next status poll. Present
            whenever the scan is still running. Absent means the status is
            terminal and polling would never change the answer — stop.
          examples:
            - 3000
        client_ref:
          type:
            - string
            - 'null'
          description: >-
            Echo of the request client_ref (POST response only; absent on GET
            status stubs).
    Error:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: >-
            Human-readable message. Free text that may interpolate per-request
            values (a domain, a tier label) — do not branch on it; branch on
            `code`.
        detail:
          type: string
          description: >-
            Optional human-readable elaboration (present on tier_not_allowed and
            monthly_scan_limit_reached).
        code:
          type: string
          enum:
            - unauthorized
            - partner_suspended
            - not_enabled
            - invalid_json
            - invalid_request_body
            - url_contains_userinfo
            - tier_not_allowed
            - invalid_llm_set
            - unscannable_domain
            - rate_limited
            - monthly_scan_limit_reached
            - invalid_limit
            - month_from_to_conflict
            - invalid_month
            - invalid_date
            - invalid_date_range
            - not_found
            - scan_failed
            - temporarily_unavailable
          description: >-
            Machine-readable failure slug, present on EVERY non-2xx response.
            Closed set; branch on this, never on `error`.
        domain:
          type: string
          description: >-
            The registrable domain that triggered an unscannable_domain
            rejection. Only present alongside code: "unscannable_domain".
    PartnerCompanyContext:
      type: object
      description: >-
        Informational context for new website-authoritative requests. Submitted
        metadata does not override website analysis. Absent on historical
        deliveries; saved payloads are never enriched retroactively. This
        comparison does not reject a scan or establish competitor relevance.
      required:
        - authority
        - submitted
        - resolved
        - name_comparison
      properties:
        authority:
          type: string
          const: website
        submitted:
          type: object
          required:
            - name
            - industry
            - country
            - language
          properties:
            name:
              type:
                - string
                - 'null'
            industry:
              type:
                - string
                - 'null'
            country:
              type:
                - string
                - 'null'
            language:
              type:
                - string
                - 'null'
          description: >-
            Retained request values; omitted or unavailable values are null.
            Industry classification codes remain in company.industry_code.
        resolved:
          type: object
          required:
            - name
            - industry
            - country
            - language
          properties:
            name:
              type:
                - string
                - 'null'
            industry:
              type:
                - string
                - 'null'
            country:
              type:
                - string
                - 'null'
            language:
              type:
                - string
                - 'null'
          description: >-
            Saved ready business profile used for this run, not the submitted
            hints. All values are null when the identity/evidence gate rejected
            the profile or it is unavailable. Country is the scan's analysis
            market, not a claim of headquarters location; language is the scan's
            analysis language.
        name_comparison:
          type: object
          required:
            - status
            - basis
            - action
          properties:
            status:
              type: string
              enum:
                - exact
                - similar_name
                - verified_alias
                - different_name
                - unknown
              description: >-
                exact means names agree after case, Unicode, spacing and
                punctuation normalization. similar_name is string resemblance
                only. verified_alias requires an explicit alias statement
                retained from a first-party page; bare model aliases do not
                qualify. different_name is informational and does not imply the
                URL or resolved business is wrong. unknown means a name is
                missing or the bounded comparison cannot be made.
            basis:
              type: string
              enum:
                - normalized_name
                - normalized_edit_similarity
                - first_party_alias_statement
                - missing_submitted_name
                - missing_resolved_name
                - comparison_limit
            action:
              type: string
              enum:
                - use_resolved_company
                - review_company_context
                - none
              description: >-
                use_resolved_company: retain the website-resolved context.
                review_company_context: optionally compare that context with the
                intended business; low resemblance alone does not require URL
                review or resubmission. none: no comparison-based action. These
                suggestions do not certify identity or the competitor list.
            similarity:
              type: number
              minimum: 0
              maximum: 1
              description: >-
                Optional deterministic string resemblance, NOT a probability or
                identity confidence. Normalized edit similarity ignores common
                legal suffixes; scores of at least 0.8 with at least four
                characters on both sides yield similar_name. Exact normalized
                equality is 1. Omitted for verified aliases and unknown
                comparisons. Computed locally without an extra model or network
                call, with names bounded to 240 characters.
    PartnerScanCompetitor:
      type: object
      required:
        - name
        - domain
        - priority_score
        - mentions
        - prompt_consistency
        - llm_consistency
        - avg_rank
      properties:
        id:
          type: string
          description: >-
            Stable assessed offer or resolver-group identifier; absent on
            historical deliveries.
        name:
          type: string
          description: >-
            Offer name, or joined member names for a resolver group; grouping
            does not establish that members are aliases.
        domain:
          type:
            - string
            - 'null'
          description: >-
            Hostname of the separately assessed official page, when supported.
            Does not retain hosted project or product paths; use website_url for
            navigation.
        website_url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            Exact supplied page selected as the candidate's official presence in
            the saved assessment. Nullable when unclear; does not fall back to a
            lookup, publisher or reseller host. Preserves hosted project/product
            paths. For groups, null when member selections differ. Historical
            deliveries may omit it; clients should prefer this URL over
            constructing a link from domain.
        scoped_alternative:
          type: string
          description: >-
            The evidenced offering or activity this alternative can provide for
            the same audience, based on its saved assessment. Describes the
            specific shared audience action or outcome, including meaningful
            partial alternatives. In a multi-member group this is one
            representative member; scoped_alternatives preserves every member
            separately. May be absent on historical deliveries.
        scope_limitation:
          type: string
          maxLength: 500
          description: >-
            Material caveat, scope limit or unresolved fact recorded in the
            accepted alternative's saved assessment. Absent when no material
            limitation was recorded or on historical deliveries; absence does
            not confirm that every fact is known.
        scoped_alternatives:
          type: array
          description: >-
            Separately accepted offerings in a resolver group. Every member
            retains its own scope, limitation and separately assessed official
            page; group membership is not semantic eligibility or verified alias
            identity. The scalar scope and limitation represent the first listed
            member, while metrics count the union of eligible occurrences.
          items:
            type: object
            required:
              - candidate_id
              - name
              - domain
              - scoped_alternative
            properties:
              candidate_id:
                type: string
              name:
                type: string
              domain:
                type:
                  - string
                  - 'null'
              website_url:
                type:
                  - string
                  - 'null'
                format: uri
              scoped_alternative:
                type: string
              scope_limitation:
                type: string
                maxLength: 500
        priority_score:
          type: number
          description: >-
            Composite prominence of the union of eligible member occurrences;
            higher = more prominent. A grouped row counts at most once per
            original answer.
        mentions:
          type: integer
          description: Total mentions across all prompts and LLMs.
        prompt_consistency:
          type: number
          description: Share of prompts in which this competitor appeared (0-1).
        llm_consistency:
          type: number
          description: Share of LLMs that surfaced this competitor (0-1).
        avg_rank:
          type:
            - number
            - 'null'
          description: Average list position when mentioned (lower = ranked higher).
    Coverage:
      type: object
      description: >-
        Per-scan provider request/response coverage — how much of what was
        requested actually reported a real (non-errored) observation. Present on
        BOTH preliminary and final output. While status is "preliminary",
        providers_missing reads as "still pending"; once status is "completed"
        it reads as "failed" (the run is done, so anything still missing never
        arrived). All values are provider IDs (see llms_used), not request
        slugs.
      required:
        - providers_requested
        - providers_reported
        - providers_missing
      properties:
        providers_requested:
          type: array
          items:
            type: string
          description: >-
            Every provider dispatched for this scan (verbatim run
            configuration).
        providers_reported:
          type: array
          items:
            type: string
          description: Providers with at least one successful (non-errored) observation.
        providers_missing:
          type: array
          items:
            type: string
          description: providers_requested minus providers_reported.
  responses:
    Unauthorized:
      description: Missing, malformed, or revoked API key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unauthorized
            code: unauthorized
    Suspended:
      description: The partner account is suspended.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: partner_suspended
            code: partner_suspended
    NotEnabled:
      description: The partner scan API is not enabled in this environment.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: not_enabled
            code: not_enabled
  securitySchemes:
    partnerApiKey:
      type: http
      scheme: bearer
      description: >-
        Partner API key as a Bearer token: `Authorization: Bearer gsk_live_...`.
        Live keys (`gsk_live_`) run real scans; test keys (`gsk_test_`) run
        request validation then return deterministic sandbox mocks (`sandbox:
        true`, nothing persisted, no rate-limit consumption). Keys are issued
        per partner and are revokable.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.