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

# Run a Report Query

> Runs one report query against the project and returns a dataset.

The body says what to group by (`dimensions`), what to compute (`measures`) and what to keep (`filters`). Every app report can be rebuilt with this endpoint; the Reports API guide in the docs shows the body for each page and widget.

- **Rows are dimension groups.** `dimensions: ["brand"]` is one row per brand, `["brand", "service"]` one row per brand per platform, `["brand", "date"]` a timeline. Omit `dimensions` for one project-level row. A brand with no mentions still gets a zero-filled row. On `visibility`, `["answer"]` is one row per AI answer, a list.
- **Filters have roles.** A population field (`service`, `country`, `tag`, `topic`, `prompt`, `mentions_brand`, `cites_domain`, `mention_sentiment` on sources, `carries_narrative`, `carries_theme`) narrows which AI answers are counted and so changes every number, including denominators; it takes `in` only. An entity field (`brand`, `domain`, `narrative`, ...) or a measure field hides rows after the numbers are computed and never changes a number; entity fields need their dimension on the row.
- **Compare window.** `compare_start_date` and `compare_end_date` run the same query over a second window and add `<measure>_compare` and `<measure>_change` columns, usable in `filters` and `order_by`. Time buckets pair by position from each window's start. A single-row result also fills `benchmarks[0]` for a KPI card.
- **Paging.** `limit` (default 100, max 1000) and `offset`; `page.total` is the row count before the page was cut. `rank` is assigned per group before any row filter, so `rank lte 5` is "top five per group".
- **Related blocks.** `related` adds per-row lists such as `brands_on_page` (sources), `brands_in_answer`, `sources_in_answer`, `answer_text` (visibility with the `answer` dimension) and `narrative_description`, `evidence` (narratives). Each entry carries `row`, the index of its row.

`GET /api/v1/reports/{entity}/schema` lists what each entity accepts and `GET /api/v1/projects/{project}/reports/values/{field}` lists the identifiers a filter takes. The `narratives` entity and the `carries_narrative` / `carries_theme` filters need the Reputation capability.



## OpenAPI

````yaml /openapi.json post /api/v1/projects/{project}/reports/{entity}
openapi: 3.1.0
info:
  title: Surfacd Public API
  version: 1.0.0
  description: >-
    Public API for Surfacd. Team-scoped operations live under
    `/api/v1/team/...`; project collections live under `/api/v1/projects`;
    project-scoped operations live under `/api/v1/projects/{project}/...`. All
    endpoints require a Bearer team API key created in Team Settings. The API
    key selects the team for team-level and collection endpoints.
servers:
  - url: https://app.surfacd.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Projects
    description: Project management endpoints. Reads only in this version.
  - name: Prompts
    description: Prompt management endpoints. Reads only in this version.
  - name: Team
    description: Team settings and plan details for the current team.
  - name: Reporting
    description: >-
      Read-only reporting endpoints for projects (brand rankings, mentions,
      visibility, matrix analysis, source data).
  - name: Reference
    description: Reference data used by create, update, and reporting filters.
  - name: Tags
    description: Project tag management endpoints.
  - name: Reputation
    description: >-
      Read-only reputation analytics for a project: sentiment overview and
      trends, themes, narratives, the AI responses behind them, and the sources
      those responses cite. Requires the Reputation capability on the project's
      team; without it these endpoints return `403`.
  - name: Reports
    description: >-
      The report query endpoint. One `POST` per report entity (`visibility`,
      `sources`, `narratives`) takes dimensions, measures, filters, an optional
      compare window and paging, and returns a dataset any renderer can draw.
      The schema and values endpoints describe what a query accepts. See the
      Reports API guide in the docs for worked examples of every app report.
