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

# Run a cohort analysis

> Cohort an audience by acquisition period and analyze retention or LTV over
time. Limited to cohort-specific measures and to weekly / monthly / yearly
granularity.

**Required scope:** `analytics:read`.




## OpenAPI

````yaml /api-reference/openapi.yaml post /db-mgmt/api/query-cohort-analysis
openapi: 3.1.0
info:
  title: Upstack Data API
  version: '1.0'
  description: >
    Programmatic access to your Upstack Data pixel — analytics queries, the

    measures/dimensions catalog, and dashboard view management.


    All requests authenticate with two headers:


    - `x-api-key`: your Upstack API key (mint one at **Settings → API Keys** in
    the dashboard).

    - `x-pixel-id`: the pixel id the request targets. One key is scoped to one
    pixel.
servers:
  - url: https://api.upstackdata.com
    description: Production
security:
  - apiKey: []
    pixelId: []
tags:
  - name: Analytics
    description: Query events, attribution, and cohort analyses.
  - name: Catalog
    description: List the measures available to your pixel.
  - name: Dashboards
    description: >-
      Manage dashboard views — create, update, copy, delete, and the high-level
      preset builder.
  - name: Account
    description: >-
      Read and update the account that owns this API key — display name, active
      owners and admins, and subscription summary.
  - name: Costs
    description: >-
      Read and update every cost surface — global product overrides, shipping
      method, per-variant handling fees and COGS history, and per-type cost
      lines (order / gateway / shipping profile / variable / fixed). Mutations
      trigger the same per-order COGS recalculation as web-UI changes.
  - name: Products
    description: Browse products + variants in the configured catalog.
  - name: Events
    description: Send server-side tracking events directly from your backend.
paths:
  /db-mgmt/api/query-cohort-analysis:
    post:
      tags:
        - Analytics
      summary: Run a cohort analysis
      description: >
        Cohort an audience by acquisition period and analyze retention or LTV
        over

        time. Limited to cohort-specific measures and to weekly / monthly /
        yearly

        granularity.


        **Required scope:** `analytics:read`.
      operationId: queryCohortAnalysis
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - pixelId
                - granularity
                - measures
                - dateRange
              properties:
                pixelId:
                  type: string
                  description: Must match the `x-pixel-id` header.
                granularity:
                  type: string
                  enum:
                    - week
                    - month
                    - year
                measures:
                  type: array
                  minItems: 1
                  maxItems: 30
                  items:
                    type: string
                    enum:
                      - orders.ltv_cohort_gross_revenue
                      - orders.ltv_cohort_net_revenue
                      - orders.customers_cohort
                      - orders.avg_order_value_cohort
                      - orders.cohort_retention_percent
                      - orders.cohort_cm1
                      - orders.cohort_cm2
                  description: >
                    Cohort-specific measure ids. Pass measure ids as strings
                    (this

                    endpoint differs from `/api/query`, which takes objects with

                    `pacing`).
                dateRange:
                  $ref: '#/components/schemas/DateRange'
                timezone:
                  type: string
                filters:
                  $ref: '#/components/schemas/Filter'
                  description: |
                    Canonical filter applied to the cohort. `orders.*` fields
                    are valid in this context — discover via
                    `GET /api/filters`.
                orderSettings:
                  $ref: '#/components/schemas/OrderSettings'
      responses:
        '200':
          description: Cohort results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QueryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
