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

# Hierarchical traffic time series for a scope

> Returns a stacked time series suitable for an ECharts ``stacked area / bar`` chart, plus a breadcrumb describing the drill-down path. The shape mirrors the Customer Analytics Dashboard contract (C-1045).

Scopes form a hierarchy ``organization → brand → product (optional) → trade_item``; the response's ``series[*].type`` discriminator tells the frontend whether clicking a stacked segment should drill into another product, an ungrouped trade item, or stop.

Data comes from the pre-aggregated ``ScanDailySummary`` table — no raw event scans are queried at request time. Today's partial day is excluded; pre-aggregation runs nightly.

Caller must be a member of ``organization_id`` or a 404 is returned. ``scope_id`` (when supplied) must belong to that organization or a 404 is returned.



## OpenAPI

````yaml /openapi/openapi-analytics.json get /analytics/api/v1/traffic
openapi: 3.1.0
info:
  title: Analytics API
  version: 1.0.0
  description: >
    Pre-aggregated traffic metrics for the requesting organization at every
    level of the brand → product → trade-item hierarchy. Returns a stacked time
    series with breadcrumbs and summary totals, ready to chart directly. Scoped
    to organizations you are a member of; an org-scoped API key reaches exactly
    its own organization.


    ## Authentication


    All endpoints require an API key passed via the `X-API-Key` HTTP header,
    unless otherwise noted.


    ```

    X-API-Key: csb_<body>_<checksum>

    ```


    Generate API keys in **Settings > API Keys** in your dashboard, or via the
    Account API.

    Session-based (cookie) authentication is also accepted for browser-based
    access.


    ## Rate Limits


    | Tier        | Requests / minute | Requests / day |

    |-------------|-------------------|----------------|

    | Default     | 300               | 10,000         |

    | Custom      | Contact us        | Contact us     |


    Rate-limit headers are included on every response so callers can
    self-throttle without

    hitting our 429s ("informed governor"):


    - `RateLimit-Policy` — every active window, e.g. `300;w=60, 10000;w=86400`

    - `RateLimit-Limit` — quota for the **most-restrictive** currently-active
    window

    - `RateLimit-Remaining` — requests left in that window

    - `RateLimit-Reset` — seconds until that window resets (relative; clock-skew
    safe)


    Legacy `X-RateLimit-*` aliases are also emitted for back-compat.
    `X-RateLimit-Reset`

    keeps the absolute Unix-timestamp shape to avoid breaking existing
    consumers.


    When rate-limited, you receive `429 Too Many Requests` with a
    `retry_after_seconds` field

    in the error envelope and a `Retry-After` header.


    ## Pagination


    List endpoints return paginated results in this envelope:


    ```json

    {
      "data": [...],
      "pagination": {
        "page": 1,
        "page_size": 25,
        "total_count": 342,
        "total_pages": 14,
        "has_next": true,
        "has_previous": false
      }
    }

    ```


    Use `?page=2&page_size=50` query parameters. Maximum page size is 100.


    ## Error Responses


    All errors conform to [RFC 9457 Problem
    Details](https://www.rfc-editor.org/rfc/rfc9457)

    with `Content-Type: application/problem+json`:


    ```json

    {
      "type": "https://closient.com/docs/errors/not_found",
      "title": "Not Found",
      "status": 404,
      "detail": "The requested resource was not found.",
      "error_code": "not_found",
      "retryable": false,
      "timestamp": "2026-03-31T12:00:00+00:00"
    }

    ```


    Common error codes: `unauthorized` (401), `forbidden` (403), `not_found`
    (404),

    `validation_error` (422), `rate_limited` (429), `internal_error` (500).
  termsOfService: https://www.closient.com/terms/
servers:
  - url: https://www.closient.com
security: []
externalDocs:
  description: Closient Documentation
  url: https://docs.closient.com
paths:
  /analytics/api/v1/traffic:
    get:
      tags:
        - Traffic
      summary: Hierarchical traffic time series for a scope
      description: >-
        Returns a stacked time series suitable for an ECharts ``stacked area /
        bar`` chart, plus a breadcrumb describing the drill-down path. The shape
        mirrors the Customer Analytics Dashboard contract (C-1045).


        Scopes form a hierarchy ``organization → brand → product (optional) →
        trade_item``; the response's ``series[*].type`` discriminator tells the
        frontend whether clicking a stacked segment should drill into another
        product, an ungrouped trade item, or stop.


        Data comes from the pre-aggregated ``ScanDailySummary`` table — no raw
        event scans are queried at request time. Today's partial day is
        excluded; pre-aggregation runs nightly.


        Caller must be a member of ``organization_id`` or a 404 is returned.
        ``scope_id`` (when supplied) must belong to that organization or a 404
        is returned.
      operationId: apps_analytics_api_traffic_get_traffic_endpoint
      parameters:
        - in: query
          name: organization_id
          schema:
            description: UUID of the organization to query (caller must be a member).
            format: shortuuid
            maxLength: 22
            minLength: 22
            pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
            title: Organization Id
            type: string
          required: true
          description: UUID of the organization to query (caller must be a member).
        - in: query
          name: scope_type
          schema:
            allOf:
              - description: >-
                  The kind of entity the chart is currently zoomed into.


                  The hierarchy is ``organization → brand → (product?) →
                  trade_item``. A

                  ``product`` scope is optional; a brand may contain products
                  **and**

                  ungrouped trade items side-by-side.
                enum:
                  - organization
                  - brand
                  - product
                  - trade_item
                title: ScopeTypeEnum
                type: string
            default: organization
            description: >-
              Hierarchy level being viewed. Defaults to ``organization`` (the
              top-level entry view).
          required: false
          description: >-
            Hierarchy level being viewed. Defaults to ``organization`` (the
            top-level entry view).
        - in: query
          name: scope_id
          schema:
            description: >-
              UUID of the brand / product / trade item to scope to. Required for
              every ``scope_type`` other than ``organization``.
            title: Scope Id
            format: shortuuid
            maxLength: 22
            minLength: 22
            pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
            type: string
          required: false
          description: >-
            UUID of the brand / product / trade item to scope to. Required for
            every ``scope_type`` other than ``organization``.
        - in: query
          name: group_by
          schema:
            description: >-
              Override the default child grouping. Most callers leave this blank
              — the service derives it from ``scope_type`` (org → brand, brand →
              product_and_trade_item, product → trade_item).
            allOf:
              - $ref: '#/components/schemas/GroupByEnum'
          required: false
          description: >-
            Override the default child grouping. Most callers leave this blank —
            the service derives it from ``scope_type`` (org → brand, brand →
            product_and_trade_item, product → trade_item).
        - in: query
          name: interval
          schema:
            allOf:
              - description: Time bucket size for the X-axis.
                enum:
                  - day
                  - week
                  - month
                title: IntervalEnum
                type: string
            default: day
            description: Time bucket size for the X-axis.
          required: false
          description: Time bucket size for the X-axis.
        - in: query
          name: start
          schema:
            description: Window start (UTC, inclusive). Defaults to 30 days before ``end``.
            title: Start
            format: date-time
            type: string
          required: false
          description: Window start (UTC, inclusive). Defaults to 30 days before ``end``.
        - in: query
          name: end
          schema:
            description: Window end (UTC, exclusive). Defaults to ``now``.
            title: End
            format: date-time
            type: string
          required: false
          description: Window end (UTC, exclusive). Defaults to ``now``.
        - in: query
          name: metric
          schema:
            allOf:
              - description: >-
                  Which counter the Y-axis should report.


                  ``resolves`` is every scan recorded by the resolver;
                  ``page_visits`` is

                  the subset that landed on a hosted product page (the billable
                  surface).
                enum:
                  - resolves
                  - page_visits
                title: MetricEnum
                type: string
            default: resolves
            description: >-
              Counter to plot on the Y-axis. ``resolves`` = every scan the
              resolver served; ``page_visits`` = the hosted-page subset.
          required: false
          description: >-
            Counter to plot on the Y-axis. ``resolves`` = every scan the
            resolver served; ``page_visits`` = the hosted-page subset.
        - in: query
          name: top_n
          schema:
            default: 10
            description: >-
              Cap on distinct stacked series. The remainder collapses into a
              single ``Others`` series so the chart stays readable.
            maximum: 50
            minimum: 1
            title: Top N
            type: integer
          required: false
          description: >-
            Cap on distinct stacked series. The remainder collapses into a
            single ``Others`` series so the chart stays readable.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TrafficOut'
        '400':
          description: Bad Request
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '401':
          description: Unauthorized
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '403':
          description: Forbidden
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '404':
          description: Not Found
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '405':
          description: Method Not Allowed
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '422':
          description: Unprocessable Content
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
        '429':
          description: Too Many Requests
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ErrorOut'
      security:
        - APIKeyHeaderAuth: []
        - OAuthTokenAuth: []
        - CookieGatedSessionAuth: []
components:
  schemas:
    GroupByEnum:
      description: |-
        Dimension to break the stacked series down by, at the current scope.

        Conventional pairings:

        * ``organization`` scope → ``brand`` group_by
        * ``brand`` scope → ``product_and_trade_item`` (mixed) group_by
        * ``product`` scope → ``trade_item`` group_by
        * ``trade_item`` scope → flat (no children currently captured by the
          resolver pipeline; reserved for future ``batch`` / ``serial``)
      enum:
        - brand
        - product_and_trade_item
        - trade_item
      title: GroupByEnum
      type: string
    TrafficOut:
      description: |-
        Stacked traffic time series for a scope, ready for ECharts.

        Shape mirrors the ticket's *Response shape* example. The frontend feeds
        ``timestamps`` to the X-axis and each :class:`SeriesEntryOut` to a
        stacked series; ``total`` / ``previous_period_total`` power the summary
        cards above the chart.
      examples:
        - breadcrumb:
            - name: Salsa Fresca Foods
              type: organization
            - id: 8kPq2mWvNc4RtYuIoPaSdF
              name: Salsa Fresca
              type: brand
          interval: day
          metric: scans
          period:
            end: '2026-07-08T00:00:00Z'
            start: '2026-07-01T00:00:00Z'
          previous_period_total: 142
          scope:
            id: 8kPq2mWvNc4RtYuIoPaSdF
            name: Salsa Fresca
            type: brand
          series:
            - data:
                - 12
                - 19
                - 7
                - 0
                - 23
                - 31
                - 18
              has_children: true
              id: 3nRt7yUiOpAsDfGhJkLzXc
              name: Salsa Fresca Medium 16oz
              total: 110
              type: product
            - data:
                - 8
                - 11
                - 4
                - 0
                - 14
                - 19
                - 12
              has_children: true
              id: 5vBnM3qWeRtYuIoPaSdFgH
              name: Salsa Fresca Hot 16oz
              total: 68
              type: product
          timestamps:
            - '2026-07-01'
            - '2026-07-02'
            - '2026-07-03'
            - '2026-07-04'
            - '2026-07-05'
            - '2026-07-06'
            - '2026-07-07'
          total: 178
      properties:
        scope:
          $ref: '#/components/schemas/ScopeOut'
          description: Entity the chart is currently zoomed into.
        breadcrumb:
          description: >-
            Drill-down path from the org root to the current scope. The frontend
            renders this as a click-to-navigate trail.
          items:
            $ref: '#/components/schemas/BreadcrumbEntryOut'
          title: Breadcrumb
          type: array
        interval:
          $ref: '#/components/schemas/IntervalEnum'
          description: Time bucket size used to build ``timestamps``.
        metric:
          $ref: '#/components/schemas/MetricEnum'
          description: Which counter the ``data`` arrays report.
        period:
          $ref: '#/components/schemas/PeriodOut'
          description: Window covered by ``timestamps``.
        total:
          description: Sum of every series total within ``period`` — top summary card.
          minimum: 0
          title: Total
          type: integer
        previous_period_total:
          description: >-
            Sum for the *immediately preceding* equal-length window. Used to
            compute the delta-vs-previous summary and per-series trend
            indicator. Zero when no historical data exists.
          minimum: 0
          title: Previous Period Total
          type: integer
        series:
          description: >-
            Children of the current scope, ranked by total descending.
            High-cardinality scopes apply a ``Top N + Others`` collapse — see
            ``top_n`` query param.
          items:
            $ref: '#/components/schemas/SeriesEntryOut'
          title: Series
          type: array
        timestamps:
          description: >-
            X-axis bucket starts, ISO date / datetime. Length matches every
            series' ``data`` array.
          items:
            format: date
            type: string
          title: Timestamps
          type: array
      required:
        - scope
        - breadcrumb
        - interval
        - metric
        - period
        - total
        - previous_period_total
        - series
        - timestamps
      title: TrafficOut
      type: object
    ErrorOut:
      description: |-
        RFC 9457 Problem Details response.

        All API errors are returned in this format with Content-Type:
        application/problem+json.
      examples:
        - detail: The requested resource was not found.
          error_code: not_found
          retryable: false
          status: 404
          timestamp: '2026-03-31T12:00:00+00:00'
          title: Not Found
          type: https://closient.com/docs/errors/not_found
        - detail: Validation error.
          details:
            - loc:
                - body
                - name
              msg: Field required
              type: missing
          error_code: validation_error
          retryable: false
          status: 422
          timestamp: '2026-03-31T12:00:00+00:00'
          title: Validation Error
          type: https://closient.com/docs/errors/validation_error
        - detail: Rate limit exceeded. Please try again later.
          error_code: rate_limited
          retry_after: 31
          retryable: true
          status: 429
          timestamp: '2026-03-31T12:00:00+00:00'
          title: Rate Limited
          type: https://closient.com/docs/errors/rate_limited
      properties:
        type:
          description: URI reference identifying the error type.
          title: Type
          type: string
        title:
          description: Short human-readable summary of the error.
          title: Title
          type: string
        status:
          description: HTTP status code.
          title: Status
          type: integer
        detail:
          description: Human-readable explanation of this specific occurrence.
          title: Detail
          type: string
        error_code:
          description: Machine-readable error code (e.g. not_found, unauthorized).
          title: Error Code
          type: string
        retryable:
          default: false
          description: Whether retrying the same request can succeed.
          title: Retryable
          type: boolean
        timestamp:
          description: ISO 8601 timestamp of when the error occurred.
          title: Timestamp
          type: string
        retry_after:
          anyOf:
            - type: integer
            - type: 'null'
          description: Seconds to wait before retrying (when applicable).
          title: Retry After
        owner_action_required:
          anyOf:
            - type: boolean
            - type: 'null'
          description: Whether the error requires account owner intervention.
          title: Owner Action Required
        details:
          description: Additional context (validation errors, etc.).
          title: Details
      required:
        - type
        - title
        - status
        - detail
        - error_code
        - timestamp
      title: ErrorOut
      type: object
    ScopeOut:
      description: The current chart scope — what the user is looking at.
      examples:
        - id: 8kPq2mWvNc4RtYuIoPaSdF
          name: Salsa Fresca
          type: brand
      properties:
        type:
          $ref: '#/components/schemas/ScopeTypeEnum'
          description: Kind of entity the chart is zoomed into.
        id:
          anyOf:
            - description: >-
                URL-safe 22-character shortuuid encoding of the row's UUID
                primary key. Stable across the row's lifetime; suitable for
                sharing in URLs, log lines, and external SDK clients. Accepted
                on input as either the shortuuid form or the canonical UUID form
                (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``).
              format: shortuuid
              maxLength: 22
              minLength: 22
              pattern: >-
                ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
              type: string
            - type: 'null'
          description: >-
            Identifier of the scope entity. ``null`` only when
            ``type='organization'`` and the caller's org is implied by the
            session.
          title: Id
        name:
          description: Human-readable label for the scope (org / brand / product / GTIN).
          title: Name
          type: string
      required:
        - type
        - name
      title: ScopeOut
      type: object
    BreadcrumbEntryOut:
      description: |-
        One hop in the drill-down trail.

        The frontend renders these as click-to-navigate anchors. The path
        always starts at ``organization`` and may skip ``product`` if the
        user drilled through an ungrouped trade item.
      examples:
        - name: Salsa Fresca Foods
          type: organization
        - id: 8kPq2mWvNc4RtYuIoPaSdF
          name: Salsa Fresca
          type: brand
      properties:
        type:
          $ref: '#/components/schemas/ScopeTypeEnum'
          description: Kind of entity at this hop.
        id:
          anyOf:
            - description: >-
                URL-safe 22-character shortuuid encoding of the row's UUID
                primary key. Stable across the row's lifetime; suitable for
                sharing in URLs, log lines, and external SDK clients. Accepted
                on input as either the shortuuid form or the canonical UUID form
                (``xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx``).
              format: shortuuid
              maxLength: 22
              minLength: 22
              pattern: >-
                ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
              type: string
            - type: 'null'
          description: Identifier; ``null`` for the implicit organization root.
          title: Id
        name:
          description: Display name of this hop.
          title: Name
          type: string
      required:
        - type
        - name
      title: BreadcrumbEntryOut
      type: object
    IntervalEnum:
      description: Time bucket size for the X-axis.
      enum:
        - day
        - week
        - month
      title: IntervalEnum
      type: string
    MetricEnum:
      description: |-
        Which counter the Y-axis should report.

        ``resolves`` is every scan recorded by the resolver; ``page_visits`` is
        the subset that landed on a hosted product page (the billable surface).
      enum:
        - resolves
        - page_visits
      title: MetricEnum
      type: string
    PeriodOut:
      description: Inclusive start / exclusive end of the requested time window.
      examples:
        - end: '2026-07-08T00:00:00Z'
          start: '2026-07-01T00:00:00Z'
      properties:
        start:
          description: Window start (UTC).
          format: date-time
          title: Start
          type: string
        end:
          description: Window end (UTC, exclusive).
          format: date-time
          title: End
          type: string
      required:
        - start
        - end
      title: PeriodOut
      type: object
    SeriesEntryOut:
      description: |-
        One stacked series in the chart — a child of the current scope.

        The ``type`` discriminator tells the frontend whether clicking this
        series should drill into products (``brand`` / ``product``) or stay at
        trade-item level (``trade_item``).
      examples:
        - data:
            - 12
            - 19
            - 7
            - 0
            - 23
            - 31
            - 18
          has_children: true
          id: 3nRt7yUiOpAsDfGhJkLzXc
          name: Salsa Fresca Medium 16oz
          total: 110
          type: product
      properties:
        id:
          description: Identifier of the entity this series represents.
          format: shortuuid
          maxLength: 22
          minLength: 22
          pattern: ^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$
          title: Id
          type: string
        type:
          $ref: '#/components/schemas/SeriesTypeEnum'
          description: >-
            Whether this series is a brand, a product group, or an ungrouped
            trade item.
        name:
          description: Display name (brand / product / GTIN — Product name).
          title: Name
          type: string
        data:
          description: >-
            Counts per time bucket, aligned 1:1 with the response-level
            ``timestamps`` array. Empty buckets are zero-filled, never elided.
          items:
            type: integer
          title: Data
          type: array
        total:
          description: Sum of ``data`` — convenience for the legend / tooltip.
          minimum: 0
          title: Total
          type: integer
        has_children:
          description: >-
            Whether drilling into this series will return more children.
            ``false`` at the trade-item level today; reserved ``true`` once
            batch/serial capture is wired up.
          title: Has Children
          type: boolean
      required:
        - id
        - type
        - name
        - data
        - total
        - has_children
      title: SeriesEntryOut
      type: object
    ScopeTypeEnum:
      description: |-
        The kind of entity the chart is currently zoomed into.

        The hierarchy is ``organization → brand → (product?) → trade_item``. A
        ``product`` scope is optional; a brand may contain products **and**
        ungrouped trade items side-by-side.
      enum:
        - organization
        - brand
        - product
        - trade_item
      title: ScopeTypeEnum
      type: string
    SeriesTypeEnum:
      description: >-
        Kind of entity each stacked series represents.


        Mirrors :class:`ScopeTypeEnum` minus ``organization`` because an org
        never

        appears as a child of itself. Distinguishes ``product`` from

        ``trade_item`` so the frontend knows whether the series is drillable to

        trade items (product) or to batches/serials (trade item).
      enum:
        - brand
        - product
        - trade_item
      title: SeriesTypeEnum
      type: string
  securitySchemes:
    APIKeyHeaderAuth:
      type: apiKey
      in: header
      name: X-API-Key
    OAuthTokenAuth:
      type: http
      scheme: bearer
    CookieGatedSessionAuth:
      type: apiKey
      in: cookie
      name: sessionid

````