# Call Attendance

The `QueryCallParticipantSessions` endpoint retrieves a paginated list of participant sessions for a given call session. Each entry describes an individual join/leave event for a user within that session. This data is not real-time — stats may be delayed or incomplete during an active call. The complete and final data becomes available after the call ends and the [`call.stats_report_ready`](/video/docs/api/webhooks/events/#CallStatsReportReadyEvent) event is received.

## Response overview

- `call_type`: Type of the call.
- `call_id`: ID of the call.
- `call_session_id`: ID of the call session being queried.
- `total_participant_sessions`: Total number of participant sessions in the call session.
- `total_participant_duration`: Sum of all participant durations in seconds.
- `participants_sessions`: Array of participant session details. Available for 90 days after the call session ends — after that, this array will be empty while all other response fields remain available:
  - `user_id`: ID of the user.
  - `user_session_id`: ID of the user's session.
  - `joined_at`: Timestamp when the participant joined.
  - `left_at`: Timestamp when the participant left.
  - `duration_in_seconds`: Duration of the participant's session in seconds.
  - `publisher_type`: Type of publisher (`webrtc`, `rtmp`, `srt`, etc.).
  - `roles`: Roles assigned to the participant.
- `next` / `prev`: Cursors for pagination.

<Tabs>

```js label="JavaScript"
const call = client.video.call("default", "call-id");
const response = await call.queryCallParticipantSessions({
  session: "<session-id>",
  limit: 25,
});
```

```py label="Python"
call = client.video.call("default", "call-id")
response = call.query_call_participant_sessions(session="<session-id>", limit=25)
```

```go label="Go"
call := client.Video().Call("default", "call-id")
response, err := call.QueryCallParticipantSessions(ctx, "<session-id>", &getstream.QueryCallParticipantSessionsRequest{
  Limit: getstream.PtrTo(25),
})
if err != nil {
  return err
}
```

```java label="Java"
var call = client.video().call("default", "call-id");
var response = call.queryCallParticipantSessions(
  "<session-id>",
  QueryCallParticipantSessionsRequest.builder().limit(25).build()
);
```

```bash label="Bash"
curl -X GET "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}/session/${SESSION_ID}/participant_sessions?api_key=${API_KEY}&limit=25" \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt"
```

</Tabs>

## Filtering

You can narrow down results using `filter_conditions`. Filters follow the same syntax as other query endpoints. Note that filters only affect the `participants_sessions` list — aggregated fields such as `total_participant_sessions` and `total_participant_duration` always reflect the full call session.

<Tabs>

```js label="JavaScript"
const response = await call.queryCallParticipantSessions({
  session: "<session-id>",
  filter_conditions: { user_id: { $eq: "alice" } },
});
```

```py label="Python"
response = call.query_call_participant_sessions(
  session="<session-id>",
  filter_conditions={"user_id": {"$eq": "alice"}},
)
```

```go label="Go"
response, err := call.QueryCallParticipantSessions(ctx, "<session-id>", &getstream.QueryCallParticipantSessionsRequest{
  FilterConditions: map[string]interface{}{
    "user_id": map[string]interface{}{"$eq": "alice"},
  },
})
```

```java label="Java"
var response = call.queryCallParticipantSessions(
  "<session-id>",
  QueryCallParticipantSessionsRequest.builder()
    .filterConditions(Map.of("user_id", Map.of("$eq", "alice")))
    .build()
);
```

```bash label="Bash"
curl -X GET "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}/session/${SESSION_ID}/participant_sessions?api_key=${API_KEY}&filter_conditions%5Buser_id%5D%5B%24eq%5D=alice" \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt"
```

</Tabs>

To compute aggregated values (such as total duration or session count) for a filtered subset of participants, paginate through all pages and accumulate the results manually:

<Tabs>

```js label="JavaScript"
const filter_conditions = { user_id: { $eq: "alice" } };
let cursor;
let totalDuration = 0;
let totalSessions = 0;

do {
  const response = await call.queryCallParticipantSessions({
    session: "<session-id>",
    limit: 100,
    next: cursor,
    filter_conditions,
  });
  for (const s of response.data.participants_sessions) {
    totalDuration += s.duration_in_seconds;
    totalSessions += 1;
  }
  cursor = response.data.next;
} while (cursor);
```

```py label="Python"
filter_conditions = {"user_id": {"$eq": "alice"}}
cursor = None
total_duration = 0
total_sessions = 0

while True:
  response = call.query_call_participant_sessions(
    session="<session-id>", limit=100, next=cursor,
    filter_conditions=filter_conditions,
  )
  for s in response.data.participants_sessions:
    total_duration += s.duration_in_seconds
    total_sessions += 1
  cursor = response.data.next
  if not cursor:
    break
```

```go label="Go"
var cursor *string
totalDuration := 0
totalSessions := 0

for {
  response, err := call.QueryCallParticipantSessions(ctx, "<session-id>", &getstream.QueryCallParticipantSessionsRequest{
    Limit: getstream.PtrTo(100),
    Next:  cursor,
    FilterConditions: map[string]interface{}{
      "user_id": map[string]interface{}{"$eq": "alice"},
    },
  })
  if err != nil {
    return err
  }
  for _, s := range response.Data.ParticipantsSessions {
    totalDuration += s.DurationInSeconds
    totalSessions++
  }
  cursor = response.Data.Next
  if cursor == nil {
    break
  }
}
```

```java label="Java"
String cursor = null;
long totalDuration = 0;
int totalSessions = 0;

do {
  var response = call.queryCallParticipantSessions(
    "<session-id>",
    QueryCallParticipantSessionsRequest.builder()
      .limit(100)
      .next(cursor)
      .filterConditions(Map.of("user_id", Map.of("$eq", "alice")))
      .build()
  );
  for (var s : response.getData().getParticipantsSessions()) {
    totalDuration += s.getDurationInSeconds();
    totalSessions++;
  }
  cursor = response.getData().getNext();
} while (cursor != null);
```

</Tabs>

## Pagination

Use `next` and `prev` cursors from the response to paginate through results.

<Tabs>

```js label="JavaScript"
let cursor;
do {
  const response = await call.queryCallParticipantSessions({
    session: "<session-id>",
    limit: 25,
    next: cursor,
  });
  cursor = response.data.next;
} while (cursor);
```

```py label="Python"
cursor = None
while True:
  response = call.query_call_participant_sessions(
    session="<session-id>", limit=25, next=cursor
  )
  cursor = response.data.next
  if not cursor:
    break
```

```go label="Go"
var cursor *string
for {
  response, err := call.QueryCallParticipantSessions(ctx, "<session-id>", &getstream.QueryCallParticipantSessionsRequest{
    Limit: getstream.PtrTo(25),
    Next:  cursor,
  })
  if err != nil {
    return err
  }
  cursor = response.Data.Next
  if cursor == nil {
    break
  }
}
```

```java label="Java"
String cursor = null;
do {
  var response = call.queryCallParticipantSessions(
    "<session-id>",
    QueryCallParticipantSessionsRequest.builder().limit(25).next(cursor).build()
  );
  cursor = response.getData().getNext();
} while (cursor != null);
```

```bash label="Bash"
curl -X GET "https://video.stream-io-api.com/api/v2/video/call/${CALL_TYPE}/${CALL_ID}/session/${SESSION_ID}/participant_sessions?api_key=${API_KEY}&limit=25&next=${NEXT_CURSOR}" \
  -H "Authorization: ${TOKEN}" \
  -H "stream-auth-type: jwt"
```

</Tabs>

---

This page was last updated at 2026-06-09T15:44:32.680Z.

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react-native/analytics/call-attendance/](https://getstream.io/video/docs/react-native/analytics/call-attendance/).