ServicesTimeline

Queries and Analytics

Five read tools, each answering a different question:

  • query returns events in a time window, with type, tag, and field filters plus cursor pagination.
  • latest tails the newest events - no window needed.
  • count buckets event counts per interval across a window.
  • aggregate reduces a numeric field to one value, optionally per group.
  • search applies 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 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_asc

This 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.

On this page