# Query sessions and turns

```json metadata
{
  "schema_version": 2,
  "title": "Query sessions and turns",
  "description": "List agents and sessions, and read turns and their captured content with the Lens read API.",
  "categories": ["docs","develop","ai"],
  "tableOfContents": {"sections":[{"id":"authenticate","title":"Authenticate"},{"id":"list-agents","title":"List agents"},{"id":"list-sessions","title":"List sessions"},{"id":"get-a-session","title":"Get a session"},{"id":"get-a-turn","title":"Get a turn"},{"id":"get-captured-content","title":"Get captured content"},{"id":"get-a-context-gateway-tool-result","title":"Get a Context Gateway tool result"},{"id":"next-steps","title":"Next steps"}]}

,
  "codeExamples": [{"codetabsId":"iris_lens_read_api-stepconnect","difficulty":"beginner","id":"connect","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-stepconnect"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-stepconnect"}]},{"codetabsId":"iris_lens_read_api-steplist_agents","difficulty":"beginner","id":"list_agents","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-steplist_agents"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-steplist_agents"}]},{"codetabsId":"iris_lens_read_api-steplist_sessions","difficulty":"beginner","id":"list_sessions","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-steplist_sessions"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-steplist_sessions"}]},{"codetabsId":"iris_lens_read_api-stepget_session","difficulty":"beginner","id":"get_session","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-stepget_session"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-stepget_session"}]},{"codetabsId":"iris_lens_read_api-stepget_turn","difficulty":"beginner","id":"get_turn","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-stepget_turn"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-stepget_turn"}]},{"codetabsId":"iris_lens_read_api-stepget_content","difficulty":"beginner","id":"get_content","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-stepget_content"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-stepget_content"}]},{"codetabsId":"iris_lens_read_api-stepgateway_result","difficulty":"beginner","id":"gateway_result","languages":[{"clientId":"iris-python","clientName":"Python (Iris)","id":"Python (Iris)","langId":"python","panelId":"panel_Python (Iris)_iris_lens_read_api-stepgateway_result"},{"clientId":"iris-curl","clientName":"cURL (Iris)","id":"cURL (Iris)","langId":"bash","panelId":"panel_cURL (Iris)_iris_lens_read_api-stepgateway_result"}]}]
}
```

## Code examples

These examples connect to Iris services using SDKs or HTTP requests with curl. Follow one language throughout each walkthrough.


Use the Lens read application programming interface (API) to list sessions and read turns from code, for example to export failed turns or build your own reports. The Python examples use the Lens read client:

```bash
python -m pip install 'redis-agent-lens>=1.0.0'
```

The cURL examples use Bash and jq. Every operation is a `GET` request.

## Authenticate

Export the Iris host, your instance ID, and a read key:

```bash
export LENS_URL='<iris-host>'
export LENS_INSTANCE_ID='<lens-instance-id>'
export LENS_API_KEY='<lens-read-key>'
```

The read client reads its key from `LENS_API_KEY` and sends it as a bearer token. Every operation is scoped to your instance:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
import os

from redis_agent_lens import Lens

client = Lens(
    server_url=os.environ["LENS_URL"],
    instance_id=os.environ["LENS_INSTANCE_ID"],
)
```

##### cURL (Iris)

```bash
LENS_API="${LENS_URL%/}/iris/data/v1beta1/lens/instances/$LENS_INSTANCE_ID"
AUTH="Authorization: Bearer $LENS_API_KEY"
```



## List agents

List every agent with at least one retained session:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
agents = client.list_agents()
for agent in agents.items:
    print(agent.agent_id)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error "$LENS_API/agents" -H "$AUTH" \
  | jq -r '.items[].agentId'
```



## List sessions

List one agent's sessions over a time range, newest activity first. This example lists the sessions with a failed turn in the last 24 hours, then follows the cursor to the next page:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
page = client.list_sessions(agent_id="support-bot", range="24h", outcome=["FAILED"])
print(f"Showing {page.effective_range} of sessions")

sessions = list(page.items)
while page.next_cursor:
    page = client.list_sessions(
        agent_id="support-bot", range="24h", outcome=["FAILED"], cursor=page.next_cursor
    )
    sessions.extend(page.items)

for session in sessions:
    print(session.session_id, session.turn_count, session.last_seen)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error -G "$LENS_API/sessions" -H "$AUTH" \
  --data-urlencode "agentId=support-bot" \
  --data-urlencode "range=24h" \
  --data-urlencode "outcome=FAILED" > sessions.json
