Queries and Analytics
Five read tools, each answering a different question:
queryreturns events in a time window, with type, tag, and field filters plus cursor pagination.latesttails the newest events - no window needed.countbuckets event counts per interval across a window.aggregatereduces a numeric field to one value, optionally per group.searchapplies structured JSON filters with an explicit sort.
Window arguments accept relative expressions (-1h, -7d, now) or
epoch milliseconds and default to -1h..now - except on search,
where --start/--end are required raw epoch-ms integers. Negative
relative values need the = form (--start=-1h); the space form is
eaten by the flag parser.
Query
snug timeline query -s docs4-activity --start=-1h --end now -t 'user.*'
snug timeline query -s docs4-activity -f user_id:u123
snug timeline query -s docs4-activity -f amount:gt:50
snug timeline query -s docs4-activity --tags region:us-t filters by event type and supports wildcards. -f is a filter
expression: field:value (equals) or field:operator:value with the
short operator names eq, gt, gte, lt, lte, contains, in,
exists. The JSON payload, captured live:
{
"events": [
{
"id": "evt_fKWcGTwVDgrs",
"stream": "docs4-activity",
"type": "user.login",
"timestamp": 1787893274528,
"sequence": 1,
"data": { "amount": 25, "user_id": "u123" },
"tags": ["region:us"]
}
],
"pagination": { "has_more": false },
"time_range": { "start": 1787889686488, "end": 1787893286488 }
}Filter fields are relative to the event's data payload - write
user_id, not data.user_id. A data.-prefixed field
matches nothing, because it looks for a literal data key inside the
payload. (aggregate is the exception - see below.)
When a page fills up, pagination carries a cursor; pass it back with
-c for the next page. Verified with -l 2: page one returned sequences
[1, 2] and "cursor": "1787893274556:evt_vDYQLbPUcnvA", and passing
that cursor returned [3, 4].
Count
count returns one bucket per interval (minute, hour, day,
week) across the whole window, including empty buckets:
snug timeline count -s docs4-activity --start=-24h --end now -i hour{
"interval": "hour",
"counts": [
{ "timestamp": 1787806897279, "count": 0, "start": "2026-08-27T05:01:37.279+00:00", "end": "2026-08-27T06:01:37.279+00:00" },
...
{ "timestamp": 1787889697279, "count": 3, "start": "2026-08-28T04:01:37.279+00:00", "end": "2026-08-28T05:01:37.279+00:00" }
],
"total": 3
}An interval outside that set fails with 400 invalid_time_interval, and
a window whose start is after its end fails with 400 invalid_time_range.
Aggregate
aggregate runs count, sum, avg, min, or max over a
dot-notation field, optionally grouped:
snug timeline aggregate -s docs4-activity --start=-24h --end now -a sum -f amount -g type{
"aggregation": "sum",
"field": "data.amount",
"result": 100.0,
"groups": [
{ "key": "page.view", "value": -0.0 },
{ "key": "user.login", "value": 100.0 }
]
}Here - unlike query and search filters - a leading data. is optional:
amount and data.amount resolve identically (verified: both sum the
same). A group whose events lack the field reports 0 (rendered -0.0).
Search
search posts structured filters as JSON - the same fields and semantics
as query's -f, but with long operator names (equals, in,
contains, greater_than, less_than, greater_than_or_equal,
less_than_or_equal, exists) and an explicit sort
(timestamp_asc, timestamp_desc, sequence_asc, sequence_desc;
default timestamp_desc):
snug timeline search -s docs4-activity --start 0 --end 9999999999999 \
-f '[{"field":"amount","operator":"greater_than","value":50}]' --sort timestamp_ascThis returned exactly the two events whose amount was 75
and 60. The response shape is the same as query's. The same field-path
rule applies: "field": "data.amount" silently matches nothing - use
"amount".
The exact parameters for all five endpoints are in the Timeline API reference.