# Proxy Logs

**Page:** api/proxy-logs

[Download Raw Markdown](./api/proxy-logs.md)

---

{/* AUTO-GENERATED — Do not edit manually. Regenerate with: npm run docs:api:generate */}



## Logs

The Logs endpoints let you search, aggregate, and live-tail the centralized request, response, and event logs captured by the proxy. Use these when you need to investigate traffic, build dashboards, or stream new entries into an external system.

## Query centralized logs

Search and filter the stored request/response and event logs. Supports pagination, level filtering, cross-tenant fanout (admin only), and row-cursor streaming.

`GET /api/proxy-logs/_logs`

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `limit` | query | integer | No | Max entries to return (default: 200) |
| `offset` | query | integer | No | Entries to skip (default: 0) |
| `projectId` | query | string | No | Restrict to a single project |
| `containerId` | query | string | No | Restrict to a single container |
| `serviceName` | query | string | No | Restrict to a single service name |
| `level` | query | string | No | Comma-separated levels (debug,info,warn,error) |
| `includeRequestBody` | query | boolean | No | Include captured request bodies (default: false) |
| `includeResponseBody` | query | boolean | No | Include captured response bodies (default: false) |
| `last` | query | integer | No | Return only the last N entries |
| `afterId` | query | integer | No | Return entries with SQLite row ID greater than this (ASC cursor) |
| `cursor` | query | string | No | v8 §5.2 — cross-tenant fanout pagination cursor (signed opaque base64). Only honored when `LOGS_ADMIN_FANOUT=true` |
| `kind` | query | string | No | One of: `request`, `response`, `event` |
| `method` | query | string | No | HTTP method filter (e.g. `GET`, `POST`) |
| `source` | query | string | No | One of: `backend`, `edge` |


Scoped reads return JSON. When `LOGS_ADMIN_FANOUT=true` the response switches to NDJSON streaming and ends with a trailer line: `{"__trailer": true, "cursor": "...", "stoppedReason": "..."}`.


### Response



```json
{
  "entries": [
    {
      "id": 84321,
      "traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "tsMs": 1718201234000,
      "tsIso": "2024-06-12T14:33:54.000Z",
      "kind": "request",
      "level": "info",
      "projectId": "proj_a1b2c3d4",
      "containerId": "cnt_x9y8z7w6",
      "serviceName": "user-api",
      "method": "POST",
      "url": "/v1/users",
      "clientIp": "203.0.113.42",
      "status": 201,
      "data": {"bytesIn": 412, "userAgent": "Hoody-CLI/1.4.2"},
      "source": "backend"
    },
    {
      "id": 84322,
      "traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d480",
      "tsMs": 1718201234102,
      "tsIso": "2024-06-12T14:33:54.102Z",
      "kind": "response",
      "level": "info",
      "projectId": "proj_a1b2c3d4",
      "containerId": "cnt_x9y8z7w6",
      "serviceName": "user-api",
      "method": "POST",
      "url": "/v1/users",
      "clientIp": "203.0.113.42",
      "status": 201,
      "data": {"bytesOut": 318, "durationMs": 102},
      "source": "backend"
    }
  ],
  "total": 1284,
  "limit": 200,
  "offset": 0
}
```


```json
{
  "statusCode": 403,
  "error": "Forbidden",
  "message": "Caller is not permitted to read logs for this scope"
}
```


```json
{"__trailer": true, "error": "snapshot_expired", "status": 410}
```



### SDK usage

```ts
const page = await client.proxyLogs.logs.listIterator({
  projectId: "proj_a1b2c3d4",
  level: "warn,error",
  limit: 100,
});
for await (const entry of page) {
  console.log(entry.traceId, entry.level, entry.url);
}
```

## Get log statistics

Returns aggregate counts across level, project, container, and service dimensions. Useful for building dashboards or sizing retention.

`GET /api/proxy-logs/_logs/stats`

This endpoint takes no parameters.

### Response



```json
{
  "total": 8421,
  "byLevel": {"info": 6200, "warn": 1500, "error": 521, "debug": 200},
  "byProject": {"proj_a1b2c3d4": 5000, "proj_e5f6g7h8": 3421},
  "byContainer": {"cnt_x9y8z7w6": 3000, "cnt_m5n6o7p8": 5421},
  "byService": {"user-api": 4200, "billing-api": 2100, "webhook-svc": 2121}
}
```



### SDK usage

```ts
const stats = await client.proxyLogs.logs.getStats();
console.log("Total entries:", stats.total);
console.log("Errors:", stats.byLevel.error);
```

## Live-tail logs over SSE

Opens a persistent Server-Sent Events connection streaming new log entries as they are written. Each frame carries an `id: <ringSeq>` line for resumable reconnect.

`GET /api/proxy-logs/_logs/stream`

**v8 §6.4 framing** — every frame carries an `id: <ringSeq>` line:

```
id: 12345
data: {"id":84321,"kind":"request","level":"info",...}

```

**Reconnect resume** — clients MAY send `Last-Event-ID: <ringSeq>` on reconnect; the server skips any frame with `ringSeq <= Last-Event-ID` from the ring buffer (5000 entries / ~50 s replay window at 100 entries/s).

**Named events** (v8):
- `event: scope-destroyed` — container destroyed; stream closes immediately after. Clients should exit cleanly.
- `event: reset` — server restart; `ringSeq` counter reset with a ≥10 000 safety margin. Clients MUST discard their `lastSeenId` and reconnect fresh.

Periodic `:\n\n` heartbeats every 15s keep the connection alive.

### Parameters

| Name | In | Type | Required | Description |
|------|-----|------|----------|-------------|
| `projectId` | query | string | No | Filter to a single project (admin-port only; SNI clients are auto-scoped) |
| `containerId` | query | string | No | Filter to a single container |
| `kind` | query | string | No | One of: `request`, `response`, `event` |
| `level` | query | string | No | One of: `debug`, `info`, `warn`, `error` |
| `Last-Event-ID` | header | string | No | v8 §6.4 — numeric ringSeq of the last event received. Server skips entries ≤ this value from the ring buffer on reconnect. |

### Response



```
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

id: 12345
data: {"id":84321,"traceId":"f47ac10b-58cc-4372-a567-0e02b2c3d479","tsMs":1718201234000,"kind":"request","level":"info","method":"POST","url":"/v1/users","status":201,"source":"backend"}

id: 12346
data: {"id":84322,"traceId":"f47ac10b-58cc-4372-a567-0e02b2c3d480","tsMs":1718201234102,"kind":"response","level":"info","method":"POST","url":"/v1/users","status":201,"source":"backend"}

:

```


```json
{
  "statusCode": 403,
  "error": "Forbidden",
  "message": "Logs token missing or invalid / SNI gate denied"
}
```


```json
{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "Rate limit exceeded for log stream"
}
```



### SDK usage

```ts
const stream = await client.proxyLogs.logs.streamLogs({
  projectId: "proj_a1b2c3d4",
  level: "error",
});

for await (const frame of stream) {
  if (frame.event === "scope-destroyed") break;
  if (frame.event === "reset") {
    // discard lastSeenId and reconnect fresh
    continue;
  }
  console.log(frame.id, frame.data);
}
```