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

# Get question analytics

> Per-question aggregates for survey questions in this content: answer distribution, NPS score with promoter/passive/detractor shares, rating averages — each with a rolling-window daily series (CUMULATIVE over the trailing `rollingWindowDays`, echoed per series — unlike the content-analytics per-day byDay). Defaults to the last 30 days, UTC.



## OpenAPI

````yaml /api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{id}/analytics/questions
openapi: 3.0.0
info:
  title: Usertour API v2
  description: >-
    Project-scoped v2 API. Authenticate with a personal API token — an opaque
    `utp_...` string (NOT a JWT: do not try to decode it), created in the
    Usertour app under Settings → API, sent as `Authorization: Bearer utp_...`.
  version: '2.0'
  contact: {}
servers:
  - url: https://api.usertour.io
security: []
tags: []
paths:
  /v2/projects/{projectId}/content/{id}/analytics/questions:
    get:
      tags:
        - Analytics
      summary: Get question analytics
      description: >-
        Per-question aggregates for survey questions in this content: answer
        distribution, NPS score with promoter/passive/detractor shares, rating
        averages — each with a rolling-window daily series (CUMULATIVE over the
        trailing `rollingWindowDays`, echoed per series — unlike the
        content-analytics per-day byDay). Defaults to the last 30 days, UTC.
      operationId: ApiAnalyticsController_questionAnalytics
      parameters:
        - name: id
          required: true
          in: path
          description: Content ID
          schema:
            type: string
        - name: projectId
          required: true
          in: path
          description: Project ID
          schema:
            type: string
        - name: environmentId
          required: true
          in: query
          description: >-
            Environment whose sessions to aggregate (content is project-level;
            pick the env).
          schema:
            type: string
        - name: startDate
          required: false
          in: query
          description: 'ISO date, inclusive. Default: 30 days ago.'
          schema:
            type: string
        - name: endDate
          required: false
          in: query
          description: 'ISO date, inclusive. Default: today.'
          schema:
            type: string
        - name: timezone
          required: false
          in: query
          description: 'IANA timezone used for the per-day bucketing. Default: UTC.'
          schema:
            type: string
      responses:
        '200':
          description: Question analytics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuestionAnalyticsResponseDto'
        '400':
          description: >-
            Invalid request — E1017 validation (may carry `issues`; an invalid
            orderBy/limit is also E1017), E1015 invalid scope, E0003 invalid
            against current domain state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '401':
          description: Missing or expired API key — E1010, E1020.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '403':
          description: >-
            Refused — E1000 invalid key, E1011 project not in token scope, E1012
            insufficient scope, E1029 environment not in token scope, E1032
            environment creation needs a token without env-targeted capabilities
            (its allowlist cannot cover a not-yet-existing environment).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '404':
          description: Content or environment not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
        '429':
          description: >-
            Rate limit exceeded — E1013. The limit follows the project's plan
            (100/500/1000/3000 requests per minute); unknown credentials share a
            per-IP bucket. Every response also carries X-RateLimit-Limit /
            -Remaining / -Reset for pacing; a 429 adds the standard Retry-After
            header (seconds to back off).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponseDto'
      security:
        - bearer: []
