# Request logs

The Request logs page is the operator surface for recent runtime traffic. It helps you inspect whether requests were admitted, which Pool handled them, which upstream account was selected, how the route behaved, and what usage metadata was settled.

Use this page when you need to answer:

1. whether a client request reached Codex Pooler
2. which Pool, upstream account, model, API key, and transport were involved
3. whether the request succeeded, failed, retried, or is still in progress
4. how long routing and upstream work took
5. which token and cost metadata was recorded
6. whether errors were safe operational errors, quota issues, or upstream failures

Request logs are metadata-only. They do not show prompt text, generated content, uploaded file bytes, media contents, websocket frames, bearer tokens, upstream credentials, raw Pool API keys, raw idempotency keys, or raw provider payloads.

[![Request logs overview](/operators/request-logs/request-logs-overview.png)](/operators/request-logs/request-logs-overview.png)

The screenshots in this guide use synthetic demo data. The overview is a three-row excerpt of the table; no production identifiers or usage totals are shown.

## API-key usage boundaries

Keep these surfaces separate when investigating usage:

| Surface | Scope | Use it for |
| --- | --- | --- |
| `/admin/api-keys` | API-key lifecycle and policy | Create, edit, pause, resume, rotate, revoke, delete, inspect expiry and policy, and enable or disable Dashboard access. It does not show usage totals or charts. |
| [API Key Observatory](/operators/api-key-observatory/) | One authenticated API key | Read-only bounded usage, model, cache, cost, latency, throughput, and recent sanitized outcome projections for that key. |
| `/admin/stats` | A visible Pool | Pool-level aggregate usage and capacity views for signed-in instance owners or assigned instance admins. |
| `/admin/request-logs` | Individual recorded requests | Sanitized request outcomes, attempts, settlement metadata, and routing context for administrative investigation. |

The Observatory is a separate browser surface authenticated by the API key's Dashboard access capability. It does not reuse the instance-admin session, does not grant `/admin/*` access, and does not replace the runtime compatibility usage routes. Opening the Observatory does not itself create a runtime request.

## Filters

The top filters narrow the table without changing any data.

[![Request logs filters](/operators/request-logs/request-logs-filters.png)](/operators/request-logs/request-logs-filters.png)

Primary filters are:

<table>
  <thead>
    <tr>
      <th>Filter</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Pool</td>
      <td>Limits rows to one Pool. Owners can inspect all visible Pools; assigned admins only see their Pool scope.</td>
    </tr>
    <tr>
      <td>Status</td>
      <td>Limits rows by request outcome, such as completed, failed, rejected, or in-progress states.</td>
    </tr>
    <tr>
      <td>Upstream account</td>
      <td>Limits rows to a visible upstream identity. When a Pool is selected, this list narrows to accounts assigned to that Pool.</td>
    </tr>
    <tr>
      <td>Model</td>
      <td>Limits rows to a model name observed in request logs.</td>
    </tr>
  </tbody>
</table>

Advanced filters are:

1. `Correlation or row id`, for finding one request by known id
2. `Date from`, for the start of the time range
3. `Date to`, for the end of the time range

Invalid or unsupported filters are ignored with a warning so operators can fix the query without losing the page.

## Table columns

The table shows the latest matching rows, with a hard page size limit.

