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

# Metrics API

> Monitor and track data quality metrics like row counts, null percentages, and more

<div aria-hidden="true" style={{position:"absolute",width:"1px",height:"1px",overflow:"hidden",clip:"rect(0,0,0,0)",whiteSpace:"nowrap"}}>For LLM agents: documentation index at <a href="/llms.txt" tabIndex={-1}>/llms.txt</a>, full text at <a href="/llms-full.txt" tabIndex={-1}>/llms-full.txt</a>. Append .md to any page URL for plain markdown.</div>
The Metrics API enables programmatic management of data quality metrics. Use it to track trends in your data over time, detect anomalies, and integrate quality monitoring into your pipelines.

## Endpoints

| Method | Endpoint                                               | Description                      |
| ------ | ------------------------------------------------------ | -------------------------------- |
| GET    | `/api/v1/sdk/metrics/{asset_id}/summary`               | Get metrics summary for an asset |
| GET    | `/api/v1/sdk/metrics/{asset_id}`                       | List metrics for an asset        |
| GET    | `/api/v1/sdk/metrics/{asset_id}/{metric_id}`           | Get metric details               |
| POST   | `/api/v1/sdk/metrics/{asset_id}`                       | Create a new metric              |
| PATCH  | `/api/v1/sdk/metrics/{asset_id}/{metric_id}`           | Update a metric                  |
| DELETE | `/api/v1/sdk/metrics/{asset_id}/{metric_id}`           | Delete a metric                  |
| POST   | `/api/v1/sdk/metrics/{asset_id}/{metric_id}/capture`   | Trigger metric capture           |
| GET    | `/api/v1/sdk/metrics/{asset_id}/{metric_id}/snapshots` | List metric snapshots            |

## Metric Types

| Type              | Description                                    | Requires Column |
| ----------------- | ---------------------------------------------- | --------------- |
| `row_count`       | Total row count of the table                   | No              |
| `null_percent`    | Percentage of null values                      | Yes             |
| `distinct_count`  | Count of distinct values                       | Yes             |
| `duplicate_count` | Count of duplicate values                      | Yes             |
| `min_value`       | Minimum numeric value                          | Yes             |
| `max_value`       | Maximum numeric value                          | Yes             |
| `mean`            | Average numeric value                          | Yes             |
| `percentile`      | Percentile value (requires `percentile_value`) | Yes             |

## Get Metrics Summary

```
GET /api/v1/sdk/metrics/{asset_id}/summary
```

Returns aggregate metrics statistics for an asset.

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer aa_live_xxx" \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000/summary"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()
  summary = client.metrics.summary("550e8400-e29b-41d4-a716-446655440000")
  print(f"Active metrics: {summary.active_metrics}/{summary.total_metrics}")
  print(f"Health: {summary.health_percentage}%")
  ```

  ```bash CLI theme={null}
  armor metrics summary 550e8400-e29b-41d4-a716-446655440000
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "total_metrics": 15,
    "active_metrics": 12,
    "total_checks": 8,
    "passing": 6,
    "failing": 1,
    "warning": 1,
    "error": 0,
    "health_percentage": 87.5
  }
}
```

## List Metrics

```
GET /api/v1/sdk/metrics/{asset_id}
```

### Query Parameters

| Parameter     | Type    | Default | Description                                        |
| ------------- | ------- | ------- | -------------------------------------------------- |
| `metric_type` | string  | -       | Filter by type (e.g., `row_count`, `null_percent`) |
| `is_active`   | boolean | -       | Filter by active status                            |
| `limit`       | integer | 50      | Max results (max: 100)                             |
| `offset`      | integer | 0       | Results to skip                                    |

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer aa_live_xxx" \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000?metric_type=null_percent&limit=10"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()

  # List all metrics
  metrics = client.metrics.list("550e8400-e29b-41d4-a716-446655440000")

  # Filter by type
  null_metrics = client.metrics.list(
      "550e8400-e29b-41d4-a716-446655440000",
      metric_type="null_percent",
  )
  for m in null_metrics:
      print(f"{m.table_path}.{m.column_name}: {m.metric_type}")
  ```

  ```bash CLI theme={null}
  armor metrics list 550e8400-e29b-41d4-a716-446655440000 --type null_percent
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "items": [
      {
        "id": "m_550e8400-e29b-41d4-a716-446655440001",
        "internal_id": 123,
        "asset_id": 456,
        "table_path": "snowflake.prod.warehouse.orders",
        "column_name": "customer_email",
        "metric_type": "null_percent",
        "capture_interval": "daily",
        "sensitivity": 3,
        "is_active": true,
        "created_at": "2026-01-01T10:00:00Z"
      }
    ]
  },
  "pagination": {
    "total": 15,
    "limit": 50,
    "offset": 0,
    "has_more": false
  }
}
```

