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.