Skip to main content
Reports return numbers from a fixed catalog of metrics, such as conversations, AI-resolved conversations, task closes, and retention offers. You pick up to five metrics and a time range, and optionally a breakdown by week, phone line, team member, and more. POST /reports only reads data. It uses POST because the request is a JSON body.

Find the metric for your question

Read these definitions before you put a number in front of anyone. AI-resolved, for example, does not mean the customer’s problem was solved.
HTTP 200 does not mean the report ran. A combination of metrics, grouping, filters, and range that the catalog does not support returns HTTP 200 with "status": "unsupported_combination" and no numbers. Always check status before you read values. See Unsupported requests.

Run a report

Try it in the playground.

Request body

string[]
required
One to five metric keys from the catalog. Each key once.
string
required
The time window, in your team’s timezone:
  • today, yesterday, 7d, 14d, 30d, 90d, 365d, mtd, or ytd.
  • month, with month set to a calendar month.
  • custom, with from and to set. Up to 366 days.
today counts. 30d starts 30 days ago at the current time of day in your team’s timezone, and ends now. On October 6, the response’s range shows from 2026-09-06 and to 2026-10-06: 31 dates, the first and last of them partial. The other day ranges work the same way. For whole days, use custom.
string
YYYY-MM. Required when range is month.
string
YYYY-MM-DD, the first day. Required when range is custom.
string
YYYY-MM-DD, the last day, included. Required when range is custom.
string
Break the numbers into rows. One of day, week, month, hour_of_day, category, channel, phone_line, team_member, task_age, action_group, failure_type, failure_subcategory, or cancellation_reason. Each metric supports only some of them. See the catalog.
  • hour_of_day returns 24 rows, labeled 00:00 to 23:00, by the hour each conversation started in your team’s timezone. It works with include_percent_of_total but not with top_n.
  • task_age works only with open_tasks. It returns four rows by the time since the conversation or form came in: Under 1 day, 1 to 3 days, 3 to 7 days, and 7 days or more.
string
team_member. Splits one team-work metric (tasks_worked or task_closes) by team member over time. Requires group_by of day or week.
object
Narrow the data.
integer
1 to 10. Keep only the largest rows of a category-style breakdown, such as category, team_member, or cancellation_reason. For category, the rest go into one All other categories row. Any other grouping returns unsupported_combination. Without top_n, team_member_activity and account_updates return their top 5 rows.
boolean
Add percent_of_total to each row of a category-style or channel breakdown. One grouped count metric at a time.

Response

string
ok when the report ran. See Unsupported requests for the other values.
object
The window the report used: the requested range, the from and to dates (both included), and the team timezone.
object
One entry for each requested metric, keyed by metric. Each entry has a label, a unit (count, percent, minutes, hours, or score), and a value, which is null when there is no data. Rates also include numerator and denominator. A count with a natural base, such as memberships_saved, also has numerator, denominator, and rate. A percent value or rate runs from 0 to 100, rounded to one decimal place.
object[]
The breakdown, when there is one. Each row has a label and values, usually keyed by metric. Only metrics that support the grouping appear in values. A metric with a null value has no data for that row.A row grouped by channel, category, phone_line, or team_member also has that key, next to label:
  • channel: a channel, such as phone.
  • category: a category key, such as cancellation. null on the Uncategorized and All other categories rows.
  • phone_line: the line’s id, name, and phone_number.
  • team_member: the person’s id and name. null on the Unassigned row.
Use these keys to match rows to your own records. The label is for display.A few breakdowns use other keys in values. team_member_activity uses tasks_completed, messages_sent, and notes_added. account_updates uses account_updates, completed, and failures. conversation_categories by phone_line uses Total plus one key for each category label. split_by uses team member names.Count metrics that support day get a trend in rows even without group_by: daily for ranges up to 31 days, weekly beyond that. Ranges today and yesterday get no trend.
string
The grouping the rows use. It can differ from your group_by. A daily series longer than 62 days comes back weekly.
object
The chart the dashboard draws from the rows: kind (line or bar), title, label_kind, and up to five series of points. Absent when the rows cannot be charted.
string[]
How each metric is counted. Read these before you compare numbers with another system.
string[]
Anything that limits this result, such as missing history or overlapping rows.
string
The dashboard Reports page.

Unsupported requests

The catalog limits which metrics, groupings, and filters go together. A request outside those limits returns status set to unsupported_combination, with a reason, a message, and the allowed_alternatives you can retry with.
An ID in filters.team_member_ids that is not a current team member returns reason set to unknown_team_member, with a message that lists the IDs. These responses come back with HTTP 200, not a 4xx. A client that checks only the HTTP status treats them as success. Check status before you read values. A missing month, from, or to, or a custom range that is reversed or longer than 366 days, is also an unsupported_combination. A malformed body returns 400. That includes an unknown metric key and any field the API does not know, such as grouping or filters.phone_number_id. A filters.phone_line_id that is not one of the key owner’s phone lines returns 404 with code not_found.

