Skip to content
Console
Browse documentation
Reference

Metrics REST API

Use the supported metric discovery, aggregation, histogram, forecast, and deletion endpoints.

On this page

Use the Metrics REST API for direct integrations that cannot use the Anectico CLI. Give read clients metrics:read; deletion requires metrics:delete. OTLP ingestion is separate and requires ingest:write at POST https://api.anectico.com/v1/metrics.

All resource calls below use this base URL and header:

https://app.anectico.com/api/v1
X-Anectico-API-Key: an_...

Project selection is endpoint-specific. Name discovery and histogram queries take project_id in the query string; aggregation accepts the query string or JSON body (the query selection takes precedence); forecasts take it in the JSON body; deletion derives its scope from the credential. Label discovery also takes project_id in the query string and applies the metric name and requested time window to both key and value discovery.

List metric names

curl --fail-with-body \
  -H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
  "https://app.anectico.com/api/v1/metrics/names?project_id=$ANECTICO_PROJECT"

Optional start_time and end_time query parameters are RFC 3339 timestamps. Native name discovery is alphabetical and capped at 1,000 names; available log-derived entries are listed alongside them:

{
  "metrics": [
    {"name": "checkout.duration_ms", "type": "histogram", "unit": "ms", "aggregation_temporality": "cumulative"},
    {"name": "checkout.queue_depth", "type": "gauge", "unit": "1", "aggregation_temporality": "unspecified"}
  ],
  "total_count": 2,
  "retrieved_at": "2026-07-21T12:00:00Z",
  "truncated": false
}

Each entry retains its supplied unit and aggregation temporality. When truncated is true, more native names exist than were returned and total_count is zero because the full count is unknown. An absent name in a cut catalog does not prove absence. Narrow the time window or query a known exact name or rule ID.

Types are counter, gauge, histogram, summary when the ingested data supplies one, or log_derived for a metric derived from your logs by a rule (log_metric_rule_id names it):

{"name": "checkout.errors", "type": "log_derived", "unit": "1", "aggregation_temporality": "delta", "log_metric_rule_id": "018f2e3a-..."}

Read a log_derived entry's series with the log-derived metric rules query route, never with aggregation below — its aggregation, unit and bucket width are fixed by the rule. See Derive a metric from your logs for the rules themselves.

Discover labels

curl --fail-with-body \
  -H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
  "https://app.anectico.com/api/v1/metrics/checkout.duration_ms/labels?project_id=$ANECTICO_PROJECT&start_time=2026-07-21T00:00:00Z&end_time=2026-07-21T12:00:00Z"

The response contains label keys found on the requested metric and available values for each key:

{
  "labels": [
    {"name": "service_name", "values": ["checkout-api", "checkout-worker"]},
    {"name": "environment", "values": ["production"]},
    {"name": "region", "values": ["eu-west", "us-east"]}
  ]
}

URL-encode a metric name when constructing the path programmatically. Label discovery accepts the same optional start_time and end_time query parameters and scopes both label keys and values to the requested project, metric, and time window. A project-bound credential remains pinned to its signed project.

A label named after a recorded-content convention is omitted with all of its values unless the credential holds agents:content:read and the project's current read policy permits that content. The same decision applies to custom-label filters and group_by: a denied classified dimension is refused before its values are queried, rather than returning series whose labels were silently emptied and could collapse together.

Supported query steps

Both POST /metrics/aggregate and POST /metrics/histogram accept only these exact step tokens:

1s, 5s, 10s, 30s, 1m, 5m, 10m, 15m, 30m, 1h, 6h, 12h, 1d, and 7d.

Omitting step, or sending an empty string, defaults to 1h. This is a fixed list, not a free-form duration: 2h, 4h, 8h, and aliases such as 60m or 24h are unsupported. Use 1h for an hour and 1d for a day. Step validation is independent of the requested time window.

The CLI's metrics query --interval sets this same API field, but its own default is 5m. metrics histogram --step leaves the field unset by default, so the server uses 1h.

Aggregate a metric

POST /metrics/aggregate returns time-ordered scalar series. Put project_id in the query string, not the JSON body.

