# Search recent and archived events

Supported filters and the separate historical query path.

Contract: console-v1. Stage: private evaluation. Source snapshot: 23fdccfaa88f69e858769f1dad70f82739e2740f.

## Hot filters

POST /api/v1/events/query accepts start_time_ms, end_time_ms, source_id, class_uid, status_id, text and limit. Unknown fields—including sql—are rejected. Time defaults to the last 24 hours; maximum range is 31 days. limit defaults to 100 with a maximum of 500. text is at most 256 UTF-8 bytes and is a case-insensitive substring over activity_name or actor, not arbitrary JSON/full-text search. Tenant scope is injected server-side.

## Execution bounds

The hot path uses parameterized ClickHouse SQL with a five-second engine limit, 256 MiB memory bound, 64 MiB read-byte bound, two threads and shared query concurrency control. The response includes items, scope:hot and query:{sql,rows,elapsed_ms}. SQL is a preview of the authorized operation, not a credential or an executable permission grant. A row cap alone is not a scan-cost guarantee; dependency errors and resource limits must remain visible.

## Historical scope is explicit

POST /api/v1/history/query uses the authorized PyIceberg archive path over Iceberg/Parquet. Only start_time_ms, end_time_ms, source_id and limit are supported with the same time/row bounds. class_uid, status_id and text are explicitly rejected as unsupported_history_filter; no silent fallback to hot data. Archive unavailability fails explicitly. Historical event retrieval is not historical audit retrieval. Presto/Trino integration and an advanced SQL editor/gateway are planned, not connected.

## Python standard-library example

Use only an authorized read/query session. SALAD_SESSION contains the opaque cookie value from your own private environment; never place it in source or print it. Set the origin variables to your operator-provided values. The example prints evidence IDs, not raw records or the cookie.

```python
import json, os, urllib.request
body = json.dumps({"limit": 25, "status_id": 2}).encode()
request = urllib.request.Request(
    os.environ["CONSOLE_URL"] + "/api/v1/events/query",
    data=body, method="POST", headers={
        "Origin": os.environ["CONSOLE_ORIGIN"],
        "X-Console-CSRF": "1", "Content-Type": "application/json",
        "Cookie": "salad_session=" + os.environ["SALAD_SESSION"],
    })
with urllib.request.urlopen(request, timeout=10) as response:
    result = json.load(response)
print([event["id"] for event in result["items"]])
```

## Node standard-library example

Node.js 18+ has built-in fetch; no third-party SDK or package is required. Do not reuse a human approval session as an autonomous agent credential. This GET only lists alerts in the authorized workspace.

```javascript
const response = await fetch(process.env.CONSOLE_URL + "/api/v1/alerts", {
  headers: {Cookie: "salad_session=" + process.env.SALAD_SESSION},
  signal: AbortSignal.timeout(10000)
});
if (!response.ok) throw new Error("HTTP " + response.status);
const result = await response.json();
console.log(result.items.map(alert => alert.id));
```