Read these definitions first

Several metrics mean something narrower than their name. The methodology in each response says exactly what was counted.
  • AI-resolved (ai_resolved_conversations) means nobody on your team replied, changed the task, or added a note. It does not mean the customer’s problem was solved. For that, use caller_resolved_rate.
  • Meaningful conversations (meaningful_conversations) have enough real exchange to judge how they went: phone calls longer than 10 seconds with a transcript where the caller said enough, and website chats with a real exchange or a follow-up task. Texts and emails are never counted.
  • Handled (handled_conversations) means a meaningful conversation that left no task for your team. Needs team (needs_team_conversations) means a meaningful conversation that did leave one. Handled does not mean the customer’s problem was solved.
  • Hours to close and hours to first action count elapsed hours from when the conversation or form came in. They are not business hours or time spent working.
  • First action is the first time a person changed a task’s status or assignee, added a note, sent a reply, or closed it. Creating the task does not count. Because assignment and notes count, hours to first action is not a customer response time. Tasks nobody has acted on are left out of the hours-to-first-action metrics. To see those, run open_tasks with group_by task_age.
  • Asked for a person, resolved by AI (asked_for_person_ai_resolved_conversations) counts conversations where the customer asked for a person, the caller’s need was met, nobody on your team worked it, and the AI attempted no transfer.
  • Transfer attempts (transfer_attempts) count conversations where the AI tried to transfer the caller. A transfer attempt does not mean a person answered.
  • Tasks completed (tasks_completed) counts tasks marked done, including tasks the AI closed on its own. To count closes by people, use task_closes.
  • Team-work metrics (tasks_worked, task_closes, the hours-to-close and hours-to-first-action metrics, same_day_close_rate, and open_tasks) have data from July 25, 2026. A range that ends before then returns unsupported_combination.
  • Memberships saved (memberships_saved) counts only cancellation conversations where the AI pitched a retention offer and the membership was saved. A save with no retention offer pitched is not counted. It counts from July 1, 2026, when save tracking started. Its denominator is the cancellation conversations with a retention offer pitched in the same period.
  • Open tasks right now (open_tasks) is a snapshot. It requires range today.

Examples

AI-resolved rate by week

ai_resolved_rate has no weekly breakdown. Request the two counts by week and divide them. The headline rate for the whole range comes back in values.
The weekly rate for the week of September 8 is 118 ÷ 291, or 40.5%. Each week label is the first day of a seven-day bucket. Buckets count back from the last day of the range, so pick a range whose length is a multiple of seven to get full weeks. The chart field is left out of this example and the ones below.

Tasks closed per team member, with median hours to close

Rows are sorted by the first grouped metric, largest first. Hours to close run from when the conversation or form came in. They measure elapsed time, not time spent working.

Retention offers pitched and accepted

None of these metrics has a daily breakdown, so the response has no rows. For one location, add "filters": { "phone_line_id": 2181 }. To see why customers cancel, run cancellation_reasons with group_by set to cancellation_reason.

Conversations by phone line

The response has one row for each phone line the key’s owner can see, sorted by name. Match rows by phone_line.id. The label is the line name, and when two lines share a name it adds the phone number. The phone channel filter limits the report to calls. Leave it out to count texts, chats, and emails too. For a metric that does not group by phone line, run one report per line with filters.phone_line_id.

Calls per hour

To find your busiest hours, group conversations by hour_of_day and filter to phone calls.
Each row counts the calls that started in that hour, in the team’s timezone, across all 30 days. The busiest hour here is 13:00, with 93 calls. For a daily average, divide by the number of days: 93 ÷ 30 is about 3 calls between 1 and 2 p.m. on a typical day. Add "include_percent_of_total": true to get each hour’s share of the day’s calls.

List the metrics

Try it in the playground. Returns the full catalog. Read it from code instead of hard-coding the table below. New metrics appear there first.
When requires_group_by is true, the metric fails without one of its group_by values. categories lists every category key with its label. filters maps each filter name to its JSON Schema. The example shortens measures, categories, and filters.

Metric catalog

Fifty-four metrics as of October 2026. The table is generated from the same code as GET /reports/metrics. A metric with no group_by returns only a total. “Required” means the metric needs one of the listed groupings. Read these definitions first for the terms in the Description column.

Conversations and calls

Call quality (phone calls only)

Cancellations and offers

Tasks

Team work

group_by month covers at most 12 calendar months. With team_member, the hours-to-first-action metrics credit the person who acted first. For average call length, run avg_duration_minutes with filters.channel set to phone.