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.
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: asteprequires a non-noneagg. Sendingstepwith noagg, or withaggset tonone, returns400 bad_request. Raw mode (nostep) 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:
bucketedtells you which mode you got.countis the number of points returned.- Each point's
tsis epoch milliseconds. In bucketed mode it is the start of the bucket. labelsappear on points in raw mode only; bucketed points have nolabelsbecause 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.