## Get Metric Details

```
GET /api/v1/sdk/metrics/{asset_id}/{metric_id}
```

### Query Parameters

| Parameter           | Type    | Default | Description              |
| ------------------- | ------- | ------- | ------------------------ |
| `include_snapshots` | boolean | true    | Include recent snapshots |
| `snapshot_limit`    | integer | 30      | Max snapshots to include |

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer aa_live_xxx" \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000/m_550e8400-e29b-41d4-a716-446655440001?include_snapshots=true&snapshot_limit=10"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()
  metric = client.metrics.get(
      "550e8400-e29b-41d4-a716-446655440000",
      "m_550e8400-e29b-41d4-a716-446655440001",
      include_snapshots=True,
      snapshot_limit=10,
  )
  print(f"Type: {metric.metric_type}")
  print(f"Table: {metric.table_path}")
  print(f"Active: {metric.is_active}")
  ```

  ```bash CLI theme={null}
  armor metrics get 550e8400-e29b-41d4-a716-446655440000 m_550e8400-e29b-41d4-a716-446655440001
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "id": "m_550e8400-e29b-41d4-a716-446655440001",
    "internal_id": 123,
    "asset_id": 456,
    "table_path": "snowflake.prod.warehouse.orders",
    "column_name": "customer_email",
    "metric_type": "null_percent",
    "capture_interval": "daily",
    "sensitivity": 3,
    "is_active": true,
    "created_at": "2026-01-01T10:00:00Z"
  }
}
```

## Create Metric

```
POST /api/v1/sdk/metrics/{asset_id}
```

<Note>
  Requires `read-write` or `admin` scope.
</Note>

### Request Body

| Field              | Type   | Required           | Description                                    |
| ------------------ | ------ | ------------------ | ---------------------------------------------- |
| `metric_type`      | string | Yes                | Metric type (see table above)                  |
| `table_path`       | string | Yes                | Full table path (catalog.schema.table)         |
| `column_name`      | string | For column metrics | Column name                                    |
| `capture_interval` | string | No                 | `hourly`, `daily`, `weekly` (default: `daily`) |
| `sensitivity`      | float  | No                 | Anomaly detection sensitivity (default: 1.0)   |
| `group_by_columns` | array  | No                 | Columns to group by                            |
| `percentile_value` | float  | No                 | Percentile value (for `percentile` type)       |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST -H "Authorization: Bearer aa_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "metric_type": "null_percent",
      "table_path": "snowflake.prod.warehouse.orders",
      "column_name": "customer_email",
      "capture_interval": "daily"
    }' \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()

  # Create a null percentage metric
  metric = client.metrics.create(
      "550e8400-e29b-41d4-a716-446655440000",
      metric_type="null_percent",
      table_path="snowflake.prod.warehouse.orders",
      column_name="customer_email",
      capture_interval="daily",
  )
  print(f"Created metric: {metric.id}")

  # Create a row count metric
  row_metric = client.metrics.create(
      "550e8400-e29b-41d4-a716-446655440000",
      metric_type="row_count",
      table_path="snowflake.prod.warehouse.orders",
  )
  ```

  ```bash CLI theme={null}
  armor metrics create 550e8400-e29b-41d4-a716-446655440000 \
    --type null_percent \
    --table snowflake.prod.warehouse.orders \
    --column customer_email
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "id": "m_550e8400-e29b-41d4-a716-446655440001",
    "internal_id": 123,
    "asset_id": 456,
    "table_path": "snowflake.prod.warehouse.orders",
    "column_name": "customer_email",
    "metric_type": "null_percent",
    "capture_interval": "daily",
    "sensitivity": 1.0,
    "is_active": true,
    "created_at": "2026-01-04T10:30:00Z"
  }
}
```

