# Analyse Production Traffic

The analytics endpoints answer "how much did this agent cost last week". They do
not answer "what happened last night, and did it work". For that you need the
rows, not the aggregates - and five calls get all of them.

## The five calls

Every route below is a `GET` and is paginated. Four of them (`/api/messages`,
`/api/audit-logs`, `/api/ai-logs`, `/api/pipeline-traces`) default `limit` to 50
(maximum 200) and take an ISO-8601 `from` / `to` window. `/api/conversations` is
the odd one out: it defaults `limit` to 20 (maximum 100), and its window is
`updatedSince` / `updatedUntil`, not `from` / `to`.

| # | Route | Permission | What it gives you |
|---|---|---|---|
| 1 | `/api/conversations?instanceId=&updatedSince=&updatedUntil=` | `conversation:read` | which conversations were touched in the window |
| 2 | `/api/messages?instanceId=&from=&to=` | `conversation:read` | every message of that agent, across all conversations |
| 3 | `/api/audit-logs?instanceId=&from=&to=` | `audit_log:read` | every tool call, with its arguments, output and error |
| 4 | `/api/ai-logs?instanceId=&from=&to=` | `analytics:read` | every LLM call: tokens, cache breakdown, cost, `callType`, `outcome` |
| 5 | `/api/pipeline-traces?instanceId=&from=&to=` | `analytics:read` | every turn: total, LLM and per-phase timings |

From [`polyant-cli`](https://docs.polyant.ai/admin-panel/api-keys#using-a-key-with-polyant-cli), authenticated with a management key, the same five are:

```bash
polyant-cli agent conversations list acme --limit 100 --json
polyant-cli agent messages acme --from 2026-08-31T18:00:00Z --limit 200 --json
polyant-cli monitor events --instance acme --from 2026-08-31T18:00:00Z --json
polyant-cli monitor ai-logs --instance acme --from 2026-08-31T18:00:00Z --json
polyant-cli monitor traces --instance acme --from 2026-08-31T18:00:00Z --json
```

Join them on `conversationId`, which every one of the five carries.

### When the question starts from one conversation

Joining after the fact is for a window. Where the subject is a single conversation, three more routes narrow to it directly, and one narrows to a single turn:

| Route | Parameter | Permission |
|---|---|---|
| `/api/audit-logs` | `conversationId` | `audit_log:read` |
| `/api/instances/{slug}/governance/events` | `conversationId`, `messageId` | `agent.governance:read` |
| `/memories` | `sourceConversationId` | `memory:read` |

`/api/conversations/{id}/messages` also takes `around=<messageId>` and returns a window centred on that message rather than the newest page, which is how a tool call in `/api/audit-logs` is read back in the context of the turn that issued it.

### When the segment is the answer

If what you want is one row per conversation with its health, latency and cost already computed, do not compute it from these five: `GET /api/conversations/export.csv` streams the whole filtered segment as CSV, with the same predicates, order and columns as the [conversations table](https://docs.polyant.ai/admin-panel/conversations#export-csv), capped at 50,000 rows. The aggregates come from `ai_logs`, `pipeline_traces`, `tool_audit_logs`, `hook_executions` and `governance_events` - the same tables three of the routes above serve raw.

## Two things that will make your numbers wrong

**Failed and aborted LLM calls count toward cost, not toward call counts.**
The dashboard's cost totals include every billed call, failed and pre-empted
(`aborted`) ones included, and embedding calls. Its call counts and response
times count only model calls that returned (`outcome = 'ok'`). The raw route
returns every row and exposes the column, so filter on `outcome` (`?outcome=ok`)
when you recompute counts or latencies from it.

**Retention truncates the window from underneath.** A global age-based sweep
purges `ai_logs`, `pipeline_traces`, `tool_audit_logs`, `hook_executions` and
`scheduled_task_runs` on their own schedule
(the **Analytics retention** [installation policy](https://docs.polyant.ai/admin-panel/installation-policies), default **90 days**), independent of how long the
conversations they belong to are kept. A window older than that therefore looks
like conversations with no cost and no tool calls - which is missing data, not free traffic.
Check the retention window in Installation policies before reading an old one; shortening it deletes history on the next housekeeping run, and the change is recorded in the management audit trail.

## What is not exposed

The exact prompt sent to the provider is never returned by `/api/messages`. It
is captured only when the agent has debug capture on, and it is read one message
at a time from
`/api/conversations/{conversationId}/messages/{messageId}/debug`, the route behind
the panel's [Message details](https://docs.polyant.ai/admin-panel/conversations#message-details).
That route also returns the turn's model calls (`modelCalls`) and its channel,
streaming flag and transcription (`turn`), with or without debug capture.

The panel's [Monitor](https://docs.polyant.ai/admin-panel/activity) shows tool calls, hooks, webhook
conversations and scheduled runs in one timeline, but its route answers a panel
session only: script against the five routes above.

## Scoping

Every one of the five routes is scoped to the caller's organization and to the
workspace the request names. An agent slug belonging to another tenant returns
**zero rows**, not an error - you cannot tell an empty agent from one you may not
read, and that is deliberate.
