{
  "schema_version": 2,
  "id": "develop/ai/context-engine/lens/read-api",
  "title": "Query sessions and turns",
  "url": "https://redis.io/docs/latest/develop/ai/context-engine/lens/read-api/",
  "summary": "List agents and sessions, and read turns and their captured content with the Lens read API.",
  "tags": [
    "docs",
    "develop",
    "ai"
  ],
  "last_updated": "2026-11-03T15:26:57-08:00",
  "page_type": "content",
  "content_hash": "773f8cc4eb9c61ac624299f8f67062788d6731a5b1636364116cfb4e7564ed05",
  "sections": [
    {
      "id": "code-examples",
      "title": "Code examples",
      "role": "example",
      "text": "These examples connect to Iris services using SDKs or HTTP requests with curl. Follow one language throughout each walkthrough.\n\n\nUse 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:\n\n[code example]\n\nThe cURL examples use Bash and jq. Every operation is a `GET` request."
    },
    {
      "id": "authenticate",
      "title": "Authenticate",
      "role": "security",
      "text": "Export the Iris host, your instance ID, and a read key:\n\n[code example]\n\nThe read client reads its key from `LENS_API_KEY` and sends it as a bearer token. Every operation is scoped to your instance:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]"
    },
    {
      "id": "list-agents",
      "title": "List agents",
      "role": "content",
      "text": "List every agent with at least one retained session:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]"
    },
    {
      "id": "list-sessions",
      "title": "List sessions",
      "role": "content",
      "text": "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:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\n| Parameter | Description |\n| --------- | ----------- |\n| `agentId` | Required. The agent whose sessions to list. |\n| `range` | Required. A lookback duration such as `24h`, `7d`, or `30d`. |\n| `sessionIdPrefix` | Sessions whose ID starts with this value, ignoring case. At least 2 characters. |\n| `userId` | Sessions with this user ID, ignoring case. |\n| `outcome` | Sessions with a turn that has any of these outcomes. Use `UNSET` for turns without an outcome. |\n| `limit` | Sessions per page, up to 200. Default 50. |\n| `cursor` | The `nextCursor` value from the previous page. |\n\nThe Python client uses the same parameters in snake_case, such as `agent_id` and `session_id_prefix`.\n\nLens 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`.\n\nTo 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."
    },
    {
      "id": "get-a-session",
      "title": "Get a session",
      "role": "content",
      "text": "Read a session's threads, turn summaries, and outcome counts:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nEach turn summary includes the turn ID, outcome, duration, number of steps, and token counts."
    },
    {
      "id": "get-a-turn",
      "title": "Get a turn",
      "role": "content",
      "text": "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.\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nEach manifest entry has one of these states:\n\n| State | Meaning |\n| ----- | ------- |\n| `ENTERED_PROMPT` | At least one result entered the prompt. |\n| `FETCHED_NOT_USED` | The source returned results, and none entered the prompt. |\n| `INVOKED_EMPTY` | The source returned no results. |\n| `NOT_INSTRUMENTED` | The turn recorded no prompt assembly, or the results had no text to match. |\n\nEach candidate in an entry includes its ID, score where the source provides one, and whether it entered the prompt."
    },
    {
      "id": "get-captured-content",
      "title": "Get captured content",
      "role": "content",
      "text": "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:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nThe 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)."
    },
    {
      "id": "get-a-context-gateway-tool-result",
      "title": "Get a Context Gateway tool result",
      "role": "content",
      "text": "Read the result of a Context Gateway tool call in a turn, by the step's ID. The turn lists these steps in `contextRetrieverToolSteps`:\n\n**Available in:** Python (Iris), cURL (Iris)\n\n##### Python (Iris)\n\n[code example]\n\n##### cURL (Iris)\n\n[code example]\n\n\n\nThe result classifies what the tool returned, such as `RECORDS` or `AGGREGATE`, and includes the returned data."
    },
    {
      "id": "next-steps",
      "title": "Next steps",
      "role": "content",
      "text": "- See the [API reference](https://redis.io/docs/latest/develop/ai/context-engine/lens/api-reference) for every field.\n- See [Limits and errors](https://redis.io/docs/latest/develop/ai/context-engine/lens/limits) for request limits and error responses."
    }
  ],
  "examples": [
    {
      "id": "code-examples-ex0",
      "language": "bash",
      "code": "python -m pip install 'redis-agent-lens>=1.0.0'",
      "section_id": "code-examples"
    },
    {
      "id": "authenticate-ex0",
      "language": "bash",
      "code": "export LENS_URL='<iris-host>'\nexport LENS_INSTANCE_ID='<lens-instance-id>'\nexport LENS_API_KEY='<lens-read-key>'",
      "section_id": "authenticate"
    },
    {
      "id": "authenticate-ex1",
      "language": "python",
      "code": "import os\n\nfrom redis_agent_lens import Lens\n\nclient = Lens(\n    server_url=os.environ[\"LENS_URL\"],\n    instance_id=os.environ[\"LENS_INSTANCE_ID\"],\n)",
      "section_id": "authenticate"
    },
    {
      "id": "authenticate-ex2",
      "language": "bash",
      "code": "LENS_API=\"${LENS_URL%/}/iris/data/v1beta1/lens/instances/$LENS_INSTANCE_ID\"\nAUTH=\"Authorization: Bearer $LENS_API_KEY\"",
      "section_id": "authenticate"
    },
    {
      "id": "list-agents-ex0",
      "language": "python",
      "code": "agents = client.list_agents()\nfor agent in agents.items:\n    print(agent.agent_id)",
      "section_id": "list-agents"
    },
    {
      "id": "list-agents-ex1",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \"$LENS_API/agents\" -H \"$AUTH\" \\\n  | jq -r '.items[].agentId'",
      "section_id": "list-agents"
    },
    {
      "id": "list-sessions-ex0",
      "language": "python",
      "code": "page = client.list_sessions(agent_id=\"support-bot\", range=\"24h\", outcome=[\"FAILED\"])\nprint(f\"Showing {page.effective_range} of sessions\")\n\nsessions = list(page.items)\nwhile page.next_cursor:\n    page = client.list_sessions(\n        agent_id=\"support-bot\", range=\"24h\", outcome=[\"FAILED\"], cursor=page.next_cursor\n    )\n    sessions.extend(page.items)\n\nfor session in sessions:\n    print(session.session_id, session.turn_count, session.last_seen)",
      "section_id": "list-sessions"
    },
    {
      "id": "list-sessions-ex1",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error -G \"$LENS_API/sessions\" -H \"$AUTH\" \\\n  --data-urlencode \"agentId=support-bot\" \\\n  --data-urlencode \"range=24h\" \\\n  --data-urlencode \"outcome=FAILED\" > sessions.json\njq '{effectiveRange, nextCursor, sessions: [.items[] | {sessionId, turnCount, lastSeen}]}' sessions.json\nSESSION_ID=$(jq -er '.items[0].sessionId' sessions.json)",
      "section_id": "list-sessions"
    },
    {
      "id": "get-a-session-ex0",
      "language": "python",
      "code": "if not sessions:\n    raise SystemExit(\"No matching sessions in the last 24 hours\")\n\nsession = client.get_session(session_id=sessions[0].session_id)\nfor summary in session.turns:\n    outcome = summary.outcome.value if summary.outcome else \"no outcome\"\n    print(summary.trace_id, outcome, summary.duration_ms, summary.tokens.total)",
      "section_id": "get-a-session"
    },
    {
      "id": "get-a-session-ex1",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \"$LENS_API/sessions/$SESSION_ID\" -H \"$AUTH\" > session.json\njq '[.turns[] | {traceId, outcome, durationMs, tokens: .tokens.total}]' session.json\nTURN_ID=$(jq -er '.turns[0].traceId' session.json)",
      "section_id": "get-a-session"
    },
    {
      "id": "get-a-turn-ex0",
      "language": "python",
      "code": "turn = client.get_turn(turn_id=session.turns[0].trace_id)\nfor entry in turn.manifest.entries:\n    print(f\"{entry.source}: {entry.state.value}, fetched {entry.fetched_count}, used {entry.used_count}\")\n    for candidate in entry.candidates:\n        print(f\"  {candidate.id} score={candidate.score} used={candidate.used}\")",
      "section_id": "get-a-turn"
    },
    {
      "id": "get-a-turn-ex1",
      "language": "bash",
      "code": "curl --fail-with-body --silent --show-error \"$LENS_API/turns/$TURN_ID\" -H \"$AUTH\" > turn.json\njq '[.manifest.entries[] | {source, state, fetchedCount, usedCount}]' turn.json",
      "section_id": "get-a-turn"
    },
    {
      "id": "get-captured-content-ex0",
      "language": "python",
      "code": "for entry in turn.manifest.entries:\n    for candidate in entry.candidates:\n        if candidate.content and candidate.content.hash:\n            content = client.get_content(turn_id=turn.trace_id, hash=candidate.content.hash)\n            print(candidate.id, content.availability.value, content.body)",
      "section_id": "get-captured-content"
    },
    {
      "id": "get-captured-content-ex1",
      "language": "bash",
      "code": "HASH=$(jq -r '.manifest.entries[0].candidates[0].content.hash // empty' turn.json)\nif [ -n \"$HASH\" ]; then\n  curl --fail-with-body --silent --show-error \"$LENS_API/turns/$TURN_ID/content/$HASH\" -H \"$AUTH\" \\\n    | jq '{availability, body}'\nfi",
      "section_id": "get-captured-content"
    },
    {
      "id": "get-a-context-gateway-tool-result-ex0",
      "language": "python",
      "code": "for step in turn.context_retriever_tool_steps:\n    result = client.get_context_retriever_result(turn_id=turn.trace_id, span_id=step.span_id)\n    print(step.tool_name, result.outcome.value, result.classification.value)",
      "section_id": "get-a-context-gateway-tool-result"
    },
    {
      "id": "get-a-context-gateway-tool-result-ex1",
      "language": "bash",
      "code": "for SPAN_ID in $(jq -r '.contextRetrieverToolSteps[].spanId' turn.json); do\n  curl --fail-with-body --silent --show-error \\\n    \"$LENS_API/turns/$TURN_ID/tools/$SPAN_ID/result\" -H \"$AUTH\" \\\n    | jq '{spanId, outcome, classification}'\ndone",
      "section_id": "get-a-context-gateway-tool-result"
    }
  ]
}
