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

# Tracing API Reference

> Manual tracing primitives for custom instrumentation

# Tracing API Reference

The MutagenT SDK provides three levels of abstraction for manual tracing. Choose the one that fits your use case:

| Approach                    | Best For                                          | Effort  |
| --------------------------- | ------------------------------------------------- | ------- |
| `@trace()` decorator        | Class methods with automatic I/O capture          | Lowest  |
| `withTrace()` wrapper       | Functions with manual enrichment via `SpanHandle` | Medium  |
| `startSpan()` / `endSpan()` | Integration adapters, full manual control         | Highest |

All tracing functions are no-ops when tracing is not active, so you can safely leave instrumentation in place without runtime overhead when tracing is disabled. The `@trace()` decorator and `withTrace()` wrapper check for an existing tracing configuration only, while the low-level `startSpan()` / `endSpan()` API will additionally attempt lazy initialization from the `MUTAGENT_API_KEY` environment variable on first use.

***

## Decorators

### `trace(options)`

A method decorator that wraps class methods with automatic span creation. It captures arguments as input, the return value as output, and sets the span status based on success or failure.

**Import:**

```typescript theme={null}
import { trace } from "@mutagent/sdk/tracing";
```

**Options:**

```typescript theme={null}
interface TraceDecoratorOptions {
  kind: SpanKind;       // Required -- the type of operation
  name?: string;        // Span name (defaults to the method name)
  attributes?: Record<string, unknown>;  // Custom attributes
}
```

**Behavior:**

* Captures method arguments as `input.raw`
* Captures the return value as `output.raw`
* Sets `status: "ok"` on successful return
* Sets `status: "error"` with the error message on throw (re-throws the error)
* Works with both synchronous and asynchronous methods
* Preserves `this` context
* Runs the method inside a span context, so nested `@trace()` calls or `getCurrentSpan()` calls see the correct parent
* No-op when tracing is not initialized

**Example:**

```typescript theme={null}
import { initTracing, trace, shutdownTracing } from "@mutagent/sdk/tracing";

initTracing({ apiKey: process.env.MUTAGENT_API_KEY! });

class ResearchAgent {
  @trace({ kind: "agent", name: "research-agent" })
  async run(query: string): Promise<string> {
    const docs = await this.search(query);
    return await this.summarize(docs);
  }

  @trace({ kind: "retrieval", name: "web-search" })
  async search(query: string): Promise<string[]> {
    // This span becomes a child of "research-agent"
    return await fetchSearchResults(query);
  }

  @trace({
    kind: "llm.chat",
    name: "summarize",
    attributes: { "gen_ai.model": "gpt-5.4" },
  })
  async summarize(docs: string[]): Promise<string> {
    return await callLLM(docs.join("\n"));
  }
}

const agent = new ResearchAgent();
await agent.run("What is MutagenT?");

await shutdownTracing();
```

This produces a trace tree:

<Mermaid>
  flowchart TD
  A\["research-agent\n(agent)"] --> B\["web-search\n(retrieval)"]
  A --> C\["summarize\n(llm.chat)"]

  style A fill:#7C3AED,color:#fff
  style B fill:#334155,color:#fff
  style C fill:#06B6D4,color:#fff
</Mermaid>

<Note>
  The `@trace()` decorator uses the legacy TypeScript decorator syntax (`experimentalDecorators`). Make sure your `tsconfig.json` includes `"experimentalDecorators": true` if you use this pattern.
</Note>

***

## Wrappers

### `withTrace(options, fn)`

Wraps a function with a tracing span, providing a `SpanHandle` for manual enrichment of the span during execution.

**Import:**

```typescript theme={null}
import { withTrace } from "@mutagent/sdk/tracing";
```

**Signature:**

```typescript theme={null}
function withTrace<T>(
  options: WithTraceOptions,
  fn: (span: SpanHandle) => T
): T;
```

**Options:**

```typescript theme={null}
interface WithTraceOptions {
  kind: SpanKind;                        // Required -- the type of operation
  name?: string;                         // Span name (defaults to kind)
  input?: SpanIO;                        // Structured input data
  attributes?: Record<string, unknown>;  // Custom attributes
}
```