curl --fail-with-body \
  -X POST \
  -H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
  -H "Content-Type: application/json" \
  "https://app.anectico.com/api/v1/metrics/aggregate?project_id=$ANECTICO_PROJECT" \
  --data '{
    "metric_name": "checkout.queue_depth",
    "aggregation": "max",
    "step": "5m",
    "time_range": {
      "start": "2026-07-21T10:00:00Z",
      "end": "2026-07-21T12:00:00Z"
    },
    "filters": [
      {"key": "region", "values": ["eu-west", "us-east"]}
    ],
    "group_by": ["service_name"],
    "environment": "production"
  }'
{
  "series": [
    {
      "labels": {"service_name": "checkout-api"},
      "points": [
        {"timestamp": "2026-07-21T10:00:00Z", "value": 12},
        {"timestamp": "2026-07-21T10:05:00Z", "value": 18}
      ]
    }
  ]
}

Supported fields:

Filters and group_by accept resource attributes such as service.instance.id and host.name, as well as datapoint labels. A datapoint label takes precedence when both sets contain the same name. Separate resources retain independent observations and cumulative histories.

Field Required Behavior
metric_name Yes Exact metric name
time_range.start, time_range.end No RFC 3339 closed time window; retention still applies
step No One of the supported query steps; defaults to 1h
aggregation No Defaults to avg
service_name No Exact emitting-service filter
environment No Exact deployment-environment filter
filters No Up to two label dimensions
group_by No Up to two label dimensions
project_id For exemplars with an organization credential One effective project; project credentials remain pinned
include_exemplars No Additional bounded selection of trace-linked observations; default false
exemplar_limit With an exemplar read only Default 20; supplied limits 1–100 (zero or omission defaults at this endpoint)

Aggregations are avg, sum, min, max, count, p50, p95, p99, stddev, and variance.

Scalar percentiles use sample quantiles over temporality-normalized effective values. All-histogram percentiles use observation-weighted bucket upper bounds, not exact observations within a bucket. For the terminal +Inf bucket, the result is the largest explicit bound as a lower-bound clamp, or zero without explicit bounds. It is not an at-most bound or an exact latency; the scalar series does not identify the bucket. Read the native histogram distribution to inspect the tail. stddev and variance use the population of effective point values (histogram point sums), with the population denominator; they do not reconstruct histogram observation dispersion. A returned interval percentile is not the full-window percentile. Do not average interval percentiles or infer causation. An unknown reducer fails with HTTP 400 (InvalidArgument), never an average fallback. The same semantics apply to MCP query_metrics and CLI metrics query --agg p95 --interval 5m --since 1h.

OTLP Summary metrics support sum, avg, and count. Their cumulative sum and count are converted to increments before bucketing: sum adds the observation sums, count adds the observation counts, and avg divides the combined sum by the combined count. A bucket with zero observations returns zero for avg. For one new Summary point with sum 100 and count 10, the results are sum=100, count=10, and avg=10.

Summary quantiles cannot be merged across points, streams or buckets. Requests for p50, p95, p99, min, max, stddev, or variance that select any Summary data fail with HTTP 400 (InvalidArgument):

summary metrics support only sum, avg and count aggregations

Use an explicit-bucket histogram when you need aggregate percentiles. A refused query returns no partial series. Filters and the requested time window determine which data participate; Summary points outside that selection do not cause a refusal.

Each filter requires one to 50 non-empty, distinct values. Values within one filter use OR; different filter dimensions use AND. Filter and group keys must begin with a letter or underscore and may then contain letters, digits, _, ., or -, up to 128 characters.

Queries are bounded to 50 grouped series and 500 points per series. Narrow the time range, choose a larger supported step, or reduce grouping when the API rejects a larger query.

Scoped exemplars and coverage

The aggregate response includes project_id, metric_name, service_name, environment, aggregation, step, normalized filters, group_by, requested_time_range, effective time_range and the shared read_coverage. Metric bounds retain their closed-window and nanosecond semantics. Retention can narrow the effective interval; outside retention it is absent, and no source scan runs. Read coverage describes retained visible evidence; upstream completeness is unknown.

