# Live Unique Users — Frontend Integration
Returns the **distinct logged-in users who watched a video while it was live**, with their name/email when known. **Paginated.** Intended for the **admin** analytics dashboard.
---
## Endpoint
```
GET /admin/v1/video-analytics/live-unique-users/{videoId}?page=1&pageSize=15
```
- **Method:** `GET`
- **Auth:** Admin only — same authorization as the other `/admin/v1/video-analytics/*` endpoints (e.g. `live-viewers`, `totals`, `graph`). Send the same admin credentials/headers you already use for those. A non-admin / unauthenticated call returns **401**.
- **Base URL:** same host you already use for the admin API (per environment).
### Path parameters
| Name | Type | Description |
|------|------|-------------|
| `videoId` | string | The video id. |
### Query parameters
| Name | Type | Default | Notes |
|------|------|---------|-------|
| `page` | number | `1` | 1-based page number. Values `<= 0` are treated as `1`. |
| `pageSize` | number | `15` | Items per page. **Max `50`** (larger values are clamped to 50; `<= 0` becomes 1). |
---
## Response `200 OK`
Standard pagination envelope (`{ items, totalCount }`):
```jsonc
{
"items": [
{
"userId": "14a29c05-1983-4b14-ba98-a88202bd9493",
"name": "Riad Asllani",
"email": "riad@gjirafa.com",
"portaLink": null
},
{
"userId": "2f59d36d-e479-40bc-bc29-e6e398c40bd3",
"name": null,
"email": null,
"portaLink": "https://porta-gjirafainc-admin.gjirafa.com/users/2f59d36d-e479-40bc-bc29-e6e398c40bd3/user-details"
}
],
"totalCount": 30
}
```
### Top-level fields
| Field | Type | Notes |
|-------|------|-------|
| `totalCount` | number | **Total** distinct logged-in users who watched while live (the full count, not just this page). Use it to compute the number of pages: `Math.ceil(totalCount / pageSize)`. |
| `items` | array | Users for the **requested page only**. |
### `items[]` fields
| Field | Type | Notes |
|-------|------|-------|
| `userId` | string | The user's id (Porta/account id). Always present. |
| `name` | string \| null | Full name. **`null` when the user was not found** in the internal users DB. |
| `email` | string \| null | Email. **`null` when not found** internally. |
| `portaLink` | string \| null | **Set only when `name`/`email` are `null`.** Link to look the user up manually in the Porta admin portal. |
---
## How the frontend should render a row
For each item in `items`:
- **If `name` / `email` are present** → show them normally.
- **If they're `null` and `portaLink` is set** → the user isn't in our DB. Render the `userId` and make it (or a "View in Porta" button) link to `portaLink` (open in a new tab) so an admin can look up the details manually.
```jsx
items.map(u =>
u.email
?
:
)
// pages
const totalPages = Math.ceil(totalCount / pageSize);
```
---
## Behavior notes
- **Caching:** results are cached server-side for **~2 minutes**, per `(videoId, page, pageSize)`. Numbers refresh at most every couple of minutes (consistent across requests in that window). The metric is cumulative for the broadcast, so `totalCount` only grows.
- **Invalid or unknown `videoId`:** returns `200` with `{ "items": [], "totalCount": 0 }` (not a 404).
- **Page out of range:** returns `200` with `items: []` and the real `totalCount`.
- **Errors:** unexpected failures return `400` with an error body (same shape as other admin analytics endpoints).
---
## Examples
```bash
# first page (default page=1, pageSize=15)
curl -X GET "{BASE_URL}/admin/v1/video-analytics/live-unique-users/aZjMMPomjV" \
-H "Authorization: "
# page 2, 25 per page
curl -X GET "{BASE_URL}/admin/v1/video-analytics/live-unique-users/aZjMMPomjV?page=2&pageSize=25" \
-H "Authorization: "
```