---
title: "Usage & Analytics API"
description: "The current hosted usage endpoints, filters, response envelopes, and server-only authentication model."
---

The hosted API records per-turn token and cost data. These endpoints are served
by `@mordn/chat-api`, not by your Next.js handler, and require a server-side
mordn API key.

> **The key fixes tenant and agent scope**
>
> The API key resolves both tenant and agent. Callers cannot select another agent
> with a query parameter. Never expose the key to the browser.

```bash
curl "https://api.mordn.com/v1/usage/summary?view=production" \
  -H "Authorization: Bearer $MORDN_CHAT_KEY"
```

## Window and filters

The default window is the last **14 days**. `from` is inclusive and `to` is
exclusive. Both are ISO timestamps. `view` is `production`, `preview`, or `all`
(the default).

Filters differ by endpoint:

| Endpoint | Additional filters |
| --- | --- |
| `summary` | `userId`, `model`, `view` |
| `timeseries` | `bucket=day|hour`, `userId`, `model`, `view` |
| `by-model` | `userId`, `view` |
| `by-user` | `limit`, `model`, `view` |
| `top-conversations` | `limit`, `sort=cost|tokens`, `userId`, `model`, `view` |
| `unanswered` | optional consistency-check `agentId` |

## Endpoints

### `GET /v1/usage/summary`

```json
{
  "totals": {
    "events": 12840,
    "inputTokens": 8340221,
    "outputTokens": 2114880,
    "totalTokens": 10455101,
    "cachedInputTokens": 512000,
    "cacheReadTokens": 0,
    "cacheWriteTokens": 0,
    "costUsd": "42.1187",
    "inputCostUsd": "30.0000",
    "outputCostUsd": "12.1187",
    "marketCostUsd": "40.0000",
    "surchargeUsd": "2.1187"
  }
}
```

### `GET /v1/usage/timeseries?bucket=day`

```json
{
  "series": [
    {
      "ts": "2026-06-30T00:00:00.000Z",
      "events": 502,
      "inputTokens": 320000,
      "outputTokens": 90233,
      "totalTokens": 410233,
      "costUsd": "1.6644"
    }
  ]
}
```

### `GET /v1/usage/by-model`

Returns `{ models: [{ model, events, totalTokens, costUsd }] }` ordered by cost.

### `GET /v1/usage/by-user`

Returns `{ users: [{ userId, events, totalTokens, costUsd }] }`.

### `GET /v1/usage/top-conversations`

Returns `{ conversations: [{ conversationId, events, totalTokens, costUsd }] }`.
The aggregate does not include `userId`.

### `GET /v1/usage/unanswered`

Returns recent retrieval misses grouped by normalized query:

```json
{
  "questions": [
    {
      "query": "How do I rotate a key?",
      "count": 7,
      "lastAskedAt": "2026-07-14T08:00:00.000Z",
      "avgTopScore": 0.08
    }
  ]
}
```

An optional `agentId` must match the key's agent or the API returns `403`.
Because this endpoint returns end-user query text, apply the same access and
retention controls as other analytics data.

## Per-turn record

`UsageRecord` can also carry `resolvedProvider`, finish reason, step count,
latency, generation id, exact decimal cost strings, and
`raw?: Record<string, unknown>` provider metadata. Not every field is exposed by
every aggregate endpoint.

For a self-hosted backend, use `onChatFinish` or persist `SaveTurnInput.usage`
in your own store.