paths:
  /api/v1/projects/{project}/reports/{entity}:
    parameters:
      - $ref: '#/components/parameters/ProjectUuid'
      - name: entity
        in: path
        required: true
        description: The report entity.
        schema:
          $ref: '#/components/schemas/ReportEntity'
    post:
      tags:
        - Reports
      summary: Run a Report Query
      description: >-
        Runs one report query against the project and returns a dataset.


        The body says what to group by (`dimensions`), what to compute
        (`measures`) and what to keep (`filters`). Every app report can be
        rebuilt with this endpoint; the Reports API guide in the docs shows the
        body for each page and widget.


        - **Rows are dimension groups.** `dimensions: ["brand"]` is one row per
        brand, `["brand", "service"]` one row per brand per platform, `["brand",
        "date"]` a timeline. Omit `dimensions` for one project-level row. A
        brand with no mentions still gets a zero-filled row. On `visibility`,
        `["answer"]` is one row per AI answer, a list.

        - **Filters have roles.** A population field (`service`, `country`,
        `tag`, `topic`, `prompt`, `mentions_brand`, `cites_domain`,
        `mention_sentiment` on sources, `carries_narrative`, `carries_theme`)
        narrows which AI answers are counted and so changes every number,
        including denominators; it takes `in` only. An entity field (`brand`,
        `domain`, `narrative`, ...) or a measure field hides rows after the
        numbers are computed and never changes a number; entity fields need
        their dimension on the row.

        - **Compare window.** `compare_start_date` and `compare_end_date` run
        the same query over a second window and add `<measure>_compare` and
        `<measure>_change` columns, usable in `filters` and `order_by`. Time
        buckets pair by position from each window's start. A single-row result
        also fills `benchmarks[0]` for a KPI card.

        - **Paging.** `limit` (default 100, max 1000) and `offset`; `page.total`
        is the row count before the page was cut. `rank` is assigned per group
        before any row filter, so `rank lte 5` is "top five per group".

        - **Related blocks.** `related` adds per-row lists such as
        `brands_on_page` (sources), `brands_in_answer`, `sources_in_answer`,
        `answer_text` (visibility with the `answer` dimension) and
        `narrative_description`, `evidence` (narratives). Each entry carries
        `row`, the index of its row.


        `GET /api/v1/reports/{entity}/schema` lists what each entity accepts and
        `GET /api/v1/projects/{project}/reports/values/{field}` lists the
        identifiers a filter takes. The `narratives` entity and the
        `carries_narrative` / `carries_theme` filters need the Reputation
        capability.
      operationId: v1ProjectsReports
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReportQueryRequest'
            examples:
              kpi_card:
                summary: Dashboard KPI card with trend
                value:
                  start_date: '2026-09-01'
                  end_date: '2026-09-28'
                  compare_start_date: '2026-08-04'
                  compare_end_date: '2026-08-31'
                  dimensions:
                    - brand
                  measures:
                    - visibility
                    - sentiment
                    - avg_position
                  filters:
                    - field: brand
                      op: in
                      values:
                        - 4e1f77d6-35d8-4a29-9fb6-d83e51469b35
              rankings:
                summary: Industry rankings table with deltas
                value:
                  start_date: '2026-09-22'
                  end_date: '2026-09-28'
                  compare_start_date: '2026-09-15'
                  compare_end_date: '2026-09-21'
                  dimensions:
                    - brand
                  measures:
                    - visibility
                    - mentions
                    - avg_position
                    - sentiment
                    - rank
              timeline:
                summary: Visibility chart, every brand by day
                value:
                  start_date: '2026-09-01'
                  end_date: '2026-09-28'
                  dimensions:
                    - brand
                    - date
                  measures:
                    - visibility
                  order_by:
                    - field: date
                      dir: asc
                  limit: 1000
              sources_domains:
                summary: 'Sources: domains table (entity `sources`)'
                value:
                  start_date: '2026-09-01'
                  end_date: '2026-09-28'
                  dimensions:
                    - domain
                  measures:
                    - citations
                    - responses
                    - citation_rate
                    - avg_rank
                    - share
                    - rank
                    - analysed_pages
                    - mentioning_pages
                  related:
                    - brands_on_page
                  limit: 25
              answers_list:
                summary: >-
                  Mentions list (entity `visibility` with the `answer`
                  dimension)
                value:
                  start_date: '2026-09-01'
                  end_date: '2026-09-28'
                  dimensions:
                    - answer
                    - brand
                    - mention_sentiment
                    - service
                    - prompt
                  measures:
                    - avg_position
                    - citations
                  filters:
                    - field: brand
                      op: in
                      values:
                        - 4e1f77d6-35d8-4a29-9fb6-d83e51469b35
                  order_by:
                    - field: answered_at
                      dir: desc
                  related:
                    - brands_in_answer
                  limit: 25
              narratives_list:
                summary: Reputation narratives with movers (entity `narratives`)
                value:
                  start_date: '2026-09-01'
                  end_date: '2026-09-28'
                  compare_start_date: '2026-08-04'
                  compare_end_date: '2026-08-31'
                  dimensions:
                    - narrative
                    - narrative_sentiment
                    - theme
                    - brand
                  measures:
                    - answers
                    - share
                    - sentiment
                  filters:
                    - field: brand
                      op: in
                      values:
                        - 4e1f77d6-35d8-4a29-9fb6-d83e51469b35
                  related:
                    - narrative_description
                  order_by:
                    - field: share
                      dir: desc
      responses:
        '200':
          description: The report dataset.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReportDataset'
              example:
                id: visibility
                title: Visibility
                description: >-
                  How brands appear in AI answers: totals per group, or one row
                  per answer
                empty: No visibility data for the selected filters
                tooltip: null
                label: null
                badge: null
                group: Report
                value: 0.16157205
                column:
                  id: value
                  title: Visibility
                  type: measure
                  format: percent
                  decimals: 1
                  prefix: ''
                  suffix: ''
                from: '2026-09-01'
                to: '2026-09-28'
                columns:
                  - id: brand_uuid
                    title: Brand id
                    type: dimension
                    format: number
                    decimals: null
                    prefix: ''
                    suffix: ''
                  - id: brand
                    title: Brand
                    type: dimension
                    format: number
                    decimals: null
                    prefix: ''
                    suffix: ''
                  - id: visibility
                    title: Visibility
                    type: measure
                    format: percent
                    decimals: 1
                    prefix: ''
                    suffix: ''
                  - id: visibility_compare
                    title: Visibility (compare)
                    type: measure
                    format: percent
                    decimals: 1
                    prefix: ''
                    suffix: ''
                  - id: visibility_change
                    title: Visibility (change)
                    type: measure
                    format: percent
                    decimals: 1
                    prefix: ''
                    suffix: ''
                rows:
                  - brand_uuid: 4e1f77d6-35d8-4a29-9fb6-d83e51469b35
                    brand: Apple
                    visibility: 0.16157205
                    visibility_compare: 0.16397849
                    visibility_change: -0.0024064332
                page:
                  offset: 0
                  limit: 100
                  total: 1
                related: {}
                benchmarks:
                  - name: Compare period
                    type: compare
                    from: '2026-08-04T00:00:00.000000Z'
                    to: '2026-08-31T00:00:00.000000Z'
                    value: 0.16397849
                    rows: []
                    difference: -0.00240644
                    ratio: 0.99
                    change: -0.01
                    favourable: false
                    aboveOrBelow: below
                    lowerIsBetter: false
                actions: []
        '403':
          $ref: '#/components/responses/ReportEntitlement'
        '404':
          description: Project not found in the current team.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                required:
                  - message
        '422':
          $ref: '#/components/responses/ValidationError'