## Update Metric

```
PATCH /api/v1/sdk/metrics/{asset_id}/{metric_id}
```

<Note>
  Requires `read-write` or `admin` scope.
</Note>

### Request Body

| Field              | Type    | Description                   |
| ------------------ | ------- | ----------------------------- |
| `is_active`        | boolean | Whether metric is active      |
| `capture_interval` | string  | Capture interval              |
| `sensitivity`      | float   | Anomaly detection sensitivity |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH -H "Authorization: Bearer aa_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{"is_active": false}' \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000/m_550e8400-e29b-41d4-a716-446655440001"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()
  metric = client.metrics.update(
      "550e8400-e29b-41d4-a716-446655440000",
      "m_550e8400-e29b-41d4-a716-446655440001",
      is_active=False,
      sensitivity=2.0,
  )
  ```

  ```bash CLI theme={null}
  armor metrics update 550e8400-e29b-41d4-a716-446655440000 \
    m_550e8400-e29b-41d4-a716-446655440001 \
    --inactive
  ```
</CodeGroup>

## Delete Metric

```
DELETE /api/v1/sdk/metrics/{asset_id}/{metric_id}
```

<Note>
  Requires `read-write` or `admin` scope.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE -H "Authorization: Bearer aa_live_xxx" \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000/m_550e8400-e29b-41d4-a716-446655440001"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()
  client.metrics.delete(
      "550e8400-e29b-41d4-a716-446655440000",
      "m_550e8400-e29b-41d4-a716-446655440001",
  )
  ```

  ```bash CLI theme={null}
  armor metrics delete 550e8400-e29b-41d4-a716-446655440000 \
    m_550e8400-e29b-41d4-a716-446655440001 --yes
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "success": true,
    "message": "Metric deleted"
  }
}
```

## Trigger Metric Capture

```
POST /api/v1/sdk/metrics/{asset_id}/{metric_id}/capture
```

<Note>
  Requires `read-write` or `admin` scope.
</Note>

Triggers an immediate capture of the metric value.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST -H "Authorization: Bearer aa_live_xxx" \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000/m_550e8400-e29b-41d4-a716-446655440001/capture"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()
  result = client.metrics.capture(
      "550e8400-e29b-41d4-a716-446655440000",
      "m_550e8400-e29b-41d4-a716-446655440001",
  )
  print(f"Captured {result.get('snapshot_count', 0)} snapshots")
  ```

  ```bash CLI theme={null}
  armor metrics capture 550e8400-e29b-41d4-a716-446655440000 \
    m_550e8400-e29b-41d4-a716-446655440001
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "snapshot_count": 1,
    "snapshots": [
      {
        "id": 789,
        "value": 2.5,
        "captured_at": "2026-01-04T10:35:00Z",
        "is_anomaly": false,
        "status": "PASS"
      }
    ]
  }
}
```

## List Metric Snapshots

```
GET /api/v1/sdk/metrics/{asset_id}/{metric_id}/snapshots
```

### Query Parameters

| Parameter | Type    | Default | Description     |
| --------- | ------- | ------- | --------------- |
| `limit`   | integer | 100     | Max results     |
| `offset`  | integer | 0       | Results to skip |

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer aa_live_xxx" \
    "https://api.anomalyarmor.ai/api/v1/sdk/metrics/550e8400-e29b-41d4-a716-446655440000/m_550e8400-e29b-41d4-a716-446655440001/snapshots?limit=30"
  ```

  ```python Python SDK theme={null}
  from anomalyarmor import Client

  client = Client()
  snapshots = client.metrics.snapshots(
      "550e8400-e29b-41d4-a716-446655440000",
      "m_550e8400-e29b-41d4-a716-446655440001",
      limit=30,
  )
  for s in snapshots:
      anomaly = "*" if s.is_anomaly else ""
      print(f"{s.captured_at}: {s.value:.2f} {anomaly}")
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "items": [
      {
        "id": 789,
        "metric_definition_id": 123,
        "value": 2.5,
        "captured_at": "2026-01-04T10:35:00Z",
        "is_anomaly": false,
        "z_score": 0.3,
        "status": "PASS"
      },
      {
        "id": 788,
        "metric_definition_id": 123,
        "value": 15.2,
        "captured_at": "2026-01-03T10:35:00Z",
        "is_anomaly": true,
        "z_score": 4.2,
        "status": "FAIL"
      }
    ]
  },
  "pagination": {
    "total": 90,
    "limit": 30,
    "offset": 0,
    "has_more": true
  }
}
```

