---
title: "Get one user's device and activity stats"
method: GET
path: "/api/v1/stats/users/{user_id}"
tags: ["Stats"]
---

# Get one user's device and activity stats

`GET /api/v1/stats/users/{user_id}`

## Request

- Base URL: `https://api.neosapien.xyz` (the document declares no server; this is the origin it was published from)
- URL: `https://api.neosapien.xyz/api/v1/stats/users/{user_id}`
- Auth: HTTP bearer

One user's active time, memory totals and current device.

Use the existing organization bearer token and discover user IDs with
`GET /api/v1/users`. Unknown and inaccessible users both return 404.
Scoped dashboard logins receive 403. Limited to 120 requests per minute.

Dates are inclusive Asia/Kolkata calendar dates within the last 7 days;
omit both for today. With start_date only, the range ends today. With
end_date only, the range covers that single day. Longer ranges return 400.

Active time merges connected presence and recordings within each day's
window: the whole IST day (00:00-24:00) unless your organization has a
different window configured. The window is reported as workday_start and
workday_end, and per day as window_start and window_end. active_time.days
lists each date with its total and its activity intervals: when the device
was connected or recording, including time connected without recording,
merged so nothing is counted twice and clipped to the day's window. A day's
interval durations sum to its device_active_seconds.

Reconstruction needs about 25 hours of history before the day starts: today
minus 6 is always incomplete, and today minus 5 becomes incomplete around
23:00 IST. Missing source days make the range total null with
`state: incomplete` and are listed in missing_dates; those days have null
totals and null intervals, while the other days are still reported. Zero
is a reconstructed zero. Values are telemetry-based estimates.

Memory totals filter created_at, exclude archived/deleted memories and
count positive recording duration. Sales duration uses the domains array.
Device status and battery describe the current primary device even for
historical requests. Battery and device can be null. Source outages return
503 rather than fabricated zeros; battery lookup failure only clears battery.

Poll per user, spread requests across the hour and use bounded concurrency.
This endpoint performs no domain writes and does not add authentication
scopes or credentials.

## Path parameters

- `user_id` string, required

## Query parameters

- `start_date` string, nullable: First inclusive IST date in YYYY-MM-DD format, within today and the preceding 6 days. Defaults to end_date when only end_date is provided, otherwise today.
- `end_date` string, nullable: Last inclusive IST date in YYYY-MM-DD format. Defaults to today. Must not precede start_date or be in the future.

## Response `200`

Successful Response

- UserStatsResponse
  - `user_id` string, required
  - `name` string, nullable: Display name, if the profile has one
  - `start_date` string, date, required: First IST date, inclusive
  - `end_date` string, date, required: Last IST date, inclusive
  - `generated_at` string, date-time, required: When this response was built
  - `active_time` ActiveTime, required: Device active time over the requested days, with how far it can be trusted. ``device_active_seconds`` is null when any requested day could not be reconstructed; 0 is a real, reconstructed zero.
    - `device_active_seconds` integer, nullable: Seconds of active time; null if any day is incomplete
    - `state` 'provisional' | 'settled' | 'incomplete', required: `incomplete` if any day's source presence has expired, else `provisional` until every day is 6 h past its end, else `settled`. Settled describes the source at this computation cutoff; later recording uploads or corrections can revise a subsequent response.
    - `missing_dates` string[]: IST dates that could not be reconstructed
    - `computed_at` string, date-time, required: Cutoff the value was computed at
    - `definition_version` integer, required: Bumped whenever the active-time rules change the numbers
    - `timezone` string: Timezone of the workday
    - `workday_start` string, time, required: Daily window start (local time)
    - `workday_end` string, required: Daily window end (local time, exclusive); `24:00:00` is the end of the day
    - `days` DayActiveTime[]: Each requested date, oldest first
      - `date` string, date, required: IST calendar date
      - `window_start` string, date-time, required: Start of the day's counted window, with IST offset
      - `window_end` string, date-time, required: End of the day's counted window, with IST offset
      - `device_active_seconds` integer, nullable: Seconds of active time; null if the day is incomplete
      - `state` 'provisional' | 'settled' | 'incomplete', required: `incomplete` if the day's source presence has expired, else `provisional` until the day is 6 h past its end, else `settled`
      - `intervals` ActiveInterval[], nullable: When the device was active, oldest first; null if the day is incomplete, empty for a reconstructed zero
        - `start` string, date-time, required: Inclusive start, with IST offset
        - `end` string, date-time, required: Exclusive end, with IST offset
        - `duration_seconds` number, required: end - start in seconds, to the millisecond
        - `in_progress` boolean, required: The device was still active at `computed_at`, so `end` is the computation cutoff rather than when the activity ended
  - `memory_count` integer, required
  - `memory_duration_seconds` integer, required
  - `sales_memory_duration_seconds` integer, required
  - `device` DeviceStatus: The user's current primary device. Always current, whatever the range.
    - `device_alias` string, required: Organization-scoped device alias
    - `last_online` string, date-time, nullable: Last audio upload from the device (UTC)
    - `status` 'online' | 'offline', required: `online` if the device uploaded audio in the last 2 minutes
    - `battery_pct` integer, nullable
    - `battery_reported_at` string, date-time, nullable: When the battery reading was reported (UTC)

## Other responses

- `400`: Malformed, reversed, future or too-old dates
- `401`: Missing, invalid or expired bearer token
- `403`: Not available for scoped logins
- `404`: User not found in your organization
- `405`: Only GET is supported
- `422`: Validation Error
- `429`: More than 120 requests per minute; back off
- `503`: A data source is unavailable

## Changes

- **2026-10-30** `04eb6fb25d56`: 1 breaking, 2 info
  - the `active_time/workday_end` response's property format changed from `time` to no format for status `200`
  - added the optional property `active_time/days` to the response with the `200` status
  - the `active_time/workday_end` response's property pattern `^(([01]\d|2[0-3]):[0-5]\d:[0-5]\d|24:00:00)$` was added for the status `200`
- **2026-10-24** `885905813bca`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/neosapien/apis/neocore/changes/api/v1/stats/users/:user_id/get.md)

---

[API](https://skmtc.dev/neosapien/apis/neocore.md) - [All operations](https://skmtc.dev/neosapien/apis/neocore/llms.txt) - [OpenAPI document](https://skmtc.dev/neosapien/apis/neocore/revisions/a42bfdafa6fe?raw)
