filters narrows a reporting query to matching threads, and groupBy splits the result into one series or value per label, tier, company, or other dimension. All three reporting queries accept filters. The time series and single value queries also accept groupBy.
Filter fields
filters takes a ThreadMetricFilters object with these fields:
A company, tenant, tier, or customer group identifier that matches nothing fails the query with
thread_metric_filter_no_match.
Filter by user
assignedToUser and userIds both take user IDs, but they answer different questions:
assignedToUser: who the thread is assigned to right now. It works on every metric and matches the current primary or additional assignee, not whoever was assigned when the measured event happeneduserIds: who gets the credit for the measured action. It only works onagent_metrics, and what credit means depends on the metric
userIds, each agent_ metric credits either the user who did the action or every user ever assigned to the thread.
The user who did the action, such as sending the reply or changing the status. For assignment transitions, that’s the user who was assigned:
agent_threads_first_response_timeagent_threads_time_between_follow_up_responsesagent_messages_sent_countagent_threads_status_transitions_count__todo,__snoozed, and__doneagent_threads_assignment_transitions_count
agent_ metric credits every user ever assigned to the thread, even if the thread has since been reassigned. A thread that passed through 3 users counts toward all 3.
userIds is only allowed at the top level of filters, not inside and, or, or not. On a metric without the agent_ prefix, it returns input_validation. Naming anyone other than yourself in either filter needs the metricsAgent:read permission.
Filters match current values
Filters match the values a thread has now, not the values it had when the measured event happened. Remove a label from a thread and it drops out of every past bucket for that label. The same applies to assignee, tier, priority, company, tenant, customer group, and fields.Combine filters with AND, OR, and NOT
Fields in the same object combine with AND, and the values in one field combine with OR. For other combinations, nest filter objects:and: a list of filter objects that must all matchor: a list of filter objects where at least one must matchnot: one filter object that must not match
and:
and and or up to 2 levels deep, so (A AND B) OR (C AND D) works and a third level returns input_validation. not doesn’t add a level. userIds is only allowed at the top level.
Group results by a dimension
groupBy takes a list with one ThreadMetricGroupByInput. These dimensions are supported:
Each group’s
group.value is a string: an ID such as a tier ID, a priority from "0" to "3", or a field value. group.label holds the display name, such as the tier name. It’s null for MESSAGE_SOURCE, THREAD_FIELD, and TENANT_FIELD, whose value is already readable, and for a deleted entity.
Grouped results follow two rules:
- A thread with several values counts in each group. A thread with 2 labels counts under both, and the same applies to customer groups, tenants, and assignees.
- A thread with no value is left out. Grouping by tier drops threads with no tier, so the groups don’t add up to the ungrouped total.
Limit the number of groups with topN
topN returns the largest groups, from 1 to 50, and pools the rest into one group whose value is __OTHER__. Without topN, the query returns every group. On a large workspace, grouping by a dimension with thousands of values can time out with thread_metric_query_timed_out.
__OTHER__ follows these rules:
- Groups are ranked by the metric’s value over the whole range. On a duration metric, the top 5 groups are the 5 slowest, not the 5 busiest.
__OTHER__isn’t a real entity. You can’t filter by it or get its thread IDs.- On a duration time series,
__OTHER__is the percentile of the remaining groups’ values in each bucket, not the percentile of their threads pooled together. Single value queries pool the remaining groups’ threads and take the percentile of those.
groupBy returns the 10 slowest tiers by first response time and pools the rest:

