---
title: "Preview Metric Expression"
method: POST
path: "/api/v1/metrics/preview"
tags: ["metrics"]
---

# Preview Metric Expression

`POST /api/v1/metrics/preview`

Evaluate an unsaved expression and return its value AND its parts (PR 11).

## Request body

- ExpressionPreviewRequest — Preview an UNSAVED expression over a date range (PR 11).
  - `expression` union, required
    - MetricNode — A leaf: one metric resolved through ``resolve_metric_ref`` (any leaf domain).
      - `type` 'metric', required
      - `key` string, required
      - `category` 'standard' | 'custom' | 'formula'
      - `outcome` 'pass' | 'fail', nullable
      - `scope` LeafScope — A narrowing applied to ONE operand of an expression (PR 10). Named as an object with a single field rather than a bare boolean, so adding a property or channel narrowing later is additive instead of a rename. ``is_after_hours`` mirrors :class:`MetricRef` exactly — ``None`` all hours, ``False`` business hours only, ``True`` after hours only — and is applied by the SAME code path. There is deliberately no second definition of "after hours". ``population_filters`` is the named-filter list (business_hours, min_call_25s, …). ``business_hours`` compiles to the same ``is_after_hours IS FALSE`` predicate the hours dropdown already writes; both may be set (AND).
        - `is_after_hours` boolean, nullable
        - `population_filters` string[], nullable
    - ConstantNode — A literal operand — a bare scale factor (``× 100``), or a MEASURED quantity. ``unit`` is what makes the second case honest. A dimensionless constant scales any unit (``multiply``/``divide``) but may only ``add``/``subtract`` with another dimensionless value. A constant that stands for a measured quantity — an average handling time in seconds, say — must carry that unit, or ``calls × aht`` types as *calls* rather than *seconds* and the unit algebra starts quietly agreeing to combinations that have no meaning. ``None`` = dimensionless (the original behaviour). ``value`` is nullable because "not measured yet" is a real, first-class state, not an error: a constant with no value resolves the whole expression to UNDEFINED rather than letting a placeholder be mistaken for a measurement. That distinction is the entire point of the state — a plausible-looking number gets quoted in a meeting and then defended by someone who does not know where it came from.
      - `type` 'constant', required
      - `value` union
        - number
        - string
      - `unit` UnitSpecInput — A unit as a ``dimension → exponent`` vector plus an optional currency code. ``currency_code`` is meaningful only when a ``currency`` dimension is present. Two DIFFERENT codes are incompatible, and ``currency_code=None`` (unknown / resolved per query scope) is incompatible with EVERY coded currency — the mechanism that blocks summing an unknown-currency value into a known one (PR 14 leans on it).
        - `dimensions` object
        - `currency_code` string, nullable
    - BinaryNode — A binary operation over two sub-expressions. ``zero_division`` applies only to ``divide``; ``undefined`` (the default) yields ``None`` for a zero denominator, ``zero`` yields ``Decimal(0)`` (rare, opt-in).
      - `type` 'binary', required
      - `op` 'add' | 'subtract' | 'multiply' | 'divide', required
      - `left` union, required
        - MetricNode — A leaf: one metric resolved through ``resolve_metric_ref`` (any leaf domain).
          - `type` 'metric', required
          - `key` string, required
          - `category` 'standard' | 'custom' | 'formula'
          - `outcome` 'pass' | 'fail', nullable
          - `scope` LeafScope — A narrowing applied to ONE operand of an expression (PR 10). Named as an object with a single field rather than a bare boolean, so adding a property or channel narrowing later is additive instead of a rename. ``is_after_hours`` mirrors :class:`MetricRef` exactly — ``None`` all hours, ``False`` business hours only, ``True`` after hours only — and is applied by the SAME code path. There is deliberately no second definition of "after hours". ``population_filters`` is the named-filter list (business_hours, min_call_25s, …). ``business_hours`` compiles to the same ``is_after_hours IS FALSE`` predicate the hours dropdown already writes; both may be set (AND).
            - `is_after_hours` boolean, nullable
            - `population_filters` string[], nullable
        - ConstantNode — A literal operand — a bare scale factor (``× 100``), or a MEASURED quantity. ``unit`` is what makes the second case honest. A dimensionless constant scales any unit (``multiply``/``divide``) but may only ``add``/``subtract`` with another dimensionless value. A constant that stands for a measured quantity — an average handling time in seconds, say — must carry that unit, or ``calls × aht`` types as *calls* rather than *seconds* and the unit algebra starts quietly agreeing to combinations that have no meaning. ``None`` = dimensionless (the original behaviour). ``value`` is nullable because "not measured yet" is a real, first-class state, not an error: a constant with no value resolves the whole expression to UNDEFINED rather than letting a placeholder be mistaken for a measurement. That distinction is the entire point of the state — a plausible-looking number gets quoted in a meeting and then defended by someone who does not know where it came from.
          - `type` 'constant', required
          - `value` union
            - number
            - string
          - `unit` UnitSpecInput — A unit as a ``dimension → exponent`` vector plus an optional currency code. ``currency_code`` is meaningful only when a ``currency`` dimension is present. Two DIFFERENT codes are incompatible, and ``currency_code=None`` (unknown / resolved per query scope) is incompatible with EVERY coded currency — the mechanism that blocks summing an unknown-currency value into a known one (PR 14 leans on it).
            - `dimensions` object
            - `currency_code` string, nullable
        - BinaryNode — recursive
      - `right` union, required
        - MetricNode — A leaf: one metric resolved through ``resolve_metric_ref`` (any leaf domain).
          - `type` 'metric', required
          - `key` string, required
          - `category` 'standard' | 'custom' | 'formula'
          - `outcome` 'pass' | 'fail', nullable
          - `scope` LeafScope — A narrowing applied to ONE operand of an expression (PR 10). Named as an object with a single field rather than a bare boolean, so adding a property or channel narrowing later is additive instead of a rename. ``is_after_hours`` mirrors :class:`MetricRef` exactly — ``None`` all hours, ``False`` business hours only, ``True`` after hours only — and is applied by the SAME code path. There is deliberately no second definition of "after hours". ``population_filters`` is the named-filter list (business_hours, min_call_25s, …). ``business_hours`` compiles to the same ``is_after_hours IS FALSE`` predicate the hours dropdown already writes; both may be set (AND).
            - `is_after_hours` boolean, nullable
            - `population_filters` string[], nullable
        - ConstantNode — A literal operand — a bare scale factor (``× 100``), or a MEASURED quantity. ``unit`` is what makes the second case honest. A dimensionless constant scales any unit (``multiply``/``divide``) but may only ``add``/``subtract`` with another dimensionless value. A constant that stands for a measured quantity — an average handling time in seconds, say — must carry that unit, or ``calls × aht`` types as *calls* rather than *seconds* and the unit algebra starts quietly agreeing to combinations that have no meaning. ``None`` = dimensionless (the original behaviour). ``value`` is nullable because "not measured yet" is a real, first-class state, not an error: a constant with no value resolves the whole expression to UNDEFINED rather than letting a placeholder be mistaken for a measurement. That distinction is the entire point of the state — a plausible-looking number gets quoted in a meeting and then defended by someone who does not know where it came from.
          - `type` 'constant', required
          - `value` union
            - number
            - string
          - `unit` UnitSpecInput — A unit as a ``dimension → exponent`` vector plus an optional currency code. ``currency_code`` is meaningful only when a ``currency`` dimension is present. Two DIFFERENT codes are incompatible, and ``currency_code=None`` (unknown / resolved per query scope) is incompatible with EVERY coded currency — the mechanism that blocks summing an unknown-currency value into a known one (PR 14 leans on it).
            - `dimensions` object
            - `currency_code` string, nullable
        - BinaryNode — recursive
      - `zero_division` 'undefined' | 'zero'
  - `start_date` string, date, required
  - `end_date` string, date, required
  - `format` string

