---
title: "Define a custom metric"
method: POST
path: "/orgs/{org_id}/metrics/definitions"
tags: ["alarms"]
---

# Define a custom metric

`POST /orgs/{org_id}/metrics/definitions`

Define a metric of your own, emitted as runs land. Once it exists it behaves like any platform metric: readable through `GET /orgs/{org_id}/metrics`, listed in the catalog with `origin: custom`, and alarmable with no extra setup.

The definition is a span predicate over each completed run's trace. Span paths are `{workforce}.{step}` and matching is on the **leaf**: use `llm` or `get_address`, not the full path. Predicate fields AND together and all are optional — omitting every one matches every non-root span.

`metric_name` is forced under the reserved `trace.` prefix so a definition cannot shadow a platform series. Set `label_source` to `span_name` to break the metric out per step, then point an alarm at a single step with `dims.label`.

Definitions are **not retroactive**: matching happens as runs arrive, so a new definition only sees traces written after it. Dry-run it against stored traces first with `POST /orgs/{org_id}/metrics/definitions/preview`. Note also that only sum/count/avg/min/max are available on the result — percentiles are not derivable from metric bins.

Set `project_id` to scope the definition to one project, which also makes it editable by holders of `projects.alarms.manage` there. Leaving it out defines an org-wide metric that runs against every project's traces, and needs `alarms.manage`.

## Path parameters

- `org_id` string, required

## Request body

- CreateMetricDefinitionReq — Body for **POST** `/orgs/{org_id}/metrics/definitions`. Every predicate field is optional and they AND together. All omitted means "every non-root span", which paired with `label_source: span_name` gives a per-step call count — a reasonable first definition.
  - `has_error` boolean, nullable — `true` = only errored spans, `false` = only clean ones, omitted = both.
  - `label_source` string — `none` (default) or `span_name` to break the metric out per step.
  - `metric_name` string, required — Destination metric. `trace.` is prepended when missing.
  - `min_duration_ms` integer, nullable — Only spans at least this slow. Spans still running never match.
  - `name` string, required — Human label for the definition itself, unique within the org.
  - `project_id` string, nullable — Scope to one project. Omit to apply across the org.
  - `source` 'trace_span' — Where a definition draws its observations from. One variant today. It is in the payload from the start so that adding a second source is an additive change to this enum instead of a new resource alongside `/metrics/definitions` — the reason the endpoint is named for metrics rather than for traces.
  - `span_name` string, nullable — Exact span name — the leaf of the span path (`llm`, `get_address`), not the full `{workforce}.{step}`.
  - `span_name_prefix` string, nullable — Span-name prefix, for matching a family of tools (`get_`).
  - `status_code` string, nullable
  - `value_source` string — `count` (default), `duration_ms`, or `usage:<key>`.

## Response `201`

Definition created

- MetricDefinitionOut
  - `created_at` string, date-time, required
  - `enabled` boolean, required
  - `has_error` boolean, nullable
  - `id` integer, required
  - `label_source` string, required — `none` or `span_name`.
  - `metric_name` string, required
  - `min_duration_ms` integer, nullable
  - `name` string, required
  - `project_id` integer, nullable — Project scope; omitted when the filter applies to the whole org.
  - `source` 'trace_span', required — Where a definition draws its observations from. One variant today. It is in the payload from the start so that adding a second source is an additive change to this enum instead of a new resource alongside `/metrics/definitions` — the reason the endpoint is named for metrics rather than for traces.
  - `span_name` string, nullable
  - `span_name_prefix` string, nullable
  - `status_code` string, nullable
  - `value_source` string, required — `count`, `duration_ms`, or `usage:<key>`.

## Other responses

- `400` — Bad request
- `403` — Forbidden

## Changes

- **2026-07-28** `dd7e398ce0d3` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/timbal-ai/apis/timbal-platform-api/changes/orgs/:org_id/metrics/definitions/post.md)

---

[API](https://skmtc.dev/timbal-ai/apis/timbal-platform-api.md) · [All operations](https://skmtc.dev/timbal-ai/apis/timbal-platform-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/timbal-ai/timbal-platform-api/revisions/dd7e398ce0d3/schema)
