1. API
  2. Records

Aggregate

Description

Run a grouped aggregation query over records of a single record type — counts, sums, averages, and more, optionally grouped by up to two fields — without retrieving individual records.

This endpoint supersedes the v1 Query record endpoint. Prefer it over paging through the Search records API and aggregating client-side: it is faster.

Despite the POST verb, Aggregate is a read — no records are created. The query travels in the request body.

To discover the record_type_id and field IDs to reference, use the List record types API.

Request

HTTP Method: POST

URL: https://<tenant-domain>/api/v2/records/aggregate

The request body is a JSON object with the following fields:

Body parameter Required Description
record_type_id Yes* Numeric record type ID. Provide this or record_type_name.
record_type_name Yes* Record type name or slug. An alternative to record_type_id; if the name matches multiple types, use record_type_id.
aggregations Yes An array of 1–5 aggregation functions to compute (see Aggregations).
group_by No An array of 0–2 field references to group by (see Group-by). When omitted, you get a single overall row.
filter No A filter tree that narrows records before aggregation (see Filters).
time_range No An array of up to two timestamp windows restricting which records are aggregated (see Time range).
order_by No Sort the output rows (see Order-by).
parent_ids No An array of parent record IDs (max 100). Restricts the query to first-degree children of those records.
timezone No IANA timezone name applied to date_trunc buckets and time-range parsing. Defaults to UTC.
limit No Maximum rows to return, between 1 and 500. When omitted, the server returns up to 1000 rows and sets meta.truncated if more matched.

*Provide at least one of record_type_id or record_type_name. If you send both, record_type_id takes precedence and record_type_name is ignored. If record_type_name matches more than one record type, the request fails with a 400; supply record_type_id to disambiguate.

Field identification

Every field reference in aggregations, group_by, filter, and order_by accepts one of field_id (integer, stable across renames — recommended for automation) or field_name (the exact, case-sensitive field name or canonical snake-case slug). COUNT aggregations may omit both; when a field is supplied to COUNT, the function still counts rows rather than only non-null values in that field.

Aggregations

Each entry in aggregations has:

Key Required Description
fn Yes The aggregation function: COUNT, SUM, AVG, MIN, MAX, or MEDIAN.
field_id or field_name Depends Required for all functions except COUNT. SUM, AVG, MIN, MAX, and MEDIAN only accept NUMBER fields.
alias No A name for this value in the response. Auto-generated if omitted (for example count for a bare COUNT).

At most five aggregations per query.

Group-by

Each entry in group_by has:

Key Required Description
field_id or field_name Yes The field to group by.
date_trunc No For TIMESTAMP fields only: hour, day, week, month, or year. Rolls values into calendar buckets in the query timezone (UTC by default).

At most two group-by entries per query.

JSON and ARTIFACT fields cannot be used in group_by.

Filters

The optional filter field narrows records before they are aggregated. It is a filter tree — either a single condition:

{ "field_name": "Status", "operator": "EQUAL", "value": "open" }

or an AND group of conditions:

{
  "op": "AND",
  "nodes": [
    { "field_name": "Status", "operator": "EQUAL", "value": "open" },
    { "field_id": 102, "operator": "IS_ANY_OF", "value": ["high", "critical"] }
  ]
}

Each condition takes a field reference (field_id or field_name), an operator, and — for most operators — a value. Only AND is supported. Groups may nest to a maximum depth of 4, with up to 50 total nodes and 50 filter conditions. The available operators depend on the field's type; see the Search records API for the full operator matrix.

Time range

The optional time_range restricts which records are aggregated by timestamp. It is an array of up to two windows, ANDed together, with at most one window per field. Each window is an object:

Key Description
field Timestamp column to filter: created_at (default) or updated_at.
rolling_date_range A rolling preset: TODAY, YESTERDAY, LAST_7_DAYS, LAST_31_DAYS, LAST_365_DAYS, or ALL_TIME (ALL_TIME applies no bound).
range_start Inclusive ISO 8601 lower bound of a custom range.
range_end Inclusive ISO 8601 upper bound of a custom range.

Within a window, supply either a rolling_date_range preset or range_start/range_end bounds, not both, and range_start must not be after range_end — otherwise the request returns a 400. Equal bounds select that single instant. A second window on the same field also returns a 400.

Order-by

The order_by array sorts the output rows. It holds at most one entry, naming a group-by field (by field_id or field_name) or an aggregation alias. When omitted, grouped results are ordered by every group-by field in ascending order, with null values last.

Key Required Description
field_id or field_name or alias Yes The column to sort by: a group-by field, or an aggregation alias.
direction No ASC or DESC (case-insensitive). Defaults to ASC.

order_by shares its key shape and validation with the Search records API's sort: an invalid direction, a non-array, more than one key, or an unknown key member each return a 400 (unknown members carry a Did you mean? hint). Naming both a field and an alias in one key also returns a 400.

Example request

curl --proto '=https' --tlsv1.2 \
  -X POST \
  "https://<tenant-domain>/api/v2/records/aggregate" \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <<CREDENTIAL.tines_api_key>>' \
  -d '{
    "record_type_id": 42,
    "aggregations": [
      { "fn": "COUNT", "alias": "total" }
    ],
    "filter": {
      "field_name": "Severity",
      "operator": "IS_ANY_OF",
      "value": ["high", "critical"]
    },
    "group_by": [
      { "field_name": "Status" }
    ],
    "order_by": [
      { "alias": "total", "direction": "DESC" }
    ],
    "limit": 100
  }'

Response

A successful request returns a JSON object containing the aggregation rows and result meta.

Field description

Field Description
rows Array of result rows, each a flat object (see below).
meta Metadata about the query result.

Each object in rows is a flat map of column names to values:

  • Group-by columns are keyed by the field's name. TIMESTAMP values bucketed with date_trunc are ISO 8601 strings whose offset reflects the query timezone.
  • Aggregation columns are keyed by the alias you specified (or the auto-generated name). COUNT values are integers. Numeric aggregates (SUM, AVG, MIN, MAX, MEDIAN) are returned as strings to preserve precision.

The meta object contains:

Field Description
row_count The number of rows returned.
truncated true only when limit was omitted and the query matched more than the 1000-row default cap. Always false when you supply limit — a supplied limit is applied silently, and Aggregate does not support pagination. Use order_by with limit for a top-N query, or narrow the query when you need every row.
columns.group_by Array of column names in rows that come from the group_by fields.
columns.aggregations Array of column names in rows that come from aggregations.

Sample response

{
  "rows": [
    {
      "Status": "open",
      "total": 2
    },
    {
      "Status": "closed",
      "total": 1
    }
  ],
  "meta": {
    "row_count": 2,
    "truncated": false,
    "columns": {
      "group_by": ["Status"],
      "aggregations": ["total"]
    }
  }
}

Errors

Errors are returned as a JSON object with an error field, along with an appropriate HTTP status code:

{
  "error": {
    "type": "bad_request",
    "message": "aggregations is required and must be an array",
    "field": "aggregations"
  }
}
Status Meaning
400 Invalid query — malformed body, unknown parameter, invalid timezone, disallowed operator, or validation failure.
403 The tenant does not have access to records.
404 Record type not found, or the calling token does not have access to it.
504 Query timeout — the query exceeded its time limit. Narrow your filters or the time range.

The error object always carries type and message; validation errors add a field pointer, and some add a hint suggesting a correction.

Was this helpful?
Aggregate | API | Tines