diff --git a/content/docs/overview/agent-traces/api.mdx b/content/docs/overview/agent-traces/api.mdx
index c9d9542a..cd37de15 100644
--- a/content/docs/overview/agent-traces/api.mdx
+++ b/content/docs/overview/agent-traces/api.mdx
@@ -8,6 +8,39 @@ publishedAt: "2026-05-22"
Use the Agent Traces API when you want the same activity timeline shown in the dashboard, but as JSON for analysis, replay, or internal tooling.
+:::callout
+type: info
+### SDK availability
+The Node and Python examples below target the upcoming Steel SDK release that adds `sessions.traces()`. Use the REST endpoint until that SDK version is published.
+:::
+
+### SDK
+
+
+
+```typescript !! Typescript -wcn
+import Steel from 'steel-sdk';
+
+const client = new Steel();
+
+const trace = await client.sessions.traces(sessionId, {
+ limit: 500,
+});
+```
+
+```python !! Python -wcn
+from steel import Steel
+
+client = Steel()
+
+trace = client.sessions.traces(
+ session_id,
+ limit=500,
+)
+```
+
+
+
### GET /v1/sessions/\:id/agent-traces
Returns the trace timeline for a finished or in-progress session.
@@ -44,15 +77,21 @@ const trace = await response.json();
}
],
"total": 1,
- "hasMore": false
+ "hasMore": false,
+ "nextCursor": null,
+ "schemaVersion": "1.0",
+ "truncated": false
}
```
| Field | Type | Notes |
| ----- | ---- | ----- |
| `events` | array | Timeline activities in chronological order. |
-| `total` | number | Number of activities returned. |
+| `total` | number | Total number of matching activities. |
| `hasMore` | boolean | Whether more activity data is available. |
+| `nextCursor` | string or null | Opaque cursor for the next page. |
+| `schemaVersion` | string | Version of the normalized activity schema. |
+| `truncated` | boolean | Whether the retained source could not be scanned completely. Production exports should require `false`. |
Each activity includes a small common envelope:
@@ -66,6 +105,8 @@ Each activity includes a small common envelope:
Activity-specific details appear when relevant. For example, click activities can include `pointer`, typing activities can include `value`, keyboard activities can include `keyboard`, navigation activities can include `navigation`, and error activities can include `error`.
+Sessions that predate browser-interaction capture can return an empty `events` array.
+
### Target Details
When Steel can identify the element involved in an activity, `target` can include readable labels and useful selectors:
@@ -87,28 +128,38 @@ When Steel can identify the element involved in an activity, `target` can includ
Use target details as hints for review or replay. Some activities, such as navigation or errors, may not include a target.
-### Filtering by Time
+### Query Parameters
+
+| Query param | Notes |
+| ----------- | ----- |
+| `namespace` | Limit results to a session namespace. |
+| `startTime` | Include activities at or after this ISO 8601 timestamp. |
+| `endTime` | Include activities at or before this ISO 8601 timestamp. |
+| `limit` | Number of activities per page, from 1 to 2,000. Defaults to 500. |
+| `cursor` | Opaque cursor returned as `nextCursor`. Reuse it with identical filters. |
-You can limit results to a time range:
+Filter by time and request a page:
```bash !! curl -wcn
-curl "https://api.steel.dev/v1/sessions/$SESSION_ID/agent-traces?startTime=2026-05-22T18:00:00.000Z&endTime=2026-05-22T18:10:00.000Z" \
+curl "https://api.steel.dev/v1/sessions/$SESSION_ID/agent-traces?startTime=2026-05-22T18:00:00.000Z&endTime=2026-05-22T18:10:00.000Z&limit=500" \
-H "steel-api-key: $STEEL_API_KEY"
```
-| Query param | Notes |
-| ----------- | ----- |
-| `startTime` | Include activities at or after this ISO timestamp. |
-| `endTime` | Include activities at or before this ISO timestamp. |
+For a finished session, keep the filters fixed and pass each `nextCursor` into the next request until it is `null`. Paginating a live session is not a stable snapshot because new activities can arrive between requests.
+
+Synchronous trace export accepts up to 50,000 retained raw events and a 32 MiB normalized artifact. If a full-session request returns `413`, divide the session into non-overlapping `startTime`/`endTime` windows and export each window separately.
### Errors
| Status | Meaning |
| ------ | ------- |
| 401 | Missing or invalid `steel-api-key`. |
-| 403 | The session belongs to a different organization. |
-| 404 | No session with that ID. |
+| 404 | No accessible session with that ID. Steel also uses 404 for project or organization scope mismatches. |
+| 409 | The released session's audit data is still finalizing. Retry with backoff. |
+| 410 | The audit data has expired under the plan's retention period. |
+| 413 | The requested time range exceeds synchronous trace export limits. Retry with narrower time bounds. |
| 429 | Rate limit hit. Retry after the `Retry-After` header. |
+| 502 | The log service or browser machine is temporarily unavailable. Retry with backoff. |
:::callout
type: help
diff --git a/content/docs/overview/sessions-api/meta.json b/content/docs/overview/sessions-api/meta.json
index 3de07c3b..d608d9e4 100644
--- a/content/docs/overview/sessions-api/meta.json
+++ b/content/docs/overview/sessions-api/meta.json
@@ -6,6 +6,8 @@
"overview",
"quickstart",
"session-lifecycle",
+ "session-logs",
+ "session-audit-export",
"mobile-mode",
"fullscreen-mode",
"multi-region",
diff --git a/content/docs/overview/sessions-api/session-audit-export.mdx b/content/docs/overview/sessions-api/session-audit-export.mdx
new file mode 100644
index 00000000..c2916549
--- /dev/null
+++ b/content/docs/overview/sessions-api/session-audit-export.mdx
@@ -0,0 +1,187 @@
+---
+title: Session Audit Export
+sidebarTitle: Audit Export
+description: Export session metadata, finalized cost, browser logs, and agent traces after a Steel session ends.
+llm: true
+publishedAt: "2026-07-23"
+---
+
+A session audit export is an application-owned snapshot of one completed run. Wait for the session to finish, capture its final details and cost, drain browser logs and agent traces, then write three versioned datasets to storage you control.
+
+:::callout
+type: warning
+### Not a compliance audit log
+This export is **not** a tamper-evident compliance audit log. Steel does not sign the files, chain their hashes, or prove that an exported event was never changed or omitted. If your policy requires immutable, independently verifiable records, send the export into a purpose-built WORM or compliance logging system and add your own integrity controls.
+:::
+
+### Post-session workflow
+
+1. End the session with `sessions.release()` or wait for it to end naturally.
+2. Poll session details until `status` is `released` or `failed`.
+3. Request the finalized cost, browser logs, and agent traces. A `409` from any endpoint means the data is still finalizing; retry with backoff.
+4. Paginate [browser logs](/overview/sessions-api/session-logs) and [agent traces](/overview/agent-traces/api) with fixed filters until `nextCursor` is `null`.
+5. Persist these three datasets:
+ - `session.json` — export metadata, final session details, and finalized cost.
+ - `browser-logs.jsonl` — one versioned browser log record per line.
+ - `agent-traces.jsonl` — one versioned agent activity per line.
+
+Do not begin an audit export while the session is live. New events can shift later pages and produce gaps or duplicates.
+
+:::callout
+type: info
+### SDK availability
+The example targets the upcoming Node SDK release that adds `sessions.logs()`, `sessions.traces()`, and `sessions.cost()`.
+:::
+
+### Example
+
+```typescript !! Typescript -wcn
+import Steel from 'steel-sdk';
+import { mkdir, writeFile } from 'node:fs/promises';
+
+const client = new Steel();
+const schemaVersion = '1.0';
+const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+async function waitForAuditReady(sessionId: string) {
+ const deadline = Date.now() + 60_000;
+
+ while (Date.now() < deadline) {
+ const session = await client.sessions.retrieve(sessionId);
+
+ if (session.status !== 'live') {
+ try {
+ const cost = await client.sessions.cost(sessionId);
+ return { session, cost };
+ } catch (error) {
+ if (!(error instanceof Steel.APIError) || error.status !== 409) throw error;
+ }
+ }
+
+ await sleep(1_000);
+ }
+
+ throw new Error(`Session ${sessionId} did not become audit-ready`);
+}
+
+async function collectPages(
+ fetchPage: (cursor?: string) => Promise<{
+ events: unknown[];
+ hasMore: boolean;
+ nextCursor: string | null;
+ }>,
+) {
+ const events: unknown[] = [];
+ let cursor: string | undefined;
+
+ while (true) {
+ let page;
+ try {
+ page = await fetchPage(cursor);
+ } catch (error) {
+ if (error instanceof Steel.APIError && error.status === 409) {
+ await sleep(1_000);
+ continue;
+ }
+ throw error;
+ }
+ events.push(...page.events);
+
+ if (!page.nextCursor) {
+ if (page.hasMore) throw new Error('Pagination stalled without a next cursor');
+ return events;
+ }
+ if (page.events.length === 0) throw new Error('Pagination stalled');
+
+ cursor = page.nextCursor;
+ }
+}
+
+type Dataset = 'browser-log' | 'agent-trace';
+type RedactionPolicy = {
+ version: string;
+ apply: (dataset: Dataset | 'session', value: unknown) => unknown;
+};
+
+function toJsonLines(
+ dataset: Dataset,
+ sessionId: string,
+ events: unknown[],
+ redaction: RedactionPolicy,
+) {
+ const lines = events.map((event) =>
+ JSON.stringify({
+ schemaVersion,
+ dataset,
+ sessionId,
+ event: redaction.apply(dataset, event),
+ }),
+ );
+
+ return lines.length > 0 ? `${lines.join('\n')}\n` : '';
+}
+
+async function exportSessionAudit(
+ sessionId: string,
+ outputDirectory: string,
+ redaction: RedactionPolicy,
+) {
+ const { session, cost } = await waitForAuditReady(sessionId);
+ const [browserLogs, agentTraces] = await Promise.all([
+ collectPages((cursor) => client.sessions.logs(sessionId, { limit: 1_000, cursor })),
+ collectPages((cursor) => client.sessions.traces(sessionId, { limit: 1_000, cursor })),
+ ]);
+
+ const manifest = {
+ schemaVersion,
+ exportedAt: new Date().toISOString(),
+ redactionPolicyVersion: redaction.version,
+ session: redaction.apply('session', session),
+ cost,
+ counts: {
+ browserLogs: browserLogs.length,
+ agentTraces: agentTraces.length,
+ },
+ };
+
+ await mkdir(outputDirectory, { recursive: true });
+ await Promise.all([
+ writeFile(`${outputDirectory}/session.json`, `${JSON.stringify(manifest, null, 2)}\n`),
+ writeFile(
+ `${outputDirectory}/browser-logs.jsonl`,
+ toJsonLines('browser-log', sessionId, browserLogs, redaction),
+ ),
+ writeFile(
+ `${outputDirectory}/agent-traces.jsonl`,
+ toJsonLines('agent-trace', sessionId, agentTraces, redaction),
+ ),
+ ]);
+}
+```
+
+Pass a redaction policy that handles your application's secrets and personal data. In production, retry `429` and transient `5xx` responses with bounded exponential backoff and fail the export if any page cannot be persisted.
+
+If Agent Traces returns `413`, export the session in non-overlapping time windows. Each window must remain below the documented synchronous trace limits; persist the window bounds in `session.json` so consumers can verify complete coverage.
+
+### Schema and versioning
+
+Each paginated Steel response includes its own `schemaVersion`. The example also versions the application-owned export envelope separately. Keep upstream objects nested under `session`, `cost`, or `event` so additive Steel fields do not require a migration. Increment your export's major version when file names, envelope fields, or field meanings change; increment the minor version for backward-compatible additions. Consumers should reject unsupported major versions rather than silently dropping evidence.
+
+Record the SDK version, export code version, and redaction policy version in `session.json` if you need reproducible exports.
+
+### Redaction
+
+Agent Traces automatically mark known-sensitive input fields, such as passwords and credit-card inputs, as redacted. That is not a complete data-loss-prevention boundary. URLs, page text, console output, request metadata, and ordinary form fields can still contain tokens or personal data.
+
+Apply redaction before writing any dataset, test it against representative events, and restrict access to both exports and encryption keys. If you retain an original without redaction, isolate it under a stricter policy and identify sanitized copies as derived data.
+
+### Retention
+
+Export promptly after the session ends. Source data remains available only for your organization's current retention entitlement, which can differ for custom and grandfathered plans. Confirm that value in your Steel plan or contract rather than hard-coding a plan name. Your copies are outside Steel's retention lifecycle, so define separate expiration and deletion rules for the destination. Deleting or expiring data in Steel does not remove an export you already persisted.
+
+### Pagination checks
+
+- Use `nextCursor` until it is `null`. Do not construct or modify cursors.
+- Keep `namespace`, time bounds, and other filters identical across pages; cursors are scoped to those filters.
+- Treat `hasMore: true` without a `nextCursor`, or an empty page with `hasMore: true`, as an error.
+- Store the final counts and alert if a retry produces different counts for the same completed session.
diff --git a/content/docs/overview/sessions-api/session-logs.mdx b/content/docs/overview/sessions-api/session-logs.mdx
new file mode 100644
index 00000000..2f0fb9e1
--- /dev/null
+++ b/content/docs/overview/sessions-api/session-logs.mdx
@@ -0,0 +1,115 @@
+---
+title: Session Logs API
+sidebarTitle: Session Logs
+description: Query browser event logs for a Steel session with filtering and pagination.
+llm: true
+publishedAt: "2026-07-23"
+---
+
+Use the Session Logs API to retrieve browser requests, navigation, console output, page errors, and related browser events. For a coalesced timeline of agent interactions, use [Agent Traces](/overview/agent-traces/api) instead.
+
+:::callout
+type: info
+### SDK availability
+The Node and Python examples below target the upcoming Steel SDK release that adds `sessions.logs()`. Use the REST endpoint until that SDK version is published.
+:::
+
+### SDK
+
+
+
+```typescript !! Typescript -wcn
+import Steel from 'steel-sdk';
+
+const client = new Steel();
+
+const page = await client.sessions.logs(sessionId, {
+ eventTypes: ['Console', 'PageError'],
+ limit: 500,
+});
+```
+
+```python !! Python -wcn
+from steel import Steel
+
+client = Steel()
+
+page = client.sessions.logs(
+ session_id,
+ event_types=["Console", "PageError"],
+ limit=500,
+)
+```
+
+
+
+### GET /v1/sessions/\:id/logs
+
+```bash !! curl -wcn
+curl "https://api.steel.dev/v1/sessions/$SESSION_ID/logs?limit=500" \
+ -H "steel-api-key: $STEEL_API_KEY"
+```
+
+### Response
+
+```json !! JSON -wcn
+{
+ "events": [
+ {
+ "id": "01K0EXAMPLE",
+ "timestamp": "2026-07-23T18:03:21.345Z",
+ "type": "Console",
+ "log": "{\"message\":\"Checkout form loaded\"}",
+ "payload": { "message": "Checkout form loaded" }
+ }
+ ],
+ "total": 1,
+ "hasMore": false,
+ "nextCursor": null,
+ "schemaVersion": "1.0"
+}
+```
+
+| Field | Type | Notes |
+| ----- | ---- | ----- |
+| `events` | array | Browser events in chronological order. |
+| `events[].id` | string | Event identifier. |
+| `events[].timestamp` | ISO 8601 string | When the event happened. |
+| `events[].type` | string | Event type, such as `Console`, `Navigation`, `Request`, `Response`, `PageError`, or `RequestFailed`. |
+| `events[].payload` | object | Structured browser event payload. |
+| `events[].log` | string | Legacy JSON-encoded payload retained for compatibility. |
+| `total` | number | Total number of matching events. |
+| `hasMore` | boolean | Whether another page is available. |
+| `nextCursor` | string or null | Opaque cursor for the next page. |
+| `schemaVersion` | string | Version of the browser-log response schema. |
+
+### Query Parameters
+
+| Query param | Notes |
+| ----------- | ----- |
+| `namespace` | Limit results to a session namespace. |
+| `startTime` | Include events at or after this ISO 8601 timestamp. |
+| `endTime` | Include events at or before this ISO 8601 timestamp. |
+| `eventTypes` | Limit results to one or more event types. |
+| `limit` | Events per page. Defaults to 100 and accepts up to 1,000. |
+| `cursor` | Opaque cursor returned as `nextCursor`. Reuse it with identical filters. |
+| `offset` | Legacy zero-based event offset. Prefer `cursor` for new integrations. |
+
+For a finished session, keep the filters fixed and pass each `nextCursor` into the next request until it is `null`. Paginating a live session is not a stable snapshot because new events can arrive between requests.
+
+:::callout
+type: warning
+### Treat logs as sensitive
+URLs, console output, and request metadata can contain personal data, tokens, or application secrets. Apply your own redaction and access controls before persisting or sharing logs.
+:::
+
+### Errors
+
+| Status | Meaning |
+| ------ | ------- |
+| 401 | Missing or invalid `steel-api-key`. |
+| 404 | No accessible session with that ID. Steel also uses 404 for project or organization scope mismatches. |
+| 409 | The released session's audit data is still finalizing. Retry with backoff. |
+| 410 | The audit data has expired under the plan's retention period. |
+| 429 | Rate limit hit. Retry after the `Retry-After` header. |
+| 502 | The log service or browser machine is temporarily unavailable. Retry with backoff. |