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

# Custom Metrics in Tyk Gateway

> Define custom metric instruments in Tyk Gateway with your own dimensions sourced from request headers, JWT claims, session data, endpoint paths, and more.

## Availability

| Component   | Version                                                                                  | Edition                |
| :---------- | :--------------------------------------------------------------------------------------- | :--------------------- |
| Tyk Gateway | Available since [v5.13.0](/developer-support/release-notes/gateway#5-13-0-release-notes) | Community & Enterprise |

## Custom Metrics

By default, Tyk Gateway exports [standard RED metrics](/api-management/metrics/default-metrics) with a fixed set of dimensions. Custom metrics let you define additional instruments with dimensions drawn from request headers, session data, JWT claims, and other context, enabling use cases such as:

* **Multi-tenant billing:** Count requests per customer ID from a header or JWT claim
* **Tier-based SLOs:** Track latency separately for premium vs. standard API tiers
* **Business KPIs:** Measure domain-specific signals (e.g., successful transactions per payment provider)

## The api\_metrics Array

Custom metrics are defined in the `api_metrics` array inside `opentelemetry.metrics`:

```json theme={null}
{
  "opentelemetry": {
    "metrics": {
      "enabled": true,
      "api_metrics": [
        { ... }
      ]
    }
  }
}
```

The behavior of this field depends on its value:

| Value                   | Behavior                                                                                                              |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Field omitted or `null` | [Default RED metrics](/api-management/metrics/default-metrics) are exported automatically.                            |
| Empty array `[]`        | API-level metrics are disabled entirely; no request metrics are exported.                                             |
| Populated array         | Only the instruments you define are exported. Default RED metrics are not exported unless you explicitly define them. |

<Tip>
  **Preserving default RED metrics**

  When you populate `api_metrics`, the built-in RED instruments are no longer exported automatically. Use the block below as a starting point, it re-creates all four default instruments, then append your custom instruments after them.

  ```json expandable theme={null}
  {
    "opentelemetry": {
      "metrics": {
        "enabled": true,
        "api_metrics": [
          {
            "name": "http.server.request.duration",
            "type": "histogram",
            "description": "End-to-end request latency",
            "histogram_source": "total",
            "dimensions": [
              { "source": "metadata", "key": "method",        "label": "http.request.method" },
              { "source": "metadata", "key": "response_code", "label": "http.response.status_code" },
              { "source": "metadata", "key": "api_id",        "label": "tyk.api.id" },
              { "source": "metadata", "key": "response_flag", "label": "tyk.response_flag" }
            ]
          },
          {
            "name": "tyk.gateway.request.duration",
            "type": "histogram",
            "description": "Gateway processing time",
            "histogram_source": "gateway",
            "dimensions": [
              { "source": "metadata", "key": "method",        "label": "http.request.method" },
              { "source": "metadata", "key": "api_id",        "label": "tyk.api.id" },
              { "source": "metadata", "key": "response_flag", "label": "tyk.response_flag" }
            ]
          },
          {
            "name": "tyk.upstream.request.duration",
            "type": "histogram",
            "description": "Upstream response time",
            "histogram_source": "upstream",
            "dimensions": [
              { "source": "metadata", "key": "method",        "label": "http.request.method" },
              { "source": "metadata", "key": "api_id",        "label": "tyk.api.id" },
              { "source": "metadata", "key": "response_flag", "label": "tyk.response_flag" }
            ]
          },
          {
            "name": "tyk.api.requests.total",
            "type": "counter",
            "description": "Request count with identity dimensions",
            "dimensions": [
              { "source": "metadata", "key": "method",        "label": "http.request.method" },
              { "source": "metadata", "key": "response_code", "label": "http.response.status_code" },
              { "source": "metadata", "key": "api_id",        "label": "tyk.api.id" }
            ]
          }
        ]
      }
    }
  }
  ```
</Tip>

## Instrument Types

Each entry in `api_metrics` defines one instrument:

| Type        | Description                                                                  |
| ----------- | ---------------------------------------------------------------------------- |
| `counter`   | A monotonically increasing count. Use for request counts, error counts, etc. |
| `histogram` | A distribution of values. Use for latency measurements.                      |

Histograms require a `histogram_source` field that selects which latency to measure:

| `histogram_source` | Measures                                                       |
| ------------------ | -------------------------------------------------------------- |
| `total`            | End-to-end request latency (client to Gateway to upstream)     |
| `gateway`          | Gateway processing time only (excludes upstream response time) |
| `upstream`         | Upstream service response time only                            |

## Dimension Sources

Dimensions let you slice your metrics by any signal available at request time. Tyk can source dimension values from six places; the sections below describe common use cases and which source and key to use for each.

| Source            | What It Provides                                                                          | Example Keys                                                                                                                                                                                                                                                                                  |
| ----------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `metadata`        | Request metadata always in scope                                                          | `method`, `response_code`, `api_id`, `api_name`, `org_id`, `response_flag`, `ip_address`, `api_version`, `host`, `scheme`, `listen_path`, `endpoint`. MCP APIs also expose `mcp_method`, `mcp_primitive_type`, `mcp_primitive_name`, `mcp_error_code`, see [MCP dimensions](#mcp-dimensions). |
| `session`         | Authenticated session fields                                                              | `api_key`, `oauth_id`, `alias`, `portal_app`, `portal_org`                                                                                                                                                                                                                                    |
| `header`          | Any HTTP request header                                                                   | `X-Customer-ID`, `X-Tenant-ID`, `Authorization`                                                                                                                                                                                                                                               |
| `context`         | Tyk [context variables](/api-management/traffic-transformation/request-context-variables) | `jwt_claims_tier`, `request_id`, `path_parts`                                                                                                                                                                                                                                                 |
| `response_header` | Any upstream response header                                                              | `X-Cache-Status`, `X-Backend-Version`                                                                                                                                                                                                                                                         |
| `config_data`     | API definition metadata from the `config_data` map                                        | Any key set on the API definition                                                                                                                                                                                                                                                             |

### Response status

Two `metadata` keys capture response status, and they answer different questions:

| Key             | Source     | What it captures                                                                                                                                                                                                                                                                                                                     |
| --------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `response_code` | `metadata` | The numeric HTTP status code returned to the client (`200`, `429`, `502`, etc.). Always populated. Bounded cardinality (\~100 values).                                                                                                                                                                                               |
| `response_flag` | `metadata` | A 3-letter error classification code set by the gateway, e.g. `URS` (upstream returned 5XX), `AKI` (API key invalid), `CBO` (circuit breaker open). Falls back to the HTTP status code string (e.g. `"200"`) when no error classification applies. See the [full list of response flags](/api-management/logs#error-classification). |

Use `response_code` to break down traffic by HTTP status. Use `response_flag` to understand Gateway-level error causes.

The `status_codes` filter in the `filters` block decides *whether* a request is recorded at all. The `response_code` dimension attaches the actual code as a label on the recorded data points. A common pattern is to combine both: filter to error responses, then break them down by exact code:

```json theme={null}
{
  "name": "tyk.api.errors.by_status",
  "type": "counter",
  "dimensions": [
    { "source": "metadata", "key": "response_code", "label": "http_status_code" },
    { "source": "metadata", "key": "api_id",        "label": "api_id" }
  ],
  "filters": { "status_codes": ["4xx", "5xx"] }
}
```

### Request path

Four options are available, at different cardinality levels:

|                   | Source     | Key           | Cardinality                      | Notes                                                                                                                                                                                                                                                               |
| ----------------- | ---------- | ------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| API listen path   | `metadata` | `listen_path` | Bounded (one value per API)      | The configured listen path (e.g. `/api/v1/`). Safe to use in any metric.                                                                                                                                                                                            |
| Endpoint template | `metadata` | `endpoint`    | Bounded (one per track endpoint) | The matched path template (e.g. `/user/{id}`). Requires `track_endpoints` configured on the API. Empty string for unmatched requests.                                                                                                                               |
| Raw request path  | `context`  | `path`        | ⚠️ Potentially unbounded         | The URL path as requested by the client (e.g. `/api/v1/users/12345`). Avoid unless paths have known low cardinality.                                                                                                                                                |
| Path segments     | `context`  | `path_parts`  | ⚠️ Potentially unbounded         | The path split by `/` into a list of segments (e.g. `/api/v1/users/123` → `["api", "v1", "users", "123"]`). Access a specific segment with `{{ index ._tyk_context.path_parts N }}`. Carries the same cardinality risk as `path` if any segment varies per request. |

Prefer `listen_path` or `endpoint` (with `track_endpoints`) over raw `path` or `path_parts` in most cases.

### Client and auth identity

Several sources expose client identity, with different cardinality trade-offs:

* **Session fields** (`session` source): `alias` (the human-readable key alias), `portal_app` (Developer Portal app ID), and `portal_org` (Developer Portal org ID) are typically bounded and safe to use. `api_key` and `oauth_id` are ⚠️ high cardinality (one value per key/client); only use these with [cardinality control](/api-management/logs-metrics#cardinality-control) enabled.
* **JWT claims** (`context` source): any claim is available as `jwt_claims_<name>` (e.g. `jwt_claims_tier`, `jwt_claims_tenant_id`). See [Request context variables](#request-context-variables) for setup requirements and cardinality notes.
* **Request headers** (`header` source): use when client identity is passed as a header (e.g. `X-Tenant-ID`). Cardinality depends on what the header contains.

### Custom API metadata

If you want to attach static, API-level metadata (such as team ownership, service tier, cost centre, or criticality) to your metrics without touching request headers or tokens, use the `config_data` source.

The [config\_data](/api-management/gateway-config-tyk-oas#pluginconfigdata) field in the API definition is a free-form key-value map you can populate per API:

```json theme={null}
{
  "config_data": {
    "environment": "staging",
    "team": "platform",
    "criticality": "high"
  }
}
```

A dimension like `{"source": "config_data", "key": "team"}` will carry `team="platform"` on every metric recorded for that API; no request modification required.

**Fallback behaviour:** If `config_data_disabled` is `true` on the API, or the key is absent, the dimension falls back to the `default` value on the dimension definition. If no default is set, an empty string is used. All values are treated as strings.

### Request context variables

The `context` source reads from Tyk's [request context variables](/api-management/traffic-transformation/request-context-variables), a set of values extracted from the incoming request and enriched during middleware processing. The `context` source covers two kinds of variables: those populated automatically by Tyk, and those written by custom plugins.

<Note>
  Context variables must be enabled on the API definition (`enable_context_vars: true`) for the `context` source to work. Without this setting, all `context` dimensions will be empty.
</Note>

#### Default context variables (no plugin required)

When context variables are enabled, Tyk automatically populates the following variables for every request:

| Key                 | Description                                                                                                                                                                                  | Cardinality                                                                             |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `request_id`        | A generated correlation ID for the request.                                                                                                                                                  | ⚠️ Unbounded, unique per request. Not suitable as a metric dimension.                   |
| `path`              | The raw request path (e.g. `/api/v1/users/123`).                                                                                                                                             | ⚠️ Potentially unbounded. See [Request path](#request-path).                            |
| `path_parts`        | The request path split by `/` into a list of segments. E.g. `/api/v1/users/123` → `["api", "v1", "users", "123"]`. Access individual segments with `{{ index ._tyk_context.path_parts N }}`. | ⚠️ Potentially unbounded.                                                               |
| `remote_addr`       | The connecting client IP address.                                                                                                                                                            | ⚠️ High cardinality.                                                                    |
| `jwt_claims_<name>` | Individual JWT claims, e.g. `jwt_claims_tier`, `jwt_claims_tenant_id`. Available when JWT auth is in use.                                                                                    | Bounded if the claim takes a fixed set of values (e.g. subscription tier). Safe to use. |
| `cookies_<name>`    | Cookie values by name (hyphens replaced with underscores).                                                                                                                                   | Depends on cookie values.                                                               |
| `headers_<name>`    | Request header values by name (capitalised, hyphens replaced with underscores, e.g. `headers_User_Agent`).                                                                                   | Depends on header values.                                                               |
| `token`             | The raw inbound bearer token.                                                                                                                                                                | ⚠️ Extremely high cardinality. Do not use as a dimension.                               |

Usually the most useful defaults for metrics are **JWT claims**: they let you segment traffic by tenant, subscription tier, or any other bounded claim without requiring a plugin:

```json theme={null}
{ "source": "context", "key": "jwt_claims_tier", "label": "tier", "default": "standard" }
```

#### Custom context variables (via plugin)

Go plugins and [JQ request transforms](/api-management/traffic-transformation/jq-transforms) can write arbitrary variables into the request context, which are then accessible as `context` source dimensions. This is useful for computed signals, such as enriched tenant IDs, feature flags, routing decisions, or values decoded from opaque tokens.

**Go plugin:**

```go theme={null}
import "github.com/TykTechnologies/tyk/ctx"

func MyPlugin(w http.ResponseWriter, r *http.Request) {
    if ctxData, ok := r.Context().Value(ctx.ContextData).(map[string]interface{}); ok {
        ctxData["my_custom_var"] = "value"
    }
}
```

**JQ transform**: return a `tyk_context` object alongside the transformed body:

```json theme={null}
{
  "body": "<transformed-body>",
  "tyk_context": { "my_custom_var": "value" }
}
```

The variable is then available as a dimension:

```json theme={null}
{ "source": "context", "key": "my_custom_var", "label": "my_variable" }
```

<Note>
  Python, gRPC (coprocess), and JavaScript (JSVM) plugins cannot write to request context variables. To pass data from these plugins into metrics dimensions, use session metadata (`session` source, via `session.meta_data`) or inject a custom HTTP request header (`header` source) instead.
</Note>

### MCP dimensions

When a request is handled by an MCP API, Tyk populates four additional `metadata` keys derived from the JSON-RPC payload. These are available as dimensions in any custom metric instrument.

| Key                  | Description                                             | Example Values                                              | Cardinality        |
| -------------------- | ------------------------------------------------------- | ----------------------------------------------------------- | ------------------ |
| `mcp_method`         | JSON-RPC method invoked                                 | `tools/call`, `initialize`, `resources/read`, `prompts/get` | Bounded            |
| `mcp_primitive_type` | MCP primitive category                                  | `tool`, `resource`, `prompt`                                | Bounded (3 values) |
| `mcp_primitive_name` | Name of the specific tool, resource, or prompt          | `get_current_weather`, `search_docs`                        | Bounded per API    |
| `mcp_error_code`     | JSON-RPC error code on failure; empty string on success | `-32001`, `-32002`, \`\`                                    | Bounded            |

<Note>
  These keys are only populated for MCP APIs. For non-MCP requests all four keys resolve to an empty string; use the `default` field on the dimension definition to set a fallback value. There is no performance overhead on non-MCP metric configurations.
</Note>

**Example: count tool calls by tool name:**

```json theme={null}
{
  "name": "tyk.mcp.tool_calls.total",
  "type": "counter",
  "description": "MCP tool invocations by tool name and API",
  "dimensions": [
    { "source": "metadata", "key": "mcp_primitive_name", "label": "tool_name", "default": "" },
    { "source": "metadata", "key": "mcp_primitive_type", "label": "primitive_type", "default": "" },
    { "source": "metadata", "key": "api_id",             "label": "api_id" }
  ],
  "filters": { "methods": ["POST"] }
}
```

For a complete set of MCP monitoring use cases, see [MCP Observability](/ai-management/mcp-gateway/mcp-metrics).

### Other request metadata

Remaining `metadata` keys available for general request context:

| Key           | Description                       | Cardinality         |
| ------------- | --------------------------------- | ------------------- |
| `method`      | HTTP method (`GET`, `POST`, etc.) | Bounded             |
| `api_id`      | Tyk API ID                        | Bounded             |
| `api_name`    | API display name                  | Bounded             |
| `org_id`      | Organisation ID                   | Bounded             |
| `api_version` | API version name                  | Bounded             |
| `host`        | Request host                      | Typically bounded   |
| `scheme`      | URL scheme (`http`, `https`)      | Bounded             |
| `ip_address`  | Client IP address                 | ⚠️ High cardinality |

Each dimension definition has four fields:

| Field     | Required | Description                                                |
| --------- | -------- | ---------------------------------------------------------- |
| `source`  | Yes      | One of the source names above.                             |
| `key`     | Yes      | The specific field or header name to read from.            |
| `label`   | Yes      | The dimension name as it appears in your metrics backend.  |
| `default` | No       | Fallback value when the key is not present on the request. |

## Filters

Each instrument can restrict which requests it records using the `filters` block. All filter conditions use AND logic (a request must match all specified filters):

| Filter Field   | Type         | Description                                                                                                                    |
| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `api_ids`      | string array | Only record requests to these API IDs.                                                                                         |
| `methods`      | string array | Only record requests with these HTTP methods (e.g., `["GET", "POST"]`).                                                        |
| `status_codes` | string array | Only record responses with these status codes. Supports exact values (`"200"`) and class patterns (`"2xx"`, `"4xx"`, `"5xx"`). |

## Example Configuration

The following example defines two custom instruments:

1. A counter that tracks requests by customer ID and subscription tier
2. A histogram that tracks latency for premium-tier requests only

```json expandable theme={null}
{
  "opentelemetry": {
    "metrics": {
      "enabled": true,
      "api_metrics": [
        {
          "name": "tyk.requests.by_customer",
          "type": "counter",
          "description": "Request count by customer ID and subscription tier",
          "dimensions": [
            {
              "source": "header",
              "key": "X-Customer-ID",
              "label": "customer_id",
              "default": "unknown"
            },
            {
              "source": "context",
              "key": "jwt_claims_tier",
              "label": "tier",
              "default": "standard"
            },
            {
              "source": "metadata",
              "key": "api_id",
              "label": "api_id"
            }
          ],
          "filters": {
            "api_ids": ["payments-api", "orders-api"]
          }
        },
        {
          "name": "tyk.latency.premium_tier",
          "type": "histogram",
          "description": "End-to-end latency for premium tier requests",
          "histogram_source": "total",
          "histogram_buckets": [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
          "dimensions": [
            {
              "source": "context",
              "key": "jwt_claims_tier",
              "label": "tier"
            },
            {
              "source": "metadata",
              "key": "api_id",
              "label": "api_id"
            }
          ],
          "filters": {
            "methods": ["POST", "PUT", "PATCH"],
            "status_codes": ["2xx"]
          }
        }
      ]
    }
  }
}
```

## Full Field Reference

| Field               | Type        | Required        | Description                                                                                        |
| ------------------- | ----------- | --------------- | -------------------------------------------------------------------------------------------------- |
| `name`              | string      | Yes             | Metric instrument name. Use lowercase with dots or underscores (e.g., `tyk.requests.by_customer`). |
| `type`              | string      | Yes             | `"counter"` or `"histogram"`.                                                                      |
| `description`       | string      | No              | Human-readable description included in metric metadata.                                            |
| `dimensions`        | array       | Yes             | List of dimension definitions. See [Dimension Sources](#dimension-sources).                        |
| `filters`           | object      | No              | Restricts which requests are recorded. See [Filters](#filters).                                    |
| `histogram_source`  | string      | Histograms only | `"total"`, `"gateway"`, or `"upstream"`.                                                           |
| `histogram_buckets` | float array | No              | Custom histogram bucket boundaries in seconds. If omitted, OTel default buckets are used.          |

## Cardinality Considerations

Custom dimensions can significantly increase cardinality. Before adding a dimension, estimate the number of unique values it can take:

* `api_id`: bounded (tens or hundreds of APIs)
* `method`: bounded (fewer than 10 values)
* `customer_id` from a header: potentially unbounded if unique per user

The `cardinality_limit` setting (default: 2,000 unique combinations per instrument) protects against unbounded cardinality. When the limit is reached, new combinations are tracked in an overflow bucket. See [Cardinality Control](/api-management/logs-metrics#cardinality-control) for details.

<Note>
  On Tyk Cloud, custom metric configuration is not currently self-service. Contact Tyk support to request custom dimensions for your Cloud deployment.
</Note>