## Response `200`

Successful Response

- ExpressionPreviewResponse — The value an unsaved expression produces right now, WITH its parts (PR 11). ``components`` is what makes this a provenance surface rather than a spinner: a ratio shows "1,240 ÷ 3,100 = 40%", so a wrong number is diagnosable at the operand that caused it instead of only at the result.
  - `value` string, nullable
  - `formatted` string, required
  - `unit` UnitSpecOutput — A unit as a ``dimension → exponent`` vector plus an optional currency code. ``currency_code`` is meaningful only when a ``currency`` dimension is present. Two DIFFERENT codes are incompatible, and ``currency_code=None`` (unknown / resolved per query scope) is incompatible with EVERY coded currency — the mechanism that blocks summing an unknown-currency value into a known one (PR 14 leans on it).
    - `dimensions` object
    - `currency_code` string, nullable
    - `display_label` string, required — ``to_display_label()``, serialized (Agent-Native PR 12). Every surface that shows a raw value needs to say what unit it is IN — the audit panel prints an exact ``12,150`` directly beneath a formatted ``3.4 h``, and unlabelled that reads as hours and is wrong by a factor of 3,600. The renderer already exists here; exposing it means the client, the agent tools and the disclosures all say ``conversations · seconds`` rather than each inventing a name for the same vector from raw dimension keys.
  - `undefined_reason` string, nullable
  - `disclosures` string[]
  - `components` ExpressionPreviewComponent[]
    - `node_path` string, required
    - `role` string, required
    - `metric_ref` string, nullable
    - `label` string, nullable
    - `value` string, nullable
    - `formatted` string, required
    - `unit` UnitSpecOutput — A unit as a ``dimension → exponent`` vector plus an optional currency code. ``currency_code`` is meaningful only when a ``currency`` dimension is present. Two DIFFERENT codes are incompatible, and ``currency_code=None`` (unknown / resolved per query scope) is incompatible with EVERY coded currency — the mechanism that blocks summing an unknown-currency value into a known one (PR 14 leans on it).
      - `dimensions` object
      - `currency_code` string, nullable
      - `display_label` string, required — ``to_display_label()``, serialized (Agent-Native PR 12). Every surface that shows a raw value needs to say what unit it is IN — the audit panel prints an exact ``12,150`` directly beneath a formatted ``3.4 h``, and unlabelled that reads as hours and is wrong by a factor of 3,600. The renderer already exists here; exposing it means the client, the agent tools and the disclosures all say ``conversations · seconds`` rather than each inventing a name for the same vector from raw dimension keys.
  - `proof` string, nullable

## Other responses

- `401` — Tenant not identified
- `403` — Not authorized to query one of these metrics
- `422` — The expression combines metrics in a way that has no meaning

---

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