> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omnia-voice.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Request logging

> Opt-in capture of your chat traffic — browse it, export it, turn it into fine-tuning datasets.

Request logging keeps a **full-content record of your chat completions**,
prompts and responses, browsable in the dashboard and exportable as JSONL.
It's the raw material for improving your models: review real traffic, build
evaluation sets, and curate fine-tuning datasets from what your users
actually ask.

It is **off by default**. Omnia's standing posture is content-free: we never
retain prompts or completions unless you explicitly choose it. Request
logging is one of exactly two opt-in exceptions (the other is
[response caching](/billing/caching)).

## Enabling it

In the [dashboard](https://platform.omnia-voice.com/dashboard) →
**Settings → Workspace → Request logging** (owner/admin only):

<Steps>
  <Step title="Turn on the toggle">
    Takes effect at the gateway within a few seconds.
  </Step>

  <Step title="Pick a retention window">
    7, 30, or 90 days. Every logged request carries its own deletion
    deadline; retention is enforced row by row, automatically.
  </Step>
</Steps>

## What gets stored

|                                                           | Logged?                                          |
| --------------------------------------------------------- | ------------------------------------------------ |
| Successful chat completions (streaming and non-streaming) | ✅ full messages + response, including tool calls |
| Cache-hit replays                                         | ✅ flagged as cached                              |
| Fallback-served responses                                 | ✅ with the model it fell back from               |
| Errors, partial or dropped streams                        | ❌ never                                          |
| Embeddings / rerank / responses endpoints                 | ❌ not in v1                                      |
| Requests to dedicated endpoints                           | ❌                                                |
| Oversized exchanges (> 512 KB)                            | ❌ skipped, never truncated                       |

Each entry also records the model, [tag and trace id](/reference/observability#request-labels-tags-and-trace-ids),
token counts, and finish reason: the signals you filter by when curating.

**Credential-shaped strings are scrubbed before storage**: API keys, bearer
tokens, and similar patterns in your prompts are replaced with `[redacted]`
automatically. This is defense-in-depth, not a guarantee; avoid sending
secrets to models in the first place.

## Per-request opt-out

With logging enabled, any individual call can stay out:

```bash theme={null}
curl https://gateway.omnia-voice.com/v1/chat/completions \
  -H "Authorization: Bearer $OMNIA_API_KEY" \
  -H "X-Omnia-Log: off" \
  ...
```

The header can only **reduce** logging: with the workspace toggle off it
does nothing.

## Browsing and exporting

The **Logs** page in the dashboard lists your logged traffic newest-first,
filterable by model, [tag](#tagging-your-traffic), auto-detected
[**segment**](/concepts/segments) (the system-prompt family a request belongs
to: zero setup, works retroactively), time range, finish reason, and cache
hits. Click any row for the full conversation, message by message, tool
calls included.

**Export JSONL** downloads the current filter as chat-format lines, one
`{"messages": [...]}` object per exchange, with the assistant's reply as the
final message. That's the standard shape for fine-tuning and evaluation
pipelines, ours or anyone else's. Exports are capped at 10,000 exchanges per
download.

**Create dataset** curates the current filter into a managed dataset instead:
errored, truncated, empty, and duplicate exchanges are dropped automatically
and every drop is reported by reason. The dataset keeps its provenance:
including the [segment](/concepts/segments) it was cut from, so you always
know which application surface trained on it. Optionally hold out 10% as a
second, **disjoint** `-eval` dataset. No example appears in both, which is
what makes it a valid set to [evaluate](/reference/evals) against later.

A dataset is a **frozen snapshot**: it does not age out when the logs it was
curated from expire. The same asymmetry applies to
[grades](/judges/grading) (labels in the
API); grades are permanent, while the logged conversations they graded are
deleted on your retention window. Curate and grade what matters before it
ages out.

### Multi-turn conversations are folded, not repeated

A chat application resends the whole history on every turn, so a 3-turn
conversation is logged as **three** exchanges whose messages are nested:

```
{"messages": [Q1, A1]}
{"messages": [Q1, A1, Q2, A2]}
{"messages": [Q1, A1, Q2, A2, Q3, A3]}
```

Training on those as three separate examples would teach the model turn 1
**three times** and the final turn once, an implicit bias toward the start of
every conversation. So we reconstruct them into **one** training example with a
`weight` on each assistant turn:

```json theme={null}
{"messages": [
  {"role": "user", "content": "Q1"},
  {"role": "assistant", "content": "A1", "weight": 1},
  {"role": "user", "content": "Q2"},
  {"role": "assistant", "content": "A2", "weight": 1},
  {"role": "user", "content": "Q3"},
  {"role": "assistant", "content": "A3", "weight": 1}
]}
```

Every turn survives and every turn is learned **exactly once**. The curation
summary tells you when this happened ("842 earlier turns folded into 210
conversations"), so a dataset with fewer lines than exchanges is never a
mystery.

<Note>
  Conversations are reconstructed from the message content itself, so this works
  without any tagging on your side. Independent steps of an **agent run** are not
  folded: they aren't continuations of one another, so they stay as separate
  training examples.
</Note>

## Tagging your traffic

Label each request with the task it belongs to, and every downstream
surface (logs, datasets, evals) can be scoped to that one task:

```bash theme={null}
curl https://gateway.omnia-voice.com/v1/chat/completions \
  -H "Authorization: Bearer $OMNIA_API_KEY" \
  -H "X-Omnia-Tag: support-bot" \
  ...
```

Tags are content-free labels (≤64 chars). `X-Omnia-Trace-Id` groups the steps
of one multi-step agent run. Neither is ever forwarded upstream.

Didn't tag? Auto-detected [segments](/concepts/segments) group logged traffic
by system-prompt family with zero setup, retroactively. The tag stays the
deliberate override. The two compose, so you can filter by both at once.

## From the API

Everything on the Logs page is available to a workspace API key:

```bash theme={null}
# Browse (filters: model, tag, segment, finish_reason, cache_hit, start, end, limit, offset)
curl "https://gateway.omnia-voice.com/v1/logs?tag=support-bot&limit=25" \
  -H "Authorization: Bearer $OMNIA_API_KEY"

# Export the same filter as JSONL, straight into your own pipeline
curl "https://gateway.omnia-voice.com/v1/logs/export?tag=support-bot" \
  -H "Authorization: Bearer $OMNIA_API_KEY" > support-bot.jsonl

# Curate it into a training set + a disjoint 10% eval holdout
curl -X POST https://gateway.omnia-voice.com/v1/datasets/from_logs \
  -H "Authorization: Bearer $OMNIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "support-bot-v1", "holdout_pct": 10,
        "filters": { "tag": "support-bot", "cache_hit": false } }'
```

Every returned row carries its `segment` (the auto-detected
[system-prompt family](/concepts/segments)) and `segment_exact` (the exact
prompt variant within it); pass a row's `segment` back as `?segment=`, or as
`filters.segment` on `POST /v1/datasets/from_logs`, to slice traffic you
never tagged.

These endpoints return **409** while request logging is off, because an empty list
would read as "no traffic" when the truth is "nothing is being captured". See
the [Logs & datasets API reference](/api-reference/introduction).

## Privacy posture

* Logged content is **workspace-scoped**: stored under your workspace's key,
  never visible to any other tenant, never used by Omnia for anything.
* Retention is **per-row**: each entry is deleted when its window expires,
  regardless of later settings changes.
* Turning the toggle off stops all writes immediately; existing entries
  simply age out.
* Telemetry and observability remain **content-free** exactly as before;
  this feature adds a separate, consented store; it doesn't change the
  default.