jq '{effectiveRange, nextCursor, sessions: [.items[] | {sessionId, turnCount, lastSeen}]}' sessions.json
SESSION_ID=$(jq -er '.items[0].sessionId' sessions.json)
```



| Parameter | Description |
| --------- | ----------- |
| `agentId` | Required. The agent whose sessions to list. |
| `range` | Required. A lookback duration such as `24h`, `7d`, or `30d`. |
| `sessionIdPrefix` | Sessions whose ID starts with this value, ignoring case. At least 2 characters. |
| `userId` | Sessions with this user ID, ignoring case. |
| `outcome` | Sessions with a turn that has any of these outcomes. Use `UNSET` for turns without an outcome. |
| `limit` | Sessions per page, up to 200. Default 50. |
| `cursor` | The `nextCursor` value from the previous page. |

The Python client uses the same parameters in snake_case, such as `agent_id` and `session_id_prefix`.

Lens fits the range to your instance's retention. The response returns the range it used as `effectiveRange`, and the ranges your instance supports as `availableRanges`.

To read the next page, pass `nextCursor` with the same filters. When `nextCursor` is absent, you have the last page. A cursor expires after 5 minutes without use; start again from the first page.

## Get a session

Read a session's threads, turn summaries, and outcome counts:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
if not sessions:
    raise SystemExit("No matching sessions in the last 24 hours")

session = client.get_session(session_id=sessions[0].session_id)
for summary in session.turns:
    outcome = summary.outcome.value if summary.outcome else "no outcome"
    print(summary.trace_id, outcome, summary.duration_ms, summary.tokens.total)
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error "$LENS_API/sessions/$SESSION_ID" -H "$AUTH" > session.json
jq '[.turns[] | {traceId, outcome, durationMs, tokens: .tokens.total}]' session.json
TURN_ID=$(jq -er '.turns[0].traceId' session.json)
```



Each turn summary includes the turn ID, outcome, duration, number of steps, and token counts.

## Get a turn

Read one turn by its ID. The response includes the turn's steps, token counts, outcome, and its **context manifest**: one entry per retrieval step, with what it fetched and what the prompt used.

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
turn = client.get_turn(turn_id=session.turns[0].trace_id)
for entry in turn.manifest.entries:
    print(f"{entry.source}: {entry.state.value}, fetched {entry.fetched_count}, used {entry.used_count}")
    for candidate in entry.candidates:
        print(f"  {candidate.id} score={candidate.score} used={candidate.used}")
```

##### cURL (Iris)

```bash
curl --fail-with-body --silent --show-error "$LENS_API/turns/$TURN_ID" -H "$AUTH" > turn.json
jq '[.manifest.entries[] | {source, state, fetchedCount, usedCount}]' turn.json
```



Each manifest entry has one of these states:

| State | Meaning |
| ----- | ------- |
| `ENTERED_PROMPT` | At least one result entered the prompt. |
| `FETCHED_NOT_USED` | The source returned results, and none entered the prompt. |
| `INVOKED_EMPTY` | The source returned no results. |
| `NOT_INSTRUMENTED` | The turn recorded no prompt assembly, or the results had no text to match. |

Each candidate in an entry includes its ID, score where the source provides one, and whether it entered the prompt.

## Get captured content

Captured text, such as a retrieved document or a model prompt, is stored by content hash. Read it with the turn ID and the hash from the turn:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
for entry in turn.manifest.entries:
    for candidate in entry.candidates:
        if candidate.content and candidate.content.hash:
            content = client.get_content(turn_id=turn.trace_id, hash=candidate.content.hash)
            print(candidate.id, content.availability.value, content.body)
```

##### cURL (Iris)

```bash
HASH=$(jq -r '.manifest.entries[0].candidates[0].content.hash // empty' turn.json)
if [ -n "$HASH" ]; then
  curl --fail-with-body --silent --show-error "$LENS_API/turns/$TURN_ID/content/$HASH" -H "$AUTH" \
    | jq '{availability, body}'
fi
```



The response's `availability` is `AVAILABLE` with the text in `body`, or says why the text isn't available, such as `EXPIRED` or `NOT_CAPTURED`. See [retention](https://redis.io/docs/latest/develop/ai/context-engine/lens/concepts#retention).

## Get a Context Gateway tool result

Read the result of a Context Gateway tool call in a turn, by the step's ID. The turn lists these steps in `contextRetrieverToolSteps`:

**Available in:** Python (Iris), cURL (Iris)

##### Python (Iris)

```python
for step in turn.context_retriever_tool_steps:
    result = client.get_context_retriever_result(turn_id=turn.trace_id, span_id=step.span_id)
    print(step.tool_name, result.outcome.value, result.classification.value)
```

##### cURL (Iris)

```bash
for SPAN_ID in $(jq -r '.contextRetrieverToolSteps[].spanId' turn.json); do
  curl --fail-with-body --silent --show-error \
    "$LENS_API/turns/$TURN_ID/tools/$SPAN_ID/result" -H "$AUTH" \
    | jq '{spanId, outcome, classification}'
done
```



The result classifies what the tool returned, such as `RECORDS` or `AGGREGATE`, and includes the returned data.

## Next steps

- See the [API reference](https://redis.io/docs/latest/develop/ai/context-engine/lens/api-reference) for every field.
- See [Limits and errors](https://redis.io/docs/latest/develop/ai/context-engine/lens/limits) for request limits and error responses.