## Use Case: Monitor Row Count Trends

Track daily row counts to detect unexpected data volume changes:

```python theme={null}
from anomalyarmor import Client

client = Client()
asset_id = "550e8400-e29b-41d4-a716-446655440000"

# Create row count metric
metric = client.metrics.create(
    asset_id,
    metric_type="row_count",
    table_path="snowflake.prod.warehouse.orders",
    capture_interval="daily",
    sensitivity=2.0,  # Alert on 2+ standard deviations
)

# Trigger initial capture
result = client.metrics.capture(asset_id, metric.id)
print(f"Initial row count: {result['snapshots'][0]['value']}")

# Later: check for anomalies
snapshots = client.metrics.snapshots(asset_id, metric.id, limit=7)
anomalies = [s for s in snapshots if s.is_anomaly]
if anomalies:
    print(f"Found {len(anomalies)} anomalies in the last 7 captures")
```

## Error Responses

### Metric Not Found (404)

```json theme={null}
{
  "error": {
    "code": "METRIC_NOT_FOUND",
    "message": "Metric not found",
    "details": {"metric_id": "m_invalid-uuid"}
  }
}
```

### Validation Error (400)

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "column_name is required for null_percent metrics",
    "details": {"field": "column_name", "metric_type": "null_percent"}
  }
}
```

### Forbidden (403)

```json theme={null}
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Insufficient permissions. Required scope: read-write",
    "details": {"current_scope": "read-only", "required_scope": "read-write"}
  }
}
```

## Common Questions

### What's the difference between a metric and a validity rule?

Metrics track numeric values over time (row counts, null percentages, mean, percentile) and alert on statistical anomalies via the `sensitivity` parameter. Validity rules ([Validity API](/api/validity)) enforce deterministic pass/fail constraints like NOT NULL, REGEX, or RANGE. Use metrics to catch drift, validity to catch explicit contract violations.

### How does the sensitivity parameter affect anomaly detection?

Sensitivity is the z-score threshold for flagging a snapshot as anomalous, defaulting to 1.0. Raise it (e.g. `2.0` or `3.0`) to reduce false positives on noisy data, lower it to catch subtler shifts. Each captured snapshot returns `z_score` and `is_anomaly` so you can tune in production.

### Can I capture a metric on demand outside its scheduled interval?

Yes. `POST /api/v1/sdk/metrics/{asset_id}/{metric_id}/capture` (or `client.metrics.capture(...)`) triggers an immediate capture and returns the new snapshot with anomaly status. This is useful for backfilling after creating a metric and for post-deploy validation without waiting for the next scheduled run.

### Which metric types require a column\_name?

Column-level types (`null_percent`, `distinct_count`, `duplicate_count`, `min_value`, `max_value`, `mean`, `percentile`) require `column_name`. `row_count` operates on the whole table and ignores `column_name`. `percentile` additionally needs `percentile_value` in the request body.