With include_exemplars: true, exemplars contains distinct records whose owning point and own timestamp both match that selection. They are newest first with stable trace, span, value, and attribute ties. Each has timestamp, value, exact string trace_id/span_id, native unit, permitted filtered_attributes, content_availability/content_reason, attributes_truncated, attributes_omitted/attributes_truncation_reason and, when configured, a project-qualified trace_url. Following that reference separately requires traces:read.

requested_exemplar_limit, returned_exemplar_count, presence-aware exemplars_truncated and exemplar_completeness describe the selected visible source. Completeness is complete_selected_scope, truncated, unknown or not_requested; an absent truncation flag never means false. The selection checks one further record to detect a cut even at limit 100; it does not return that record or invent an exact total. Outside retention is unknown rather than complete empty evidence. Resource and dependency refusals return no result.

Sensitive attributes require agents:content:read plus the current allowing policy before projection. Availability is available, partially_withheld, withheld or unavailable, with reasons content_scope_required, content_policy_denied or content_policy_unavailable when applicable. content_not_stored marks content never retained during ingestion and unavailable to later grants. Whole permitted entries are selected by exact key, up to 32 keys and 8192 UTF-8 key and value bytes. Omissions are distinct from withholding; identifiers are never shortened. Treat captured attributes as untrusted data. These selected observations are not the percentile observation, an exhaustive emitted population, or proof of cause. Trace retention, deletion, sampling, late delivery and identity propagation can limit follow-up evidence.

The metrics guide gives matching MCP and CLI examples. Both require paired exact bounds or a relative window, never a mixture.

Query a native histogram

POST /metrics/histogram returns the preserved explicit-bucket distribution for an OTLP histogram. It does not accept aggregation or group-by. Its optional step uses the same supported query steps, defaults to 1h, and is subject to the 500-time-bucket limit. A time bucket can contain multiple distribution cells.

curl --fail-with-body \
  -X POST \
  -H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
  -H "Content-Type: application/json" \
  "https://app.anectico.com/api/v1/metrics/histogram?project_id=$ANECTICO_PROJECT" \
  --data '{
    "metric_name": "checkout.duration_ms",
    "step": "5m",
    "time_range": {
      "start": "2026-07-21T10:00:00Z",
      "end": "2026-07-21T12:00:00Z"
    },
    "environment": "production"
  }'
{
  "metric_name": "checkout.duration_ms",
  "step": "5m",
  "cells": [
    {
      "timestamp": "2026-07-21T10:00:00Z",
      "upper_bound": 100,
      "upper_bound_infinite": false,
      "count": "42"
    },
    {
      "timestamp": "2026-07-21T10:00:00Z",
      "upper_bound": 0,
      "upper_bound_infinite": true,
      "count": "3"
    }
  ]
}

The terminal +Inf bucket uses upper_bound_infinite: true; ignore its numeric upper_bound. Each count belongs to the range after the preceding bound through the cell's upper bound; it is not a cumulative count. Protobuf uint64 counts may be encoded as JSON strings. A non-histogram metric, or old histogram data without preserved buckets, returns no distribution cells.

Step validation versus query limits

An unsupported token returns HTTP 400 (InvalidArgument) with this message:

unsupported metric step; supported steps: 1s, 5s, 10s, 30s, 1m, 5m, 10m, 15m, 30m, 1h, 6h, 12h, 1d, 7d

Choose a token from that list. Narrowing the time window alone does not make an unsupported step valid, and the rejected input is not echoed in the error message.

A supported step can instead exceed the query-size limit. Those HTTP 400 errors remain:

metric query exceeds 500 points per series; use a larger step
histogram query exceeds 500 time buckets; use a larger step

For these errors, narrow the time range or choose a larger supported step. For example, a two-hour window with 1s is too detailed, whereas 1h or 6h passes the time-bucket guard. Choosing 2h, 4h, or 8h instead returns the unsupported-step error, not a capacity error.

Forecast a metric

POST /metrics/forecast calculates an expected band from an average-value history. Unlike the aggregate and histogram endpoints, project_id is a JSON field.