**SpanHandle methods:**

| Method                        | Description                                             |
| ----------------------------- | ------------------------------------------------------- |
| `span.setAttributes(attrs)`   | Merge custom key-value attributes into the span         |
| `span.addEvent(name, attrs?)` | Record a timestamped lifecycle event                    |
| `span.setOutput(output)`      | Set structured output data (`SpanIO`)                   |
| `span.setInput(input)`        | Set or override structured input data (`SpanIO`)        |
| `span.setMetrics(metrics)`    | Set token counts, cost, model, provider (`SpanMetrics`) |
| `span.spanId`                 | Read-only span ID                                       |
| `span.traceId`                | Read-only trace ID                                      |

**Behavior:**

* Sets `status: "ok"` on successful return
* Sets `status: "error"` with the error message on throw (re-throws the error)
* Works with both synchronous and asynchronous functions
* Runs the function inside a span context for automatic parent-child linking
* No-op when tracing is not initialized (provides a dummy `SpanHandle`)

**Examples:**

<Tabs>
  <Tab title="Basic usage">
    ```typescript theme={null}
    import { withTrace } from "@mutagent/sdk/tracing";

    const answer = await withTrace(
      { kind: "chain", name: "rag-pipeline" },
      async (span) => {
        const docs = await retrieveDocs(query);
        const response = await generateAnswer(query, docs);

        span.setOutput({ text: response });
        span.setMetrics({
          model: "gpt-5.4",
          provider: "openai",
          inputTokens: 1200,
          outputTokens: 350,
          totalTokens: 1550,
        });

        return response;
      }
    );
    ```
  </Tab>

  <Tab title="With input and events">
    ```typescript theme={null}
    import { withTrace } from "@mutagent/sdk/tracing";

    const result = await withTrace(
      {
        kind: "tool",
        name: "database-query",
        input: { text: "SELECT * FROM users WHERE active = true" },
        attributes: { "db.system": "postgresql" },
      },
      async (span) => {
        span.addEvent("query-started");

        const rows = await db.query("SELECT * FROM users WHERE active = true");

        span.addEvent("query-completed", { "db.row_count": rows.length });
        span.setOutput({ raw: rows });

        return rows;
      }
    );
    ```
  </Tab>

  <Tab title="Nested spans">
    ```typescript theme={null}
    import { withTrace } from "@mutagent/sdk/tracing";

    await withTrace({ kind: "workflow", name: "process-document" }, async (outer) => {
      const text = await withTrace({ kind: "tool", name: "extract-text" }, async (inner) => {
        // This span is automatically a child of "process-document"
        return await extractText(document);
      });

      await withTrace({ kind: "llm.chat", name: "analyze" }, async (inner) => {
        inner.setMetrics({ model: "gpt-5.4", provider: "openai" });
        return await analyze(text);
      });
    });
    ```
  </Tab>

  <Tab title="LLM call with messages">
    ```typescript theme={null}
    import { withTrace } from "@mutagent/sdk/tracing";

    const response = await withTrace(
      {
        kind: "llm.chat",
        name: "chat-completion",
        input: {
          messages: [
            { role: "system", content: "You are a helpful assistant." },
            { role: "user", content: "Explain quantum computing." },
          ],
        },
      },
      async (span) => {
        const result = await openai.chat.completions.create({
          model: "gpt-5.4",
          messages: [
            { role: "system", content: "You are a helpful assistant." },
            { role: "user", content: "Explain quantum computing." },
          ],
        });

        span.setOutput({
          messages: [
            { role: "assistant", content: result.choices[0].message.content! },
          ],
        });
        span.setMetrics({
          model: "gpt-5.4",
          provider: "openai",
          inputTokens: result.usage?.prompt_tokens,
          outputTokens: result.usage?.completion_tokens,
          totalTokens: result.usage?.total_tokens,
        });

        return result.choices[0].message.content!;
      }
    );
    ```
  </Tab>
</Tabs>

