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

# Metrics Registry

> Code-first dashboard tabs and cards — declare metrics in agent code and sync them to the platform

The **Metrics Registry** lets agent code declare its own dashboard tabs and cards, then sync them to the platform with a single call. It mirrors the canonical shape used by the CLI's `rf metrics sync` command — same validation, same idempotency — so the same definitions work whether you sync at deploy time or from inside the agent process.

<Note>
  Available since `@runflow-ai/sdk@1.1.12`. The portal-based dashboard editor still works — Metrics Registry is a code-first alternative for teams that want metrics tracked as code.
</Note>

## When to use

* You want dashboard definitions in version control next to the agent code.
* You're rolling out the same dashboard across many agents and want the definitions reusable.
* You're migrating an existing portal-built dashboard into code (legacy field shapes like `tableMode` are auto-normalized).

If a one-off card is fine, the portal **Metrics** tab is faster — see [Business Event Tracking](/core-concepts/observability#business-event-tracking) for that flow.

## Quick start

```typescript theme={null}
import { metrics } from '@runflow-ai/sdk/observability';

// 1. Declare a tab (idempotent on `name`)
metrics.defineTab({ name: 'Vendas' });

// 2. Declare a card on that tab
metrics.defineCard({
  tab: 'Vendas',
  title: 'Funil de checkout',
  cardType: 'funnel',
  config: {
    funnelMode: 'multi_event',
    steps: [
      { eventName: 'cart_open',        label: 'Carrinho aberto' },
      { eventName: 'checkout_started', label: 'Início checkout' },
      { eventName: 'sale',             label: 'Pagamento' },
    ],
  },
  gridLayout: { x: 0, y: 0, w: 6, h: 5 },
});

// 3. Push to the platform
await metrics.sync();
```

The registry is a **process-wide singleton** — `import { metrics }` returns the same instance from every module, so you can split declarations across files freely.

## Validation is synchronous

`defineCard()` validates against the shared Zod schemas at registration time. Shape mistakes throw immediately:

```typescript theme={null}
metrics.defineCard({
  tab: 'Vendas',
  title: 'My card',
  cardType: 'funnel',
  config: { /* missing required `steps` */ },
});
// ❌ Throws: metrics.defineCard("My card"): config.steps: Required
```

Legacy aliases used by the portal editor are auto-normalized before validation (for example, the old `tableMode` field maps to the new `mode` field), so migrating an old definition usually doesn't require any changes.

Each `(cardType, title)` pair must be unique in the registry — registering twice throws to prevent silent dupes.

## `sync()` is two-phase

Tabs are upserted first, then cards. The platform resolves each `tab` name to a `tabId` before the card upsert, so cards always land on the right tab:

```typescript theme={null}
const result = await metrics.sync();
// {
//   agentId: 'agent-uuid',
//   tabs:  { created: 1, updated: 2, total: 3 },
//   cards: { created: 4, updated: 1, failed: 0, total: 5 },
// }
```

Per-card idempotency uses a deterministic key (`${cardType}:${title}:${tab ?? ''}`), so repeated syncs only update — they never duplicate. By default failures are logged and skipped; pass `{ strict: true }` to make any failure throw.

## Configuration

`sync()` reads credentials from these in order:

| Source                | Variable                                                  |
| --------------------- | --------------------------------------------------------- |
| Explicit option       | `metrics.sync({ agentId, baseUrl, apiKey })`              |
| Environment           | `RUNFLOW_API_URL`, `RUNFLOW_API_KEY`, `RUNFLOW_AGENT_ID`  |
| Runtime context (SDK) | Set by the platform when running inside a deployed agent. |

Missing any of the three throws — surface them in your config before calling `sync()`.

## CLI parity

The CLI command `rf metrics sync` posts the exact same shape to the same endpoints. You can switch between code-first and CLI-driven dashboards without recomputing anything — the platform dedupes by the same idempotency key on both sides.

## Card types

`defineCard()` supports the same card catalog as the portal editor — `number`, `rate`, `line`, `bar`, `pie`, `funnel`, `table`, `gauge`, etc. Each type expects its own `config` shape; see the portal **Metrics** tab for the canonical UI of every card type.

## Listing and consuming cards

You can read back what's published — and even drive your own renderer — over the runtime REST API:

* `GET /api/v1/runtime/observability/dashboard-cards?agentId=` — list every card configured for an agent (returns `cardType`, `config`, `gridLayout`, `tabId`).
* `POST /api/v1/runtime/observability/dashboard-cards` — the same endpoint `metrics.sync()` calls. Safe to invoke directly from server-to-server jobs.
* `GET /api/v1/runtime/observability/dashboard-tabs?agentId=` — list configured tabs.
* `POST /api/v1/runtime/observability/dashboard-tabs` — idempotent upsert (used by `metrics.defineTab(...) → metrics.sync()`).
* `POST /api/v1/runtime/v1/observability/events` — push events from outside the SDK.
* `GET /api/v1/runtime/v1/observability/events/feed` — most-recent-first event feed (filter by `agentId`, `eventName`, paginated).
* `POST /api/v1/runtime/v1/observability/events/query` — table-style query, `mode: 'raw'` for individual rows or `mode: 'aggregate'` with `groupBy` + `metrics` for grouped reports.

For **KPI-style aggregation** (single-number queries — `sum`, `avg`, `count`, `rate`, `distinct_count`, `group_by` with optional `dateGrouping` for time series), use the MCP surface:

* `aggregate_events` — returns the raw value or series. Same shape the portal Metrics tab uses.
* `query_events` — same as the REST endpoint, also available via MCP.
* `render_dashboard_card` — pass a `cardId` and get back the current value, dispatching on `cardType` + `config` (supports number / rate / line / bar / pie / gauge / table / funnel).

See [REST Endpoints → Dashboards & Events](/api-reference/api-client#dashboards-events) and [MCP → Available MCP Tools](/core-concepts/mcp#available-mcp-tools-runflow-public-mcp) for the full shapes.

<Note>
  Discovery endpoints (`event_names`, `event_properties`, `property-values`) remain portal-only over REST. They're also exposed as MCP tools (`get_event_names`, `get_event_properties`) for AI clients.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Business Event Tracking" icon="chart-line" href="/core-concepts/observability#business-event-tracking">
    Emit the events your cards aggregate
  </Card>

  <Card title="Metrics API Reference" icon="code" href="/api-reference/observability#code-first-metrics">
    Signatures and options
  </Card>
</CardGroup>