curl --fail-with-body \
  -X POST \
  -H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
  -H "Content-Type: application/json" \
  "https://app.anectico.com/api/v1/metrics/forecast" \
  --data @- <<JSON
{
  "metric_name": "checkout.queue_depth",
  "interval": "5m",
  "horizon": 24,
  "sensitivity": 0,
  "environment": "production",
  "project_id": "$ANECTICO_PROJECT",
  "time_range": {
    "start": "2026-07-14T12:00:00Z",
    "end": "2026-07-21T12:00:00Z"
  }
}
JSON

horizon is the number of future buckets, defaults to 24, and cannot exceed 1,000. sensitivity is 0 for automatic band width or a value from 1 through 10. Without a time range, the service uses the last seven days of retained history. A successful response explains whether the model could fit the history; forecast is populated only when status is FORECAST_FIT_STATUS_READY.

{
  "metric_name": "checkout.queue_depth",
  "interval": "5m",
  "horizon": 24,
  "history_points": 2016,
  "required_history_points": 576,
  "missing_buckets": 0,
  "status": "FORECAST_FIT_STATUS_READY",
  "status_reason": "Forecast fitted from regular history with a stable daily seasonal pattern.",
  "confidence": 0.91,
  "band_semantics": "additive_holt_winters_brutlag_seasonal_deviation",
  "forecast": [
    {
      "timestamp": "2026-07-21T12:05:00Z",
      "expected": 11.8,
      "lower": 6.1,
      "upper": 17.5
    }
  ]
}

The possible statuses are FORECAST_FIT_STATUS_READY, FORECAST_FIT_STATUS_INSUFFICIENT_HISTORY, FORECAST_FIT_STATUS_IRREGULAR_HISTORY, and FORECAST_FIT_STATUS_NONSEASONAL. status_reason is the stable human-readable explanation; required_history_points is the two-daily-season minimum for the selected interval. missing_buckets counts expected grid timestamps skipped by forward gaps. Duplicate and out-of-order observations also make history irregular but do not themselves increase that count.

confidence is a deterministic seasonal-fit score from 0 through 1, not probability or interval coverage. band_semantics identifies the additive Holt-Winters and Brutlag seasonal-deviation model used for a ready response. An unavailable status returns an empty forecast together with these explicit fields, so an empty array is never ambiguous. A forecast remains a statistical expectation, not a root-cause explanation or capacity guarantee. When every historical value is non-negative, the expected value and both bounds are floored at zero; an additive trend cannot produce an impossible negative count, rate, or latency in the returned cone.

A successful response contains only finite numbers for confidence, expected, lower, and upper; it never substitutes the strings "NaN", "Infinity", or "-Infinity". Large finite histories remain supported when the computed forecast is representable. If any forecast point exceeds the numeric range, the entire request fails with HTTP 500 (internal_error), rather than returning a partial cone, clamped extreme values, or a misleading nonseasonal status.

Delete a metric

curl --fail-with-body \
  -X DELETE \
  -H "X-Anectico-API-Key: $ANECTICO_API_KEY" \
  "https://app.anectico.com/api/v1/metrics/anectico.verification.value?project_id=$ANECTICO_PROJECT"

Deletion removes every datapoint with that metric name in one project and cannot be undone through Anectico. project_id is required: an organization-level credential must name the project to delete from, and there is no way to delete a metric across every project in one call. A project-scoped key may omit project_id and stays confined to its own project; naming a different one is rejected. Prefer the CLI, which sends your active project and requires explicit confirmation:

anectico metrics delete anectico.verification.value --yes

Deletion is durable, not a filter. Once a metric is deleted, its datapoints stop appearing in metric queries, series, exemplars, dashboards and exports — and a late or retried copy sent by an SDK after the delete stays hidden too, rather than reappearing. Alert rules honour the same deletion, including anomaly rules: a deleted metric cannot raise an alert, and cannot contribute to the value an alert reports.

Deletion is metric-specific. Removing one metric name leaves your traces, logs, errors and other metrics untouched; delete those through their own controls.