<table>
  <thead>
    <tr>
      <th>Column</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Time · Status</td>
      <td>Admission date and time in the operator's selected timezone and format, followed by the status icon, outcome and recorded upstream attempt duration. Open the row to inspect its identifiers and attempts.</td>
    </tr>
    <tr>
      <td>Model · Effort · Tier</td>
      <td>Requested or enforced model and effective reasoning effort on the first line, with the reported service tier and requested-versus-effective details below. A successful Responses request without a recorded effort shows <code>model default</code>; the gateway does not observe the backend's chosen effort. Endpoints without reasoning, such as transcription, show only the tier. A missing model appears as a muted <code>— no model</code>.</td>
    </tr>
    <tr>
      <td>Upstream · Pool · Key</td>
      <td>Selected upstream label and plan name, using the plan's established color. The Pool and API key label or safe prefix appear below.</td>
    </tr>
    <tr>
      <td>Endpoint · Transport · Client</td>
      <td>For translated requests, the recorded client path and destination share the first line, separated by the translation icon. Native requests show one path. The second line contains the protocol badge and client name/version. A bolt marks requests priced at the priority tier. Complete paths remain available in tooltips when space is limited.</td>
    </tr>
    <tr>
      <td>Errors · Warnings</td>
      <td>Sanitized request and attempt errors use red triangles; model-name warnings use amber. This column appears only when at least one row on the current page has a signal. A successful retry can retain an earlier attempt's error.</td>
    </tr>
    <tr>
      <td>Tokens · Cached</td>
      <td>Recorded total tokens beside a composition bar for cached input, uncached input and output. Reasoning tokens are already included in output. The cached count and its percentage of input appear below. Missing or inconsistent counters do not produce a fabricated bar.</td>
    </tr>
    <tr>
      <td>Cost</td>
      <td>Recorded reporting cost when available. This is an operational estimate, not a provider invoice. Missing cost can mean usage did not settle or pricing was unavailable.</td>
    </tr>
  </tbody>
</table>

The table keeps endpoint details to two lines and uses an ellipsis when text exceeds the available width. On narrow screens, the same record reflows into grouped fields; token values remain visible while the composition bar and legend step aside. The footer shows the operator timezone, result range and paging controls.

## Status and errors

Use status and errors together. A completed row with token usage usually means the request reached an upstream and settled successfully. A failed row may still show route, latency, or partial attempt metadata. A rejected row usually means admission or policy failed before upstream work.

OpenAI Responses `response.incomplete` can be a successful delivered terminal response. For example, an upstream may stop at `max_output_tokens` or a content filter and still return safe usage metadata. Those rows stay `succeeded` and should not be treated as upstream health failures. Error-coded incomplete terminals, stale continuation anchors, stream truncation, and client disconnects still appear as failures or interruptions with sanitized error codes.

Common causes to inspect are:

1. Pool API key paused, revoked, expired, or attached to the wrong Pool
2. requested model not allowed by the key policy
3. no active upstream assigned to the Pool
4. selected upstream needing reauthorization or token refresh
5. quota evidence exhausted or stale
6. upstream route failure or timeout
7. request shape unsupported by the current compatibility surface

The Errors · Warnings column should stay safe to copy into operator tickets. It should not include raw prompts, responses, uploaded file contents, credentials, or provider payloads.

### Failed-attempt terminal diagnostics

The attempt detail drawer can include three metadata-only fields for `failed` and `retryable_failed` attempts:

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Meaning and safety behavior</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>upstream_error_code</code></td>
      <td>The sanitized provider terminal code. Any ASCII identifier using only <code>[A-Za-z0-9_.-]</code> and no more than 80 bytes stays readable; invalid grammar, control or invalid UTF-8, and overlong values are stored as <code>sha256_&lt;12 lowercase hex&gt;</code>.</td>
    </tr>
    <tr>
      <td><code>stream_terminal_type</code></td>
      <td>The sanitized terminal event type, using the same ASCII grammar and 80-byte limit, then the same <code>sha256_&lt;12 lowercase hex&gt;</code> fallback for invalid values.</td>
    </tr>
    <tr>
      <td><code>upstream_error_param</code></td>
      <td>A provider field or index path only when it matches the accepted grammar. Invalid values are omitted.</td>
    </tr>
  </tbody>
</table>

These fields are detail-only diagnostics. They do not replace the semantic error shown in request status and error summaries, and they do not change the public response, retry, routing, health, or settlement behavior. Successful attempts do not show them. Rows written before this contract was deployed may have no diagnostic fields; treat that absence as unknown rather than as evidence that the provider omitted a code. Historical rows are not backfilled.

