> ## 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.