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

# Get Active Users

> Query the number of distinct active users for your team, with optional time granularity and per-user grouping.

<Note>
  This is a **v2 endpoint** that uses Bearer token authentication and query parameters, unlike the v1 Analytics API which uses service keys in the request body. See [Authentication](#authentication) below.
</Note>

<Warning>
  This endpoint is **not** intended for real-time usage monitoring. Data is hourly-aggregated and the
  rate limit is low (10 requests per hour per team). Use it for periodic reporting and bulk export.
</Warning>

## Authentication

This endpoint uses **Bearer token** authentication. Include your service key in the `Authorization` header:

```
Authorization: Bearer <your_service_key>
```

The service key must have the **Analytics Read** permission. Create one in your [team settings](https://windsurf.com/team/settings) under "Service Keys".

## What counts as an active user

A user is counted as **active** for a given time bucket if they have any billing event in it. The
`active_users` field reports the count of distinct users.

<Note>
  Active user counts include usage from **both the Devin CLI and Devin Desktop**. A user who is
  active in either (or both) is counted, and they are only counted once per time bucket.
</Note>

## Grouping and Granularity

Use `granularity` and `group_by` to control the shape of returned data:

* **No granularity or grouping** — returns a single row with the total active user count for the entire date range
* **`granularity=daily`** — each row includes a `timestamp` in `YYYY-MM-DD` format (daily active users)
* **`granularity=monthly`** — each row includes a `timestamp` in `YYYY-MM` format (monthly active users)
* **`group_by=user`** — returns one row per active user with a `user_id`; each row's `active_users` is `1`

<Note>
  Unlike [Get Consumption](/desktop/accounts/api-reference/get-consumption), the active users endpoint
  only supports `user` for `group_by`. Other dimensions (`model_uid`, `ide`) are not valid here.
</Note>

## Pagination

Results are paginated with a default page size of 1,000 rows (max 10,000). When more results are available,
the response includes a `next_page_cursor` in the `pagination` object. Pass it as the `page_cursor` query
parameter to fetch the next page.

Page cursors expire after 24 hours. A follow-up page request does not count as a new query against your rate limit.

## Caching

Responses include an `ETag` header. To avoid redundant data transfer, include the `If-None-Match` header
with the previous `ETag` value — the server will return `304 Not Modified` if the data has not changed.

## Rate Limits

This endpoint is rate-limited to **10 requests per hour** per team. If you exceed this limit, the
server returns `429 Too Many Requests` with a `Retry-After` header.

Paginating an earlier query (following a `next_page_cursor`) does **not** count against this limit —
only the initial query for each report does. The low limit reflects that this endpoint is for
periodic reporting, not real-time usage monitoring.


## OpenAPI

````yaml desktop/accounts/api-reference/analytics-v2-openapi.yaml GET /api/v2alpha/analytics/active-users
openapi: 3.1.0
info:
  title: Devin Desktop Analytics API v2
  version: 2.0.0
  description: >
    The Analytics API v2 provides credit and ACU consumption analytics for
    enterprise teams.

    Data is sourced from hourly-aggregated billing events and supports flexible
    filtering, grouping,

    and cursor-based pagination.
servers:
  - url: https://server.codeium.com
security:
  - bearerAuth: []
paths:
  /api/v2alpha/analytics/active-users:
    get:
      summary: Get active users analytics
      description: >
        Query the number of distinct active users for the authenticated team. A
        user is counted as

        active for a time bucket if they have any billing event in it. Results
        are sourced from

        hourly-aggregated billing events and can be filtered by date range,
        product, model, and group.


        Use `granularity` to break the count down per day or month, and
        `group_by=user` to return one

        row per user (each row's `active_users` will be `1`).


        Responses are cached for 1 hour. Use `If-None-Match` with a previously
        returned `ETag` to

        receive a `304 Not Modified` when the data has not changed.


        These endpoints are designed for periodic reporting and bulk export.
        They are **not** intended for real-time usage monitoring: data is
        hourly-aggregated and the rate limit is low (10 requests per hour per
        team).
      operationId: getActiveUsers
      parameters:
        - name: start_date
          in: query
          required: true
          schema:
            type: string
            format: date
          description: Start of the date range (inclusive) in `YYYY-MM-DD` format.
          example: '2026-01-01'
        - name: end_date
          in: query
          required: true
          schema:
            type: string
            format: date
          description: >-
            End of the date range (inclusive) in `YYYY-MM-DD` format. The range
            must not exceed 90 days.
          example: '2026-01-31'
        - name: product
          in: query
          required: true
          schema:
            type: string
            enum:
              - agent
          description: Product to query active users for.
          example: agent
        - name: granularity
          in: query
          required: false
          schema:
            type: string
            enum:
              - daily
              - monthly
          description: >
            Time granularity for grouping results. When specified, each row
            includes a `timestamp` field.

            If omitted, the active user count is aggregated across the entire
            date range.
        - name: group_by
          in: query
          required: false
          schema:
            type: string
            enum:
              - user
          description: >
            Dimension to group results by. The active users endpoint only
            supports `user`, which returns

            one row per active user (each with `active_users` = `1`).
          example: user
        - name: models
          in: query
          required: false
          schema:
            type: string
          description: Comma-separated list of model UIDs to filter results to.
          example: claude-4-sonnet,gpt-4.1
        - name: group_id
          in: query
          required: false
          schema:
            type: string
          description: >-
            Filter results to users in a specific group. The service key must
            have access to this group.
        - name: user_id
          in: query
          required: false
          schema:
            type: string
          description: Filter results to a specific user (auth UID).
        - name: page_size
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 10000
            default: 1000
          description: Maximum number of rows to return per page.
        - name: page_cursor
          in: query
          required: false
          schema:
            type: string
          description: >-
            Opaque cursor from a previous response's
            `pagination.next_page_cursor` to fetch the next page.
        - name: If-None-Match
          in: header
          required: false
          schema:
            type: string
          description: >-
            ETag value from a previous response. If the data has not changed,
            the server returns `304 Not Modified`.
      responses:
        '200':
          description: Active users data returned successfully.
          headers:
            ETag:
              schema:
                type: string
              description: >-
                Entity tag for cache validation. Pass this as `If-None-Match` in
                subsequent requests.
            Cache-Control:
              schema:
                type: string
              description: Cache directive (e.g., `private, max-age=3600`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActiveUsersResponse'
              examples:
                aggregate:
                  summary: Total active users over the date range
                  value:
                    data:
                      - active_users: 142
                    pagination:
                      next_page_cursor: null
                    metadata:
                      data_freshness: '2026-01-16T03:00:00Z'
                      query_time_ms: 612
                      team_id: team_abc123
                daily:
                  summary: Daily active users
                  value:
                    data:
                      - timestamp: '2026-01-15'
                        active_users: 138
                      - timestamp: '2026-01-16'
                        active_users: 145
                    pagination:
                      next_page_cursor: null
                    metadata:
                      data_freshness: '2026-01-16T03:00:00Z'
                      query_time_ms: 734
                      team_id: team_abc123
                by_user:
                  summary: Grouped by user
                  value:
                    data:
                      - user_id: user_abc123
                        active_users: 1
                      - user_id: user_def456
                        active_users: 1
                    pagination:
                      next_page_cursor: null
                    metadata:
                      data_freshness: '2026-01-16T03:00:00Z'
                      query_time_ms: 845
                      team_id: team_abc123
        '304':
          description: Data has not changed since the ETag provided in `If-None-Match`.
        '400':
          description: Invalid request parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_param:
                  value:
                    error: start_date is required
                bad_group_by:
                  value:
                    error: 'unsupported group_by dimension for active-users: model_uid'
                bad_product:
                  value:
                    error: 'unsupported product: foo (supported: agent)'
        '401':
          description: Authentication failed or insufficient permissions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_auth:
                  value:
                    error: missing Authorization header
                invalid_key:
                  value:
                    error: invalid service key
                insufficient_permissions:
                  value:
                    error: insufficient permissions
        '403':
          description: >-
            The supplied page cursor does not belong to the authenticated team
            or requested group.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                cursor_team_mismatch:
                  value:
                    error: page cursor does not belong to this team
        '405':
          description: HTTP method not allowed (only `GET` is supported).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            Rate limit exceeded (10 requests per hour per team). Paginating an
            earlier query does not count against this limit.
          headers:
            Retry-After:
              schema:
                type: string
              description: Suggested wait time before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                rate_limited:
                  value:
                    error: rate limit exceeded
        '503':
          description: >-
            Analytics service is not available (e.g., in self-hosted
            deployments).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - bearerAuth: []
components:
  schemas:
    ActiveUsersResponse:
      type: object
      required:
        - data
        - pagination
        - metadata
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ActiveUsersRow'
          description: Array of active users data rows.
        pagination:
          type: object
          properties:
            next_page_cursor:
              type:
                - string
                - 'null'
              description: >
                Opaque cursor for fetching the next page of results. Pass this
                value as the `page_cursor`

                query parameter in a follow-up request. `null` when there are no
                more pages.

                Page cursors expire after 24 hours.
        metadata:
          type: object
          properties:
            data_freshness:
              type: string
              format: date-time
              description: >-
                Timestamp indicating when the underlying data was last refreshed
                (truncated to the hour).
            query_time_ms:
              type: integer
              format: int64
              description: Server-side query execution time in milliseconds.
            team_id:
              type: string
              description: The team ID resolved from the authenticated service key.
            group_id:
              type: string
              description: >-
                The group ID the results were scoped to. Only present when
                `group_id` was supplied.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable error message.
    ActiveUsersRow:
      type: object
      required:
        - active_users
      properties:
        timestamp:
          type: string
          description: >
            Time bucket for the row. Format depends on `granularity`:
            `YYYY-MM-DD` for daily, `YYYY-MM` for monthly.

            Only present when `granularity` is specified.
          examples:
            - '2026-05-01'
            - 2026-05
        user_id:
          type: string
          description: >-
            User identifier (auth UID). Only present when `group_by` includes
            `user`.
        active_users:
          type: integer
          format: int64
          description: >
            Count of distinct active users in the row. A user is counted as
            active for a time bucket if

            they have any billing event in it. When grouped by `user`, this is
            always `1`.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        A service key with **Analytics Read** permission, passed as a Bearer
        token in the `Authorization` header.


        Create a service key in your [team
        settings](https://windsurf.com/team/settings) under the "Service Keys"
        section.

````