components:
  parameters:
    ProjectUuid:
      name: project
      in: path
      required: true
      description: Project UUID.
      schema:
        type: string
        format: uuid
  schemas:
    ReportEntity:
      type: string
      enum:
        - visibility
        - sources
        - narratives
      description: >-
        A report entity. `visibility`: brand visibility, mentions, position,
        sentiment and rank; with the `answer` dimension, one row per AI answer
        (a list, no compare window). `sources`: cited domains, hostnames, URLs
        and pages. `narratives`: reputation narratives, themes and sentiment
        (needs the Reputation capability).
    ReportQueryRequest:
      type: object
      description: >-
        A report query. Rows are dimension groups; measures are the numbers
        computed per group; filters narrow the population or hide rows. Omit
        `dimensions` for one project-level row.
      properties:
        start_date:
          type: string
          format: date
          description: Inclusive start of the window.
        end_date:
          type: string
          format: date
          description: Inclusive end of the window, on or after `start_date`.
        compare_start_date:
          type: string
          format: date
          nullable: true
          description: >-
            Optional second window to compare against. Required when
            `compare_end_date` is present. Every measure then gains
            `<measure>_compare` and `<measure>_change` columns. Not accepted
            with the `answer` dimension on `visibility`.
        compare_end_date:
          type: string
          format: date
          nullable: true
          description: Required when `compare_start_date` is present; on or after it.
        dimensions:
          type: array
          items:
            type: string
          description: >-
            What each row groups by, in display order. Use at most one time
            bucket (`date`, `week` or `month`). On `visibility`, `answer` makes
            one row per AI answer. The schema endpoint lists the entity's
            dimensions.
        measures:
          type: array
          items:
            type: string
          minItems: 1
          description: >-
            The numbers to compute per row. The schema endpoint lists the
            entity's measures.
        filters:
          type: array
          items:
            $ref: '#/components/schemas/ReportFilter'
        order_by:
          type: array
          items:
            $ref: '#/components/schemas/ReportOrder'
          description: >-
            Defaults to the entity's own order (for example visibility
            descending on `visibility`, newest first with the `answer`
            dimension).
        limit:
          type: integer
          minimum: 1
          maximum: 1000
          default: 100
        offset:
          type: integer
          minimum: 0
          default: 0
        related:
          type: array
          items:
            type: string
          description: >-
            Extra per-row blocks the entity offers, such as `brands_on_page` on
            `sources`, `brands_in_answer`, `sources_in_answer` and `answer_text`
            on `visibility` with the `answer` dimension, `narrative_description`
            and `evidence` on `narratives`. Each entry in the response names its
            row by index.
        top:
          type: object
          description: >-
            Keep only the best values of one dimension, ranked over the whole
            window, on every row. `{"field": "brand", "by": "visibility",
            "limit": 6}` on `["brand", "date"]` gives the six most visible
            brands for every day, where `limit` would give six rows.
            Lower-is-better measures rank ascending. Not with the `answer`
            dimension.
          properties:
            field:
              type: string
              description: A dimension in `dimensions`, not `date`, `week` or `month`.
            by:
              type: string
              description: The measure that ranks the values.
            limit:
              type: integer
              minimum: 1
              maximum: 1000
          required:
            - field
            - by
            - limit
      required:
        - start_date
        - end_date
        - measures
    ReportDataset:
      type: object
      description: >-
        The result of a report query: a column schema, raw rows keyed by column
        id, paging, optional related blocks and, for a single-row compare query,
        a benchmark.
      properties:
        id:
          $ref: '#/components/schemas/ReportEntity'
        title:
          type: string
        description:
          type: string
        empty:
          type: string
          description: Text to show when `rows` is empty.
        tooltip:
          type: object
          nullable: true
        label:
          type: string
          nullable: true
        badge:
          type: string
          nullable: true
        group:
          type: string
        value:
          type: number
          description: >-
            Headline value of the first measure when the result has exactly one
            row, otherwise 0.
        column:
          $ref: '#/components/schemas/ReportColumn'
        from:
          type: string
          format: date
        to:
          type: string
          format: date
        columns:
          type: array
          items:
            $ref: '#/components/schemas/ReportColumn'
          description: >-
            The row schema in display order: id columns (`brand_uuid`,
            `prompt_uuid`, ...), dimensions, then each measure followed by its
            `_compare` and `_change` columns when a compare window is set.
        rows:
          type: array
          items:
            type: object
            additionalProperties: true
          description: >-
            One object per row, keyed by column id. Percent measures are
            decimals; a `null` compare column means the compare window had no
            matching row.
        page:
          $ref: '#/components/schemas/ReportPage'
        related:
          type: object
          additionalProperties:
            type: array
            items:
              $ref: '#/components/schemas/ReportRelatedEntry'
          description: One list per requested block, keyed by block id.
        benchmarks:
          type: array
          items:
            $ref: '#/components/schemas/ReportBenchmark'
        actions:
          type: array
          items:
            type: object
      required:
        - id
        - title
        - columns
        - rows
        - page
        - related
        - benchmarks
    ReportFilter:
      type: object
      description: >-
        One condition. The field's role decides where it runs: a population
        field narrows which AI answers are counted (and so changes every
        number), an entity or measure field hides rows after the numbers are
        computed (totals unchanged). The schema endpoint lists each field with
        its role and operators.
      properties:
        field:
          type: string
          description: >-
            A filter field of the entity, from `GET
            /api/v1/reports/{entity}/schema`.
          examples:
            - service
            - brand
            - visibility
        op:
          $ref: '#/components/schemas/ReportFilterOperator'
        values:
          type: array
          items:
            type: string
            maxLength: 2048
          minItems: 1
          description: >-
            Required with `in` and `not_in`. Public identifiers: brand, prompt,
            narrative and theme UUIDs; tag and topic UUIDs or names; platform
            slugs; ISO country codes; domain names; or fixed vocabulary values.
        value:
          oneOf:
            - type: number
            - type: string
          description: >-
            Required with every other operator: a number for comparisons, text
            for `contains`.
      required:
        - field
        - op
    ReportOrder:
      type: object
      properties:
        field:
          type: string
          description: >-
            A dimension or measure on the row, including `<measure>_compare` and
            `<measure>_change` when a compare window is set.
        dir:
          type: string
          enum:
            - asc
            - desc
          default: desc
      required:
        - field
    ReportColumn:
      type: object
      description: >-
        How to format one column of `rows`. `format: percent` values are
        decimals (0.42 means 42%).
      properties:
        id:
          type: string
        title:
          type: string
        type:
          type: string
          enum:
            - dimension
            - measure
            - date
        format:
          type: string
          enum:
            - number
            - percent
        decimals:
          type: integer
          nullable: true
        prefix:
          type: string
        suffix:
          type: string
      required:
        - id
        - title
        - type
        - format
        - decimals
        - prefix
        - suffix
    ReportPage:
      type: object
      properties:
        offset:
          type: integer
        limit:
          type: integer
        total:
          type: integer
          description: Rows before the page was cut. Also correct on a page past the end.
      required:
        - offset
        - limit
        - total
    ReportRelatedEntry:
      type: object
      description: >-
        One block entry. `row` is the index of the row it belongs to in `rows`;
        the other keys depend on the block.
      properties:
        row:
          type: integer
      required:
        - row
      additionalProperties: true
    ReportBenchmark:
      type: object
      description: >-
        Set on a single-row query with a compare window, so a KPI card can show
        the trend of the first measure.
      properties:
        name:
          type: string
          examples:
            - Compare period
        type:
          type: string
          examples:
            - compare
        from:
          type: string
          format: date-time
        to:
          type: string
          format: date-time
        value:
          type: number
          description: The first measure over the compare window.
        rows:
          type: array
          items:
            type: object
        difference:
          type: number
          nullable: true
          description: Current minus compare.
        ratio:
          type: number
          nullable: true
        change:
          type: number
          nullable: true
          description: Relative change as a decimal.
        favourable:
          type: boolean
          description: >-
            Whether the move is good for the measure; `avg_position` counts down
            as good.
        aboveOrBelow:
          type: string
          enum:
            - above
            - below
            - equal
        lowerIsBetter:
          type: boolean
    ValidationError:
      type: object
      description: Returned when one or more request parameters fail validation.
      properties:
        message:
          type: string
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
      required:
        - message
        - errors
    ReportFilterOperator:
      type: string
      enum:
        - in
        - not_in
        - eq
        - gt
        - gte
        - lt
        - lte
        - contains
      description: >-
        `in` and `not_in` take `values`. `eq`, `gt`, `gte`, `lt` and `lte` take
        a numeric `value`. `contains` takes a text `value` and matches a label
        case-insensitively.
  responses:
    ReportEntitlement:
      description: >-
        The query reads reputation data (the `narratives` entity, or a
        `carries_narrative` / `carries_theme` filter) and the team does not have
        the Reputation capability.
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
            required:
              - message
    ValidationError:
      description: Validation failed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: token
      description: >-
        Team API key created in Team Settings. Use it with URLs for the same
        team.

````

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