Getting startedGuidesReferenceChangelog
Apoxy:// Docs / Guides / Query gateway metrics

Query gateway metrics

Read current totals for a Gateway, chart requests over time broken down by route, and save your own metric recipe.

This guide reads traffic metrics for a Gateway three ways: a snapshot of current totals, a time series broken down by route, and a metric recipe you define yourself.

Prerequisites

  • An Apoxy project with a Gateway serving traffic.
  • An API key from the Apoxy console (Account Settings > API Keys).
  • Your project ID, shown in the Apoxy console.

Set both in your shell so the examples below stay short:

$terminalSH
export APOXY_API_KEY="<api-key>" export APOXY_API="https://<project-id>.api.apoxy.dev"

See what you can query

The catalog lists every metric recipe available in your project:

$terminalSH
curl -s -H "X-Apoxy-API-Key: $APOXY_API_KEY" \ "$APOXY_API/apis/metrics.apoxy.dev/v1alpha1/metrics"

You should see the built-in recipes, including http.requests, http.latency, http.bytes, and http.errors. Each entry carries its measures under status.measures and the dimensions it can be grouped by under status.keys.

Read a Gateway snapshot

A snapshot returns every applicable recipe for one object over one window in a single call. Ask for the last hour of a Gateway named prod, with a per-route breakdown:

$terminalSH
curl -s -H "X-Apoxy-API-Key: $APOXY_API_KEY" \ "$APOXY_API/apis/gateway.apoxy.dev/v1/gateways/prod/metrics?window=1h&include=routes&top=10"

You should see totals for the Gateway, then a listeners array, each listener carrying its own totals and the top routes under it:

$terminalJSON
{ "kind": "GatewayMetrics", "metadata": { "name": "prod" }, "window": "1h0m0s", "since": "2026-08-20T20:00:00Z", "until": "2026-08-20T21:00:00Z", "dataUpTo": "2026-08-20T21:00:00Z", "metrics": { "http.requests": { "total": 76821, "status_2xx": 74604, "status_4xx": 2061, "status_5xx": 156 }, "http.latency": { "p50": 38, "p95": 190, "p99": 412 } }, "units": { "p50": "ms", "p95": "ms", "p99": "ms" }, "listeners": [ { "name": "https", "metrics": { "http.requests": { "total": 76705, "status_5xx": 156 } }, "truncated": true, "totalCount": 37, "routes": [ { "kind": "HTTPRoute", "name": "api", "rule": 0, "metrics": { "http.requests": { "total": 62958, "status_5xx": 149 } } } ] } ] }

truncated: true with totalCount: 37 means the listener has 37 routes with traffic and you asked for the top 10. Raise top up to 50, or use the series query below for complete coverage.

Omit include=routes and you get the Gateway and listener totals only, which is the cheaper read for a status tile.

Chart requests over time

The series endpoint returns one line per group. Ask for six hours of requests on the same Gateway, in five-minute buckets, split by route and ranked by server errors:

$terminalSH
curl -s -H "X-Apoxy-API-Key: $APOXY_API_KEY" \ "$APOXY_API/apis/metrics.apoxy.dev/v1alpha1/metrics/http.requests/series?scopeKind=Gateway&scopeName=prod&groupBy=route_name&since=-6h&step=5m&top=10&orderBy=status_5xx"

You should see a series array, one entry per route, each with a points array:

$terminalJSON
{ "kind": "MetricSeriesSet", "metric": "http.requests", "scopeKind": "Gateway", "scopeName": "prod", "since": "2026-08-20T15:00:00Z", "until": "2026-08-20T21:00:00Z", "step": "5m0s", "dataUpTo": "2026-08-20T21:00:00Z", "truncated": false, "totalCount": 7, "series": [ { "labels": { "route_name": "api" }, "points": [ { "timestamp": "2026-08-20T15:00:00Z", "values": { "total": 5210, "status_4xx": 140, "status_5xx": 12 } }, { "timestamp": "2026-08-20T15:05:00Z", "values": { "total": 5188, "status_4xx": 131, "status_5xx": 9 } } ] } ] }

Buckets with no traffic are omitted rather than returned as zero. Anything after dataUpTo is still filling, so plot up to that timestamp to avoid a trailing dip that corrects itself a minute later.

Swap groupBy=route_name for status_class, method, listener, or backend_name to slice the same recipe a different way. Swap http.requests for http.latency to chart p50, p95, and p99 instead.

Get one row per Gateway

Set step equal to the window and you get a single bucket, which turns the series endpoint into a fleet listing. Group by gateway at project scope for one row per Gateway:

$terminalSH
curl -s -H "X-Apoxy-API-Key: $APOXY_API_KEY" \ "$APOXY_API/apis/metrics.apoxy.dev/v1alpha1/metrics/http.requests/series?scopeKind=Project&groupBy=gateway&since=-1h&step=1h"

Each series comes back with exactly one point. This is the read behind a list page, and it replaces polling every object's snapshot separately.

Save your own metric

The built-in recipes cover request counts, errors, latency, and bytes. For anything else, save a recipe and query it through the same endpoint. This one counts server errors on a single path prefix:

$metric.yamlYAML
apiVersion: metrics.apoxy.dev/v1alpha1 kind: Metric metadata: name: v2.errors spec: source: otel_logs type: counter description: Server errors on the /v2 path prefix. prql: | filter (url.path | text.starts_with "/v2/") filter http.response.status_code >= 500 aggregate { n = count this }

Apply it:

$terminalSH
apoxy apply -f metric.yaml

The fragment is aggregate-only: filter and derive steps followed by one aggregate. Leave out from, group, and time_bucket entirely. The source comes from spec.source, the time bucket from the step parameter, and the grouping from groupBy, so the recipe stays reusable at any resolution.

The server compiles the fragment as it stores it. A fragment that does not compile is rejected on the spot with the compiler's message, so you find out at apply time rather than at read time.

Query it exactly like a built-in:

$terminalSH
curl -s -H "X-Apoxy-API-Key: $APOXY_API_KEY" \ "$APOXY_API/apis/metrics.apoxy.dev/v1alpha1/metrics/v2.errors/series?scopeKind=Gateway&scopeName=prod&since=-1h&step=1m&groupBy=envoy.response_flags"
Reserved names

The prefixes http., log., upstream., and tls. belong to the built-in recipes, and the name series is taken by the subresource. Writing to any of them returns 403. Pick a prefix of your own, as v2.errors does above.

Verify

Confirm the recipe was stored and compiled:

$terminalSH
curl -s -H "X-Apoxy-API-Key: $APOXY_API_KEY" \ "$APOXY_API/apis/metrics.apoxy.dev/v1alpha1/metrics/v2.errors"

You should see a Compiled condition with status True, along with a status.measures entry for n and the list of keys you can group it by.

Common errors

  • 400 with a list of valid keys - the groupBy field is not one of the recipe's status.keys. Read the recipe to see what it supports.
  • 400 naming buckets, series, and measures - the request asks for more than 20000 points. Widen step, lower top, or restrict recipes with metric=.
  • 403 on apply - the recipe name uses a reserved prefix.
  • 404 - the Gateway, route, or other owner object in the path does not exist.
  • 429 - too many concurrent queries. Retry after the interval in the Retry-After header.

Where to next