Query custom data: metrics and events

Read your custom data back with POST /v1/custom/metrics/query and POST /v1/custom/events/query. Covers raw versus time-bucketed metric queries, the step and aggregation rules, the substring event filter, time-based paging, examples, and every response and error field.

public-apicustom-dataquerymetricseventsaggregationstepbucketedrawpagination

Overview

Read back the metrics and events you sent with the two query endpoints. Both require the API scope, take a start and end window, and return 200 with the results.

Endpoint Reads
POST /v1/custom/metrics/query Your custom metric samples — raw or time-bucketed.
POST /v1/custom/events/query Your custom events in a dataset, newest first.

Query metrics

Method and path: POST /v1/custom/metrics/query
Auth: API key with the API scope.

Field Type Required Constraints Description
metricName string Yes Non-empty The metric to read, matching the name you sent.
start number or string Yes Epoch millis or ISO-8601 Start of the window (inclusive).
end number or string Yes Epoch millis or ISO-8601 End of the window (inclusive).
labels object No String to string Only return series whose labels match all of these.
step string No One of 30s, 5m, 1h, 1d Bucket width for aggregation. If set, agg is required.
agg string No One of avg, sum, min, max, last, none How to combine samples inside each bucket.

Raw versus bucketed

There are two modes, chosen by whether you send step:

Mode How to trigger it Returns
Raw Omit step (and agg, or set agg to none). Every individual sample in the window, each with its labels. Capped at 10000 points.
Bucketed Send a step together with an agg other than none. One aggregated point per step-wide bucket.
Note: The one hard rule: a step requires a non-none agg. Sending step with no agg, or with agg set to none, returns 400 bad_request. Raw mode (no step) is capped at 10000 points — narrow the window or switch to a bucketed query if you hit the cap.

Raw request:

curl -X POST https://api.verops.io/v1/custom/metrics/query \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{
    "metricName": "orders.count",
    "start": 1700000000000,
    "end": 1700003600000,
    "labels": {"region": "us"}
  }'

Raw response:

{
  "metricName": "orders.count",
  "bucketed": false,
  "count": 2,
  "points": [
    {"ts": 1700000000000, "value": 42, "labels": {"region": "us"}},
    {"ts": 1700001800000, "value": 47, "labels": {"region": "us"}}
  ]
}

Bucketed request — an hourly sum:

curl -X POST https://api.verops.io/v1/custom/metrics/query \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{
    "metricName": "orders.count",
    "start": 1700000000000,
    "end": 1700604800000,
    "step": "1h",
    "agg": "sum"
  }'

Bucketed response:

{
  "metricName": "orders.count",
  "bucketed": true,
  "count": 3,
  "points": [
    {"ts": 1700000000000, "value": 89},
    {"ts": 1700003600000, "value": 74},
    {"ts": 1700007200000, "value": 103}
  ]
}

Notes on the response:

  • bucketed tells you which mode you got.
  • count is the number of points returned.
  • Each point's ts is epoch milliseconds. In bucketed mode it is the start of the bucket.
  • labels appear on points in raw mode only; bucketed points have no labels because samples across series are combined.

Aggregations: avg (mean), sum (total), min, max, and last (the most recent value in the bucket). Use none (or omit both) for raw points.

Query events

Method and path: POST /v1/custom/events/query
Auth: API key with the API scope.

Field Type Required Constraints Description
dataset string Yes Non-empty The dataset to read, matching the dataset you sent.
start number or string Yes Epoch millis or ISO-8601 Start of the window (inclusive).
end number or string Yes Epoch millis or ISO-8601 End of the window (inclusive).
contains string No Case-insensitive substring filter matched against the event name and its attributes.
limit number No 1 to 1000, default 100 Maximum number of events to return.

Request example:

curl -X POST https://api.verops.io/v1/custom/events/query \
  -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
  -d '{
    "dataset": "audit",
    "start": 1700000000000,
    "end": 1700604800000,
    "contains": "alice",
    "limit": 50
  }'

Response — 200:

{
  "dataset": "audit",
  "count": 1,
  "events": [
    {
      "ts": 1700000000000,
      "name": "login",
      "dataset": "audit",
      "attributes": {"user": "alice", "ip": "10.0.0.1", "mfa": true}
    }
  ]
}

Events are returned newest first. The contains filter is a plain substring test over both the event name and the serialized attributes, so "alice" matches an event named login whose attributes include "user":"alice".

Paging through more than limit events

There is no cursor. To walk a large window, page by time: read with a limit, take the oldest event's ts from the result, and issue the next call with end set to just below that ts. Repeat until a call returns fewer than limit events.

Errors for both query endpoints

Status Code When
400 bad_request Missing required field; a step without a valid agg; an invalid step or agg value; limit out of range.
401 unauthorized Missing or invalid key.
403 forbidden Key lacks the API scope.
429 rate_limited Rate limit exceeded — honor Retry-After.
500 / 503 internal_error / unavailable Transient — retry with backoff.
Best practice: Prefer bucketed queries for charts and dashboards — they return a bounded, predictable number of points and let the server do the aggregation. Reserve raw queries for short windows where you truly need every sample and its labels, and remember the 10000-point cap.