components:
  schemas:
    QuestionAnalyticsResponseDto:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              object:
                type: string
                enum:
                  - questionAnalytics
              question:
                type: object
                properties:
                  cvid:
                    type: string
                  name:
                    type: string
                  type:
                    type: string
                    enum:
                      - nps
                      - star-rating
                      - scale
                      - single-line-text
                      - multi-line-text
                      - multiple-choice
                required:
                  - cvid
                  - name
                  - type
              totalResponses:
                type: integer
                minimum: -9007199254740991
                maximum: 9007199254740991
              distribution:
                type: array
                items:
                  type: object
                  properties:
                    answer:
                      anyOf:
                        - type: string
                        - type: number
                    count:
                      type: integer
                      minimum: -9007199254740991
                      maximum: 9007199254740991
                    percentage:
                      type: number
                      description: 0-100 (not 0-1).
                  required:
                    - answer
                    - count
                    - percentage
                description: >-
                  Overall answer distribution over the requested range. For
                  choice questions EVERY configured option appears, in option
                  order, with count 0 when nobody chose it — render it directly,
                  no join against the version needed; answers recorded under
                  options since removed from the question follow after. When
                  answers are mutually exclusive (single select), the integer
                  percentages are reconciled to sum to 100; multi-select
                  percentages are per-option shares of respondents and may sum
                  past 100.
              nps:
                type: object
                properties:
                  score:
                    type: number
                    description: >-
                      ROLLING-WINDOW aggregate (the last byDay row) — computed
                      over the trailing `rollingWindowDays`, NOT over the
                      requested range. It therefore does not share a denominator
                      with `totalResponses` (which IS range-scoped):
                      promoter/passive/detractor counts can legitimately dwarf
                      it.
                  promoters:
                    type: object
                    properties:
                      count:
                        type: integer
                        minimum: -9007199254740991
                        maximum: 9007199254740991
                      percentage:
                        type: number
                        description: 0-100 (not 0-1).
                    required:
                      - count
                      - percentage
                    description: >-
                      Rolling-window share — same window as `score`, see its
                      note.
                  passives:
                    type: object
                    properties:
                      count:
                        type: integer
                        minimum: -9007199254740991
                        maximum: 9007199254740991
                      percentage:
                        type: number
                        description: 0-100 (not 0-1).
                    required:
                      - count
                      - percentage
                    description: Rolling-window share — same window as `score`.
                  detractors:
                    type: object
                    properties:
                      count:
                        type: integer
                        minimum: -9007199254740991
                        maximum: 9007199254740991
                      percentage:
                        type: number
                        description: 0-100 (not 0-1).
                    required:
                      - count
                      - percentage
                    description: Rolling-window share — same window as `score`.
                  rollingWindowDays:
                    type: integer
                    minimum: -9007199254740991
                    maximum: 9007199254740991
                    description: >-
                      Length in days of the trailing window each byDay row
                      aggregates — the content's configurable rolling-window
                      setting (web analytics tab), default 365. Echoed here
                      because the setting is per-content: without it a consumer
                      cannot tell what a point in the series means.
                  byDay:
                    type: array
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                        score:
                          type: number
                        total:
                          type: integer
                          minimum: -9007199254740991
                          maximum: 9007199254740991
                      required:
                        - date
                        - score
                        - total
                    description: >-
                      Rolling-window CUMULATIVE series: each row aggregates the
                      trailing `rollingWindowDays` ending on that date, so
                      consecutive rows overlap and the LAST row equals the
                      overall metrics. NOT per-day increments (daily deltas are
                      not recoverable by differencing); the content-analytics
                      byDay uses the opposite, per-day convention.
                required:
                  - score
                  - promoters
                  - passives
                  - detractors
                  - rollingWindowDays
                  - byDay
                description: NPS questions only.
                nullable: true
              rating:
                type: object
                properties:
                  average:
                    type: number
                    description: >-
                      ROLLING-WINDOW average (the last byDay row) over the
                      trailing `rollingWindowDays` — not scoped to the requested
                      range like `totalResponses`.
                  rollingWindowDays:
                    type: integer
                    minimum: -9007199254740991
                    maximum: 9007199254740991
                    description: >-
                      Length in days of the trailing window each byDay row
                      aggregates — the content's configurable rolling-window
                      setting (web analytics tab), default 365. Echoed here
                      because the setting is per-content: without it a consumer
                      cannot tell what a point in the series means.
                  byDay:
                    type: array
                    items:
                      type: object
                      properties:
                        date:
                          type: string
                        average:
                          type: number
                        total:
                          type: integer
                          minimum: -9007199254740991
                          maximum: 9007199254740991
                      required:
                        - date
                        - average
                        - total
                    description: >-
                      Rolling-window CUMULATIVE series: each row aggregates the
                      trailing `rollingWindowDays` ending on that date, so
                      consecutive rows overlap and the LAST row equals the
                      overall metrics. NOT per-day increments (daily deltas are
                      not recoverable by differencing); the content-analytics
                      byDay uses the opposite, per-day convention.
                required:
                  - average
                  - rollingWindowDays
                  - byDay
                description: Star-rating / scale questions only.
                nullable: true
            required:
              - object
              - question
              - totalResponses
              - distribution
              - nps
              - rating
          description: >-
            One entry per aggregable question (nps / rating / choice). Free-text
            questions (single/multi-line text) are omitted entirely — no
            aggregate signal for open text. To read raw answers (including free
            text), fetch sessions for this content with answers expanded.
      required:
        - results
    ErrorResponseDto:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: >-
                Stable machine-readable code (e.g. E1017). Match on this, never
                on `message`.
            message:
              type: string
              description: Human-readable summary. Wording may change between releases.
            issues:
              description: >-
                Validation errors (E1017) may carry one entry per problem so
                every field can be fixed in a single round-trip. Absent on other
                errors.
              type: array
              items:
                type: object
                properties:
                  rule:
                    type: string
                    description: >-
                      Which validation layer rejected it: schema |
                      reactive_condition | action_not_allowed | step_shape |
                      reference_target | auto_start | media_url. New values may
                      be added; treat an unknown value as a generic validation
                      failure.
                  message:
                    type: string
                  path:
                    description: >-
                      Path into the request body (e.g.
                      `steps[0].triggers[0].when[1]`).
                    type: string
                required:
                  - rule
                  - message
            doc_url:
              type: string
              description: Base URL of the API documentation.
      required:
        - error
  securitySchemes:
    bearer:
      scheme: bearer
      bearerFormat: utp_... personal API token (opaque)
      type: http

````