***

## Low-Level API

The low-level API gives you full control over span lifecycle. This is the layer that integration packages and advanced use cases build on.

### `startSpan(options)`

Create and start a new span. If no trace is active, a new trace ID is generated. If a parent span exists in the current async context, the new span is automatically linked as a child.

If tracing has not been explicitly initialized via `initTracing()`, `startSpan()` will attempt lazy initialization from the `MUTAGENT_API_KEY` environment variable (once per lifecycle).

**Import:**

```typescript theme={null}
import { startSpan } from "@mutagent/sdk/tracing";
```

**Signature:**

```typescript theme={null}
function startSpan(options: SpanOptions): MutagentSpan | undefined;
```

**Options:**

```typescript theme={null}
interface SpanOptions {
  kind: SpanKind;                        // Required -- the type of operation
  name?: string;                         // Span name (defaults to kind)
  input?: SpanIO;                        // Structured input data
  attributes?: Record<string, unknown>;  // Custom attributes
  parentSpanId?: string;                 // Explicit parent (overrides context)
}
```

Returns `undefined` if tracing is not initialized.

***

### `endSpan(span, endOptions?)`

End a span, computing its duration and delivering it to the batch collector.

**Import:**

```typescript theme={null}
import { endSpan } from "@mutagent/sdk/tracing";
```

**Signature:**

```typescript theme={null}
function endSpan(span: MutagentSpan, endOptions?: SpanEndOptions): void;
```

**End Options:**

```typescript theme={null}
interface SpanEndOptions {
  status?: SpanStatus;                   // "ok" | "error" | "unset"
  statusMessage?: string;               // Error message or status description
  output?: SpanIO;                       // Structured output data
  metrics?: SpanMetrics;                 // Token counts, cost, latency
  attributes?: Record<string, unknown>;  // Additional attributes to merge
  events?: SpanEvent[];                  // Lifecycle events to append
}
```

***

### `getCurrentSpan()`

Get the currently active span from the async context.

```typescript theme={null}
import { getCurrentSpan } from "@mutagent/sdk/tracing";

const span = getCurrentSpan();
if (span) {
  console.log(`Active span: ${span.name} (${span.kind})`);
}
```

Returns `undefined` if no span is active or tracing is not initialized.

***

### `getCurrentTraceId()`

Get the trace ID of the currently active trace.

```typescript theme={null}
import { getCurrentTraceId } from "@mutagent/sdk/tracing";

const traceId = getCurrentTraceId();
// Useful for correlating logs with traces
console.log(`Processing request in trace: ${traceId}`);
```

Returns `undefined` if no trace is active or tracing is not initialized.

***

### `runInSpanContext(span, fn)`

Run a function within the async context of a span. This makes the span visible to `getCurrentSpan()` and `getCurrentTraceId()` inside the function, and ensures any child spans created within are linked to this span as their parent.

```typescript theme={null}
import { startSpan, endSpan, runInSpanContext } from "@mutagent/sdk/tracing";

const span = startSpan({ kind: "agent", name: "my-agent" });
if (span) {
  const result = await runInSpanContext(span, async () => {
    // getCurrentSpan() returns `span` here
    // Any startSpan() calls here will have `span` as their parent
    return await doWork();
  });
  endSpan(span, { status: "ok", output: { raw: result } });
}
```

If tracing is not initialized, the function runs directly without any context wrapping.

***

### Full Low-Level Example