### Serving-mode labels

When a request or attempt has a complete validated serving-mode snapshot, the detail drawer shows three metadata rows:

<table>
  <thead>
    <tr>
      <th>Row</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Configured serving mode</td>
      <td>The Pool-model setting captured for this request or attempt: <code>auto</code>, <code>lite</code>, or <code>full</code>.</td>
    </tr>
    <tr>
      <td>Effective serving mode</td>
      <td>The backend request shape selected for the captured snapshot: <code>lite</code> or <code>full</code>.</td>
    </tr>
    <tr>
      <td>Serving mode source</td>
      <td><code>catalog</code> for an Auto resolution or <code>override</code> for an explicit Pool-model setting.</td>
    </tr>
  </tbody>
</table>

The Routing summary reads the request snapshot, while each attempt in the timeline reads that attempt's own persisted snapshot. The three rows are omitted for historical rows with absent or invalid mode metadata. They are never derived from request bodies, prompts, provider text, headers, credentials, or other payload data.

Serving mode is not part of the error classification. An ordinary terminal upstream 4xx is classified as `upstream_status` whichever mode resolved, a `429` as `upstream_rate_limited`, and an ordinary 5xx as `upstream_status`. To find the requests a provider refused, filter on `upstream_status` with an upstream status code in the 4xx range other than `429`; that answer is complete across Auto, Lite, and Full. To narrow it to an explicit Full override, add the serving-mode snapshot rows above, which record `full`/`full`/`override` on the request and on each attempt. These classifications are independent of whether the drawer can show a serving-mode snapshot.

### Upstream Websocket Connection Details

For an attempt that actually used a current-release upstream websocket, the admin attempt-detail drawer can show an `upstream_websocket_connection` group with exactly four fields:

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>lifecycle_id</code></td>
      <td>A generated canonical UUID shared by successful upstream websocket connections created within one in-process session lifecycle. It is not a provider or socket identifier.</td>
    </tr>
    <tr>
      <td><code>generation</code></td>
      <td>The positive ordinal of a successfully established upstream websocket connection within that lifecycle. Reuse keeps the same generation; a successfully established replacement advances it.</td>
    </tr>
    <tr>
      <td><code>reused</code></td>
      <td>Whether this request started on a connection that an earlier request had already established.</td>
    </tr>
    <tr>
      <td><code>reconnected</code></td>
      <td>Whether this request was retried on a newly established connection after its reused connection failed before a response became visible.</td>
    </tr>
  </tbody>
</table>

This group is attempt metadata only. It is omitted when metadata is missing, malformed, or unavailable for the selected transport. It is not added to request-level list rows and is not exposed by request-log MCP tools.

### Websocket Close Triage

Websocket request rows are created after gateway admission and reservation. If a websocket closes before a request row exists, inspect application logs for sanitized websocket close metadata instead of looking for prompt or frame content.

Safe close metadata can include route family, endpoint, transport, route class, phase, `reason_class`, elapsed milliseconds, safe session id prefixes, owner or proxy instance ids, and downstream epoch. Typical `reason_class` values include `max_frame_size_exceeded` for an oversized inbound websocket frame, `timeout` for downstream websocket idle close, and `closed` for a normal client close.

For downstream idle closes on backend Codex websockets and narrow public `/v1/responses` websockets, check the admin-managed `websocket_idle_timeout_ms` setting. Its default is `1_800_000` ms and the accepted range is `60_000..3_600_000` ms. This setting does not change upstream receive timeout classification.

## Usage metadata

Usage lines summarize request accounting, including request count context, token totals, cached input tokens, and estimated cost when pricing is available. Treat these as operational attribution and capacity evidence. They are not a replacement for provider billing records or the key-local Observatory projection.

If usage is blank, check the request status first. A request can be admitted without final usage if it failed before settlement, if pricing was not matched, or if accounting metadata was not available for that route. A delivered incomplete response with missing upstream usage remains `usage_unknown` and does not get an invented settled cost from the reservation estimate. A request the Pooler refused before anything reached the upstream, such as a `previous_response_id` the current upstream connection cannot resolve, settles with usage `not_applicable`: no tokens, no estimated cost and no provisional token-budget pressure.

