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

# Dry-run a custom metric definition

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

Run a candidate predicate against recent stored traces without saving it, and see what it would have recorded.

Definitions are not retroactive, so a saved predicate that matches nothing looks identical to one that matches nothing *yet*. This answers that before you commit: which spans matched, what values they'd contribute, and how many distinct labels the metric would carry.

Samples the newest runs (20 by default, 50 max) — enough to tell a working predicate from a broken one, not a measurement of the true rate. Pass `workforce_id` to make the sample representative of the agent you're actually matching. This is the only endpoint that reads stored traces, which is why it is capped; do not poll it.

Because it returns trace content, `project_id` doubles as the permission target: omit it and the sample spans the whole org, which needs `alarms.manage`; set it and the sample is capped to that project, which needs `projects.alarms.manage` there. A caller scoped to one project must therefore pass it.

## Path parameters

- `org_id` string, required

## Request body

- PreviewMetricDefinitionReq — Body for **POST** `/orgs/{org_id}/metrics/definitions/preview`. The same predicate fields as create, minus everything about persistence.
  - `has_error` boolean, nullable
  - `label_source` string
  - `limit` integer, nullable — Runs to inspect, newest first. Default 20, max 50.
  - `min_duration_ms` integer, nullable
  - `project_id` string, nullable — Sample runs from one project only.
  - `span_name` string, nullable
  - `span_name_prefix` string, nullable
  - `status_code` string, nullable
  - `value_source` string
  - `workforce_id` string, nullable — Sample runs from one agent/workflow (`OrgsApps.id`). Strongly recommended: it makes the sample representative of what you're filtering, instead of whatever the org ran most recently.

## Response `200`

What the definition would record

- PreviewMetricDefinitionOut
  - `labels` PreviewLabelOut[], required — Per-label aggregates, exactly as they'd land in `Metrics`. More than a handful of entries here means `label_source: span_name` over a high-cardinality predicate — every label is another bin per minute.
    - `count` integer, required
    - `label` string, nullable — `null` when `label_source` is `none`.
    - `max` number, double, required
    - `min` number, double, required
    - `sum` number, double, required
  - `observations` integer, required — Total matching spans across the sample.
  - `runs_matched` integer, required — Of those, how many produced at least one observation.
  - `runs_sampled` integer, required — Runs with a trace that were actually inspected.
  - `samples` PreviewSampleOut[], required
    - `run_id` integer, required
    - `span_name` string, required
    - `value` number, double, required

## 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/preview/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)