```typescript theme={null}
import {
  initTracing,
  shutdownTracing,
  startSpan,
  endSpan,
  runInSpanContext,
} from "@mutagent/sdk/tracing";

initTracing({ apiKey: process.env.MUTAGENT_API_KEY! });

// Root span
const agentSpan = startSpan({ kind: "agent", name: "qa-agent" });

if (agentSpan) {
  await runInSpanContext(agentSpan, async () => {
    // Child span (auto-linked to agentSpan)
    const retrievalSpan = startSpan({
      kind: "retrieval",
      name: "vector-search",
      input: { text: "How does MutagenT work?" },
    });

    if (retrievalSpan) {
      const docs = await vectorStore.search("How does MutagenT work?");
      endSpan(retrievalSpan, {
        status: "ok",
        output: {
          documents: docs.map((d) => ({
            content: d.text,
            score: d.score,
            metadata: { source: d.source },
          })),
        },
      });
    }

    // Another child span
    const llmSpan = startSpan({
      kind: "llm.chat",
      name: "generate-answer",
      input: {
        messages: [
          { role: "user", content: "How does MutagenT work?" },
        ],
      },
    });

    if (llmSpan) {
      await runInSpanContext(llmSpan, async () => {
        const completion = await openai.chat.completions.create({
          model: "gpt-5.4",
          messages: [{ role: "user", content: "How does MutagenT work?" }],
        });

        endSpan(llmSpan, {
          status: "ok",
          output: {
            messages: [
              {
                role: "assistant",
                content: completion.choices[0].message.content!,
              },
            ],
          },
          metrics: {
            model: "gpt-5.4",
            provider: "openai",
            inputTokens: completion.usage?.prompt_tokens,
            outputTokens: completion.usage?.completion_tokens,
            totalTokens: completion.usage?.total_tokens,
          },
        });
      });
    }
  });

  endSpan(agentSpan, { status: "ok" });
}

await shutdownTracing();
```

***

## Type Reference

### `SpanIO`

Structured input and output data for spans. All fields are optional -- use whichever matches your data shape.

```typescript theme={null}
interface SpanIO {
  messages?: Array<{
    role: "system" | "user" | "assistant" | "tool";
    content: string;
    name?: string;
    toolCallId?: string;
  }>;
  toolCalls?: Array<{
    id: string;
    name: string;
    arguments: Record<string, unknown>;
    result?: unknown;
  }>;
  documents?: Array<{
    content: string;
    metadata?: Record<string, unknown>;
    score?: number;
  }>;
  text?: string;
  raw?: unknown;
}
```

| Field       | Use When                                               |
| ----------- | ------------------------------------------------------ |
| `messages`  | LLM chat completions (input prompts, output responses) |
| `toolCalls` | Tool/function calling (input calls, output results)    |
| `documents` | Retrieval operations (retrieved chunks with scores)    |
| `text`      | Simple text input/output                               |
| `raw`       | Anything else (serializable escape hatch)              |

***

### `SpanMetrics`

Quantitative metrics for span performance and cost tracking.

```typescript theme={null}
interface SpanMetrics {
  model?: string;        // e.g., "gpt-5.4", "claude-sonnet-4-6"
  provider?: string;     // e.g., "openai", "anthropic"
  inputTokens?: number;  // Prompt tokens
  outputTokens?: number; // Completion tokens
  totalTokens?: number;  // Total tokens
  costUsd?: number;      // Estimated cost in USD
  latencyMs?: number;    // Execution duration
  ttftMs?: number;       // Time to first token
}
```

***

### `SpanStatus`

```typescript theme={null}
type SpanStatus = "ok" | "error" | "unset";
```

| Status    | Meaning                                    |
| --------- | ------------------------------------------ |
| `"ok"`    | The operation completed successfully       |
| `"error"` | The operation failed                       |
| `"unset"` | Default state -- status not yet determined |

***

### `SpanEvent`

Timestamped lifecycle events that can be attached to spans.

```typescript theme={null}
interface SpanEvent {
  name: string;
  timestamp?: Date;                      // Defaults to now
  attributes?: Record<string, unknown>;  // Event metadata
}
```

***

### `SpanKind`

All 15 span kinds as string literals:

```typescript theme={null}
type SpanKind =
  | "llm.chat" | "llm.completion" | "llm.embedding"   // Generation
  | "chain" | "agent" | "graph" | "node" | "edge"     // Orchestration
  | "workflow" | "middleware"
  | "tool"                                              // Tools
  | "retrieval" | "rerank"                              // Retrieval
  | "guardrail"                                         // Safety
  | "custom";                                           // Other
```