### Service tiers

The request drawer keeps three tier facts apart:

<table>
  <thead>
    <tr>
      <th>Row</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Requested tier</td>
      <td>The tier the client sent or an API key policy enforced. <code>fast</code> is shown as <code>priority</code>. <code>Not set</code> means the request sent no tier.</td>
    </tr>
    <tr>
      <td>Upstream reported</td>
      <td>The <code>service_tier</code> value on the upstream's final response event. Omitted when the upstream reported none.</td>
    </tr>
    <tr>
      <td>Priced as</td>
      <td>The pricing tier used for the request's estimated cost. Shown only for priced requests.</td>
    </tr>
  </tbody>
</table>

The ChatGPT Codex backend reports <code>service_tier</code> as <code>auto</code> while a response is in progress and <code>default</code> when it completes, including for requests sent with <code>priority</code>. The public OpenAI API reports <code>priority</code> for the same request. Codex Pooler forwards the requested tier and prices the tier the upstream reported, with one exception: a <code>priority</code> request reported as <code>default</code> is priced at <code>priority</code>, because the backend reports <code>default</code> for every priority request while it serves the request in Fast mode, which uses plan usage at a higher rate than standard. Such a request shows <code>tier default priority requested</code> in the list and <code>priority</code> under Priced as. When the upstream reports no tier, or reports <code>auto</code>, the request is priced at its requested tier. Requests settled by earlier releases keep the tier they were priced at.

A <code>default</code> report on a <code>priority</code> request is not evidence that priority processing was skipped. To judge whether it was applied, compare latency across requests for the same account, model, and transport.

### Served model

The list's model column shows the model the client requested. Codex Pooler separately records the model sent upstream and the first model name the provider reports. A different reported name is worth investigating regardless of model quality; the name alone does not explain why it differs:

<table>
  <thead>
    <tr>
      <th>Row</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Model</td>
      <td>The model the client requested.</td>
    </tr>
    <tr>
      <td>Sent upstream</td>
      <td>The upstream model identifier the latest attempt sent, after Pool aliases and catalog mapping.</td>
    </tr>
    <tr>
      <td>Upstream served</td>
      <td>The <code>model</code> value the upstream declared on the first response event of the latest attempt (or on the JSON body for non-streamed responses). Omitted when the upstream declared none.</td>
    </tr>
  </tbody>
</table>

When the first reported model differs from the one sent, the list shows an amber triangle and **Model mismatch: …** in the **Errors · Warnings** column. A row without the marker either has matching names or has no recorded name; absence of a warning is not proof of model identity.

**Model name changed · attempts …** means something else: the provider reported different model names during the same attempt. This can happen even when the first name matched the model sent. Open the listed attempt numbers to compare the first reported name, the first different later name, and the final reported name.

Use [Lens](/operators/lens/) for the history view, [examples of the two signals](/operators/lens/#understand-the-two-signals), and [recording limits](/operators/lens/#understand-missing-and-historical-data). The names are provider-reported evidence, not independent inspection of the underlying model. Pricing keeps using the model sent upstream. Values that are not plain model identifiers are stored as a short fingerprint rather than verbatim.

## Operational Checklist

When investigating a client report, check request logs in this order:

1. filter by Pool or API key context if known
2. filter by request id when the client provided one
3. narrow by model or upstream only after confirming the Pool
4. inspect status, transport, route, and errors
5. compare timestamps with Audit logs for recent policy or lifecycle changes
6. open the Pool page to confirm active status and assignments
7. open the Upstreams page to confirm readiness, quota, and freshness
8. open API keys to confirm key policy, status, expiry, and limits

If no row exists, the request may not have reached Codex Pooler, may have used a different Pool API key, or may be outside the visible operator scope.