components:
  schemas:
    DateRange:
      type: object
      required:
        - start
        - end
      properties:
        start:
          type: string
          format: date
          example: '2026-04-01'
        end:
          type: string
          format: date
          example: '2026-04-30'
    Filter:
      $ref: '#/components/schemas/FilterGroup'
      description: |
        Top-level canonical filter. Alias of `FilterGroup` — every request that
        carries a `filters` field at the body level uses this shape. Example:

        ```json
        {
          "and": [
            { "field": "orders.source_name", "op": "in", "value": ["web", "pos"] },
            { "field": "orders.customer_type", "op": "equals", "value": "new_customer" }
          ]
        }
        ```
    OrderSettings:
      type: object
      description: Order-level query settings — exclusion filters, pending/voided handling.
      properties:
        countPendingOrders:
          type: boolean
        countVoidedOrders:
          type: boolean
        refundDateAttribution:
          type: string
          enum:
            - original_order_date
            - refund_date
        exclusionFilters:
          type: array
          items:
            type: object
            description: |
              Exclusion filter group. Free-form here; see the in-app
              **Order Settings** UI for the live shape.
    QueryResponse:
      type: object
      required:
        - results
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/QueryResult'
    FilterGroup:
      type: object
      description: |
        Group node — exactly one of `and` / `or` / `not` is set. `and` and `or`
        carry an array of child nodes (leaf or nested group); `not` carries a
        single child node. Nesting depth is capped at 3.
      oneOf:
        - type: object
          required:
            - and
          properties:
            and:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/FilterNode'
        - type: object
          required:
            - or
          properties:
            or:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/FilterNode'
        - type: object
          required:
            - not
          properties:
            not:
              $ref: '#/components/schemas/FilterNode'
    QueryResult:
      type: object
      properties:
        granularity:
          $ref: '#/components/schemas/Granularity'
        dimensions:
          type: array
          items:
            type: string
        dateRange:
          $ref: '#/components/schemas/DateRange'
        compareDateRange:
          $ref: '#/components/schemas/DateRange'
        measureDetails:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/MeasureDetail'
        data:
          oneOf:
            - $ref: '#/components/schemas/QueryData'
            - type: 'null'
    Error:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: Human-readable error message.
        errors:
          type: array
          description: Per-field validation errors (present on 400 responses).
          items:
            type: object
            properties:
              message:
                type: string
              key:
                type: string
              path:
                type: array
                items:
                  type: string
    FilterNode:
      description: Either a `FilterCondition` leaf or a nested `FilterGroup`.
      oneOf:
        - $ref: '#/components/schemas/FilterCondition'
        - $ref: '#/components/schemas/FilterGroup'
    Granularity:
      type: string
      enum:
        - none
        - second
        - minute
        - hour
        - day
        - week
        - month
        - quarter
        - year
    MeasureDetail:
      type: object
      properties:
        measure:
          type: string
        isAverage:
          type: boolean
        isPercentage:
          type: boolean
        calculatedAverage:
          type: boolean
        success:
          type: boolean
        primaryAvg:
          type: number
        compareAvg:
          type: number
        primarySum:
          type: number
        compareSum:
          type: number
        percentDiff:
          type: number
        hasPacing:
          type: boolean
        preferredTrendDirection:
          type: string
          enum:
            - up
            - down
          description: Whether higher (`up`) or lower (`down`) values are favorable.
    QueryData:
      type: object
      properties:
        primary:
          type: array
          items:
            $ref: '#/components/schemas/MeasureDataRow'
        compare:
          type: array
          items:
            $ref: '#/components/schemas/MeasureDataRow'
    FilterCondition:
      type: object
      required:
        - field
        - op
        - value
      description: Leaf node of a canonical filter — a single `field op value` predicate.
      properties:
        field:
          $ref: '#/components/schemas/FilterFieldId'
        op:
          $ref: '#/components/schemas/FilterOperator'
        value:
          $ref: '#/components/schemas/FilterValue'
    MeasureDataRow:
      type: object
      description: |
        One row of query data. Always carries the requested dimension values and
        per-measure numeric values keyed by measure id. May include a `dt` field
        when granularity isn't `none`.
      additionalProperties: true
      properties:
        dt:
          type: string
          description: Bucket date for the row (when `granularity != none`).
        start:
          type: string
        end:
          type: string
    FilterFieldId:
      type: string
      description: |
        Canonical filter field id (`<scope>.<snake_case_id>`). Each id is only
        valid in certain endpoint contexts — discover the full set via
        `GET /api/filters`.
      enum:
        - orders.source_name
        - orders.customer_type
        - orders.order_type
        - orders.value
        - orders.payment_gateway
        - orders.tags
        - channel.resource_name
        - channel.resource_source
        - channel.resource_status
        - channel.resource_configured_status
        - channel.resource_effective_status
        - channel.account_id
        - channel.campaign_id
        - channel.adset_id
        - channel.ad_id
        - attribution.utm_source
        - attribution.utm_medium
        - attribution.utm_campaign
        - attribution.utm_adset
        - attribution.utm_term
        - attribution.utm_ad
        - attribution.utm_content
        - emq.dataset_id
        - emq.event_name
        - emq.diagnostic_name
    FilterOperator:
      type: string
      description: |
        Operator on a filter condition. Which operators are valid for a field
        depends on its `valueType` — discover with `GET /api/filters`.
      enum:
        - equals
        - not_equals
        - greater_than
        - less_than
        - greater_than_or_equals
        - less_than_or_equals
        - contains
        - in
        - not_in
        - array_includes
        - array_includes_all
        - array_includes_any
    FilterValue:
      description: |
        Scalar (`string` / `number` / `boolean`) for equality / comparison /
        `contains` operators. Array of scalars for `in`, `not_in`, and
        `array_includes_*` operators.
      oneOf:
        - type: string
        - type: number
        - type: boolean
        - type: array
          items:
            type: string
        - type: array
          items:
            type: number
        - type: array
          items:
            type: boolean
  responses:
    BadRequest:
      description: Validation error. Includes an `errors` array with per-field detail.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: >-
        Missing or invalid `x-api-key` / `x-pixel-id`, or key is
        revoked/expired.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: >-
        API key does not carry the required scope for this endpoint, or body
        `pixelId` does not match the `x-pixel-id` header.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
      description: Your Upstack API key. Starts with `upstack_`.
    pixelId:
      type: apiKey
      in: header
      name: x-pixel-id
      description: The pixel id the request targets.

````