Skip to content

Queries and Analytics

Four read tools, each answering a different question:

  • query lists a stream's events on the standard list contract: filters on time, type, tags and any field of the event's data, sorted by timestamp and paged by cursor.
  • query --sort -timestamp 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.

count and aggregate take a window with --start/--end, which accept relative expressions (-1h, -7d, now) or epoch milliseconds and default to -1h..now. Negative relative values need the = form (--start=-1h); the space form is eaten by the flag parser.

Query

snug timeline query -s docs4-activity --filter 'timestamp[gte]=-1h' --filter 'type[prefix]=user.'
snug timeline query -s docs4-activity --filter 'data.user_id[eq]=u123'
snug timeline query -s docs4-activity --filter 'data.amount[gt]=50' --sort -timestamp
snug timeline query -s docs4-activity --filter 'tags[all]=region:us'
snug timeline query -s docs4-activity --filter 'data.error[exists]=false' --all

The list filters on:

  • timestamp with gt, gte, lt, lte: RFC 3339, now, or a time before now such as -30m, -6h, -7d. With no timestamp filter the whole stream is listed.
  • type with eq, ne, in, prefix (type[prefix]=user. matches every user.* type).
  • tags with eq, ne, in, all.
  • data.<path> for any dot-separated path into the event payload, with eq, ne, in, gt, gte, lt, lte, contains and exists (true or false). A value reads as true/false, a number, or text; a whole number also matches the same digits stored as a string. An absent or null field matches only ne and exists=false.

Events come oldest first; --sort -timestamp lists newest first. The list pages by cursor only (page and total are refused): pass page.next_cursor back with --cursor, or read every page with --all. Filters other than the time window are checked as events are scanned, and one request scans at most TIMELINE_MAX_QUERY_RESULTS events, so a narrow filter can return a short page whose has_more is still true - keep following the cursor. The JSON payload:

{
  "events": [
    {
      "id": "evt_fKWcGTwVDgrs",
      "stream": "docs4-activity",
      "type": "user.login",
      "timestamp": 1787893274528,
      "sequence": 1,
      "data": { "amount": 25, "user_id": "u123" },
      "tags": ["region:us"]
    }
  ],
  "page": { "has_more": false }
}

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 data.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 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).

The exact parameters for every endpoint are in the Timeline API reference.