Usage & Analytics API

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.

code
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:

EndpointAdditional filters
summaryuserId, model, view
timeseries`bucket=day
by-modeluserId, view
by-userlimit, model, view
top-conversationslimit, `sort=cost
unansweredoptional consistency-check agentId

Endpoints

GET /v1/usage/summary

code
{
  "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

code
{
  "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:

code
{
  "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.