See the [Tracing Overview](/tracing/overview#spankind-taxonomy) for detailed descriptions of each kind.

***

### `MutagentSpan`

The internal mutable span representation during its lifecycle. Returned by `startSpan()` and passed to `endSpan()` and `runInSpanContext()`.

```typescript theme={null}
interface MutagentSpan {
  readonly id: string;
  readonly traceId: string;
  readonly parentSpanId: string | undefined;
  readonly kind: SpanKind;
  readonly name: string;
  readonly startedAt: Date;
  input: SpanIO | undefined;
  output: SpanIO | undefined;
  status: SpanStatus;
  statusMessage: string | undefined;
  metrics: SpanMetrics;
  attributes: Record<string, unknown>;
  events: SpanEvent[];
}
```

***

### `FinishedSpan`

Immutable span representation after `endSpan()` is called. This is the shape serialized and sent to the API by the batch buffer.

```typescript theme={null}
interface FinishedSpan {
  readonly spanId: string;
  readonly traceId: string;
  readonly parentSpanId: string | undefined;
  readonly name: string;
  readonly kind: SpanKind;
  readonly input: SpanIO | undefined;
  readonly output: SpanIO | undefined;
  readonly startTime: string;     // ISO 8601
  readonly endTime: string;       // ISO 8601
  readonly durationMs: number;
  readonly model: string | undefined;
  readonly provider: string | undefined;
  readonly inputTokens: number | undefined;
  readonly outputTokens: number | undefined;
  readonly totalTokens: number | undefined;
  readonly costUsd: number | undefined;
  readonly status: SpanStatus;
  readonly statusMessage: string | undefined;
  readonly attributes: Record<string, unknown>;
  readonly events: SerializedSpanEvent[];
  readonly metadata: Record<string, unknown> | undefined;
}

interface SerializedSpanEvent {
  readonly name: string;
  readonly timestamp: string;  // ISO 8601
  readonly attributes?: Record<string, unknown>;
}
```

***

### `TracePayload`

The HTTP payload format sent to `POST /api/traces`. Spans are grouped by trace ID. You do not construct this manually -- the SDK builds it automatically.

```typescript theme={null}
interface TracePayload {
  traces: Array<{
    traceId: string;
    name?: string;
    sessionId?: string;
    source: string;
    environment?: string;
    spans: Array<{
      spanId: string;
      parentSpanId?: string;
      name: string;
      kind: SpanKind;
      input?: SpanIO;
      output?: SpanIO;
      startTime: string;        // ISO 8601
      endTime: string;          // ISO 8601
      durationMs: number;
      model?: string;
      provider?: string;
      inputTokens?: number;
      outputTokens?: number;
      totalTokens?: number;
      costUsd?: number;
      status: SpanStatus;
      statusMessage?: string;
      attributes?: Record<string, unknown>;
      events?: SerializedSpanEvent[];
      metadata?: Record<string, unknown>;
    }>;
  }>;
}

interface TraceHTTPResponse {
  accepted: number;
  rejected: number;
}
```

***

### `TracingConfig`

Configuration object passed to `initTracing()`. See the [Tracing Setup](/tracing/setup) page for full details on each option.

```typescript theme={null}
interface TracingConfig {
  apiKey: string;              // Required
  endpoint?: string;           // Default: "https://api.mutagent.io"
  environment?: string;        // e.g., "production", "staging", "development"
  batchSize?: number;          // Default: 10
  flushIntervalMs?: number;    // Default: 5000
  debug?: boolean;             // Default: false
  source?: string;             // Default: "sdk"
}
```

***

## When to Use Which Approach

<AccordionGroup>
  <Accordion title="Use @trace() decorator when...">
    * You have a class-based architecture with methods that represent distinct operations
    * You want zero-effort input/output capture (arguments and return values are recorded automatically)
    * You do not need to set custom metrics or structured I/O during execution

    ```typescript theme={null}
    class MyAgent {
      @trace({ kind: "agent" })
      async run(query: string) { /* ... */ }
    }
    ```
  </Accordion>

  <Accordion title="Use withTrace() wrapper when...">
    * You want to enrich the span during execution (set metrics, output, events)
    * You are working with standalone functions rather than class methods
    * You need the `SpanHandle` to record token usage, model info, or custom events

    ```typescript theme={null}
    await withTrace({ kind: "llm.chat" }, async (span) => {
      span.setMetrics({ model: "gpt-5.4", inputTokens: 500 });
      // ...
    });
    ```
  </Accordion>

  <Accordion title="Use startSpan() / endSpan() when...">
    * You are building a framework integration or adapter
    * Span start and end happen in different scopes or callbacks
    * You need to explicitly control when context propagation happens via `runInSpanContext()`

    ```typescript theme={null}
    const span = startSpan({ kind: "tool" });
    // ... later, in a callback ...
    endSpan(span, { status: "ok" });
    ```
  </Accordion>
</AccordionGroup>

***

## Server-Side Trace API

The MutagenT server exposes these REST endpoints for trace ingestion and retrieval. The SDK handles ingestion automatically, but you can also call these endpoints directly.

All endpoints require workspace context via the `x-workspace-id` header and authentication via `x-api-key` (API key) or `Authorization: Bearer` (OAuth token).

### Ingestion Endpoints

| Method                   | Path                                    | Description                                   |
| ------------------------ | --------------------------------------- | --------------------------------------------- |
| `POST /api/traces`       | Ingest a single trace with spans        | Idempotent -- same `traceId` updates existing |
| `POST /api/traces/batch` | Batch ingest up to 100 traces           | More efficient for high-volume ingestion      |
| `POST /api/traces/otlp`  | OTLP bridge (ExportTraceServiceRequest) | Accepts OTel HTTP/JSON format                 |

### Retrieval Endpoints

| Method                      | Path                             | Description                                                                         |
| --------------------------- | -------------------------------- | ----------------------------------------------------------------------------------- |
| `GET /api/traces`           | List traces with filtering       | Returns slim `TraceListItem[]` by default; use `?include=spans` for full span trees |
| `GET /api/traces/:id`       | Get trace by ID with all spans   | Returns full span tree                                                              |
| `GET /api/traces/stats`     | Aggregated trace stats           | Group by `model`, `provider`, `kind`, or `source`                                   |
| `GET /api/traces/analytics` | Analytics summary with KPIs      | Cost breakdown, latency percentiles, error rate                                     |
| `DELETE /api/traces/:id`    | Delete a trace and all its spans | Permanent deletion                                                                  |

### `GET /api/traces` — Slim List Response

Since PR #476, `GET /api/traces` returns a **slim list** of `TraceListItem` objects rather than full span trees. This reduces typical list-page payloads by \~98%.

**Default response shape (`TraceListItem`):**

```typescript theme={null}
interface TraceListItem {
  traceId: string;
  name: string;
  source: string;
  environment: string;
  status: "running" | "completed" | "error";
  traceKind: SpanKind;        // Kind of the root span (e.g. "agent", "chain")
  startTime: string;          // ISO 8601
  endTime: string | null;     // ISO 8601, null if still running
  durationMs: number | null;
  model: string | undefined;
  provider: string | undefined;
  inputTokens: number | undefined;
  outputTokens: number | undefined;
  totalTokens: number | undefined;
  costUsd: number | undefined;
  spanCount: number;          // Total number of spans in the trace
}
```

The `traceKind` field reflects the `kind` of the root span and is the primary way to distinguish agent traces (`agent`) from pipeline traces (`chain`) or LLM traces (`llm.chat`) at list-view without fetching spans.

**Fetching full spans:**

To retrieve the complete span tree for all traces in a list response, append `?include=spans`:

```bash theme={null}
# Slim list (default) -- fast, small payload
GET /api/traces?limit=20

# Full span trees -- use only when spans are needed
GET /api/traces?limit=20&include=spans
```

<Warning>
  `?include=spans` can return large payloads for traces with many spans. Prefer fetching individual traces via `GET /api/traces/:id` when you need full span details for a specific trace.
</Warning>

**TypeScript SDK example:**

```typescript theme={null}
import { Mutagent } from "@mutagent/sdk";

const client = new Mutagent({ apiKey: process.env.MUTAGENT_API_KEY! });

// List recent agent traces (slim, no spans)
const traces = await client.trace.listTraces({
  source: "claude-code",
  limit: 20,
});

for (const trace of traces.data) {
  console.log(`${trace.traceId}  kind=${trace.traceKind}  spans=${trace.spanCount}`);
}

// Fetch full span tree for a specific trace
const full = await client.trace.getTrace({ id: traces.data[0].traceId });
console.log(full.spans); // Full FinishedSpan[]
```

### Query Filters for `GET /api/traces`

| Parameter     | Type     | Description                                                                                |
| ------------- | -------- | ------------------------------------------------------------------------------------------ |
| `sessionId`   | `string` | Filter by session ID                                                                       |
| `status`      | `string` | Filter by status: `running`, `completed`, `error`                                          |
| `source`      | `string` | Filter by source: `sdk`, `langchain`, `langgraph`, `vercel-ai`, `openai`, `otel`, `manual` |
| `environment` | `string` | Filter by environment: `production`, `staging`, `development`                              |
| `startAfter`  | `string` | ISO 8601 timestamp -- traces starting after this time                                      |
| `startBefore` | `string` | ISO 8601 timestamp -- traces starting before this time                                     |
| `limit`       | `number` | Page size (1--100, default 20)                                                             |
| `offset`      | `number` | Pagination offset (default 0)                                                              |
| `include`     | `string` | Set to `spans` to include full span trees in each list item                                |

***

### `GET /api/traces/stats`

Retrieve aggregated trace statistics, grouped by a specified dimension.

**Query Parameters:**

| Parameter   | Type     | Description                                                     |
| ----------- | -------- | --------------------------------------------------------------- |
| `groupBy`   | `string` | Dimension to group by: `model`, `provider`, `kind`, or `source` |
| `from`      | `string` | ISO 8601 start date for the query range                         |
| `to`        | `string` | ISO 8601 end date for the query range                           |
| `sessionId` | `string` | Filter by session ID                                            |

Returns aggregated metrics (trace count, token usage, latency, cost) grouped by the specified dimension.

***

### `GET /api/traces/analytics`

Retrieve an analytics summary with key performance indicators (KPIs).

**Query Parameters:**

| Parameter     | Type     | Description                        |
| ------------- | -------- | ---------------------------------- |
| `startAfter`  | `string` | ISO 8601 start date                |
| `startBefore` | `string` | ISO 8601 end date                  |
| `budget`      | `number` | Budget threshold for cost analysis |

Returns an analytics summary including cost breakdown, token usage over time, latency percentiles, error rate, and cost efficiency metrics.

***

## Python API

The Python SDK provides the same low-level API with snake\_case naming. Decorators and wrappers are not yet available in Python -- use the low-level API directly.

```python theme={null}
import asyncio
from mutagent.tracing import (
    init_tracing,
    shutdown_tracing,
    start_span,
    end_span,
    get_current_span,
    get_current_trace_id,
    SpanOptions,
    SpanEndOptions,
    SpanIO,
    SpanMetrics,
    SpanKind,
    SpanStatus,
)

init_tracing(api_key="mt_xxxxxxxxxxxx")

# Create a span
span = start_span(SpanOptions(
    kind=SpanKind.CHAIN,
    name="rag-pipeline",
    input=SpanIO(text="What is MutagenT?"),
))

if span:
    # Do work...
    result = generate_answer("What is MutagenT?")

    # End the span
    end_span(span, SpanEndOptions(
        status=SpanStatus.OK,
        output=SpanIO(text=result),
        metrics=SpanMetrics(
            model="gpt-5.4",
            provider="openai",
            input_tokens=800,
            output_tokens=200,
            total_tokens=1000,
        ),
    ))

asyncio.run(shutdown_tracing())
```

<Note>
  Python context propagation uses `contextvars`, which works across `async`/`await` and threads. Parent-child linking is automatic when spans are nested within the same execution context.
</Note>
