Report builder

Group your calls by any dimension over any date range, in your own time zone, and get the same fourteen measures for every group.

After this page you can answer any question of the form "how did each _ do between these dates?", know which day a call belongs to and why, and pull the same report from a program. You choose what to group by, the window, the time zone and any filters; the report answers with one row per group and the same measures on every row.

It is Reports in the console and GET /api/v1/reports/summary in the API. They are the same query, so a figure you pull agrees with the figure on screen, and both agree with the call log filtered the same way.

The four choices

ChoiceWhat it doesDefault
Group byWhat each row stands for. One dimension, or two.campaign
Windowfrom and to. A call belongs to the moment it started.The last seven days, today included, on your own calendar
Time zoneWhere midnight falls, and where an hour of the day begins.The workspace's time zone
FiltersNarrow the calls before they are grouped.None

Everything else follows from those four. The measures are fixed; you cannot ask for a figure that means something different here from what it means everywhere else.

Group by

Name one or two dimensions — groupBy=publisher,day, or the parameter twice. Two rows are produced per combination that has calls; a combination with none is simply absent.

DimensionA row isWhen a call has none
campaignOne campaign.No campaign
publisherOne publisher.Direct / owned media
buyerThe buyer the call was sent to.No buyer
targetThe target the call was sent to.No target
numberThe tracking number that was dialled.Unknown number
stateThe caller's state, from their area code.Unknown
dayA calendar day in your time zone.
hourAn hour of the day, 0 to 23, labelled 14:00. Every Tuesday at two is one row.
dowA day of the week, 0 (Sunday) to 6.
statusThe call's carrier status.Unknown
reasonWhy the call went unrouted. The row carries the label, the sentence and the owner.Routed calls are left out
tag:<key>One value of one tag — tag:utm_source.A null key labelled (not set)
  • Entities are grouped on their id and labelled with the name frozen on the call, so renaming a campaign does not split it into two rows. A record you have since deleted keeps its name: the row has a null key, the name as its label, and deleted: true.
  • day, hour and dow are computed in the report's time zone, not the server's. A Manila afternoon does not get split across two bars.
  • A report grouped by reason counts only unrouted calls, because routed calls have no reason and a row reading "—: 9,412" answers a question nobody asked. totals still covers everything.
  • The two dimensions must differ, and a third is refused.

The window, and which day a call belongs to

  • from and to take an ISO 8601 instant, such as 2026-09-14T05:00:00Z. A call is in the window when it started inside it, whatever time it ended.
  • With no from, the window starts at midnight six days before to in the report's time zone: the last seven days, today included. With no to, it ends now.
  • The window may span at most 366 days.
  • tz is an IANA zone name such as America/Chicago. It decides where a day, an hour and a day of the week begin. It does not move the from and to you sent.
  • A tz that is not a real zone answers 400 naming the field. It is never quietly replaced with UTC — a report that is wrong by one bar and says nothing is worse than an error.

Filters

Filters narrow the calls before they are grouped, and they are the same vocabulary as the call log — so a filter you build in one can be pasted into the other, and a report row clicks through to exactly the calls behind it.

  • By entity: campaignId, publisherId, buyerId, targetId, numberId. The console shows these as Every campaign, Every publisher and so on until you pick one.
  • By what happened: outcome (connected, converted, unrouted, missed, live), status, reason, hasRecording.
  • By the call itself: caller (a full number, or at least its last four digits), minTalk and maxTalk in seconds.
  • By tag: tagKey alone means the call carries it; with tagValue it must have exactly that value.

Filters only ever narrow. The workspace, and the fact that these are tracking calls, are fixed before any filter is read — there is no filter that widens a report beyond your own calls.

What every row contains

The same fourteen measures, every time. Each is defined once and means the same thing on the overview, on the call log's totals, in an export and from the API. The full definitions are in the metrics glossary.

CountsRatesMoneyTime
calls, connected, converted, unroutedconnectRate, conversionRaterevenueCents, payoutCents, profitCents, usageCents, rpcCents, epcCentsavgTalkSeconds, totalTalkSeconds
Money is integer cents. Rates are fractions to four places — 0.8066, never 80.66 — and are null rather than 0 when there is nothing to divide by: no calls yet and every call failed are different facts.

Ordering and size

Default orderA report whose first dimension is day, hour or dow reads in time order. Anything else reads busiest first.
sortcalls, connected, converted, unrouted, revenueCents, payoutCents, usageCents, totalTalkSeconds or profitCents, with dir=asc or desc.
Not sortableThe two rates and the per-call figures. They are derived after the grouping, so ordering on them is a client-side sort of the rows you hold.
limit1 to 5,000, default 500. truncated: true means more groups matched than came back.
An unknown sortRefused with 400, unlike the call log's, which falls back. A report ordered by something other than what you asked for is a report you would misread.

When a report is truncated, group more coarsely or narrow the window rather than raising the limit. Five thousand rows is not a report anybody reads; it is an export.

Worked example: publisher by day, on the campaign's own clock

A week of one campaign, broken down by publisher and day, for a workspace in Chicago. This needs a key with reports:read; see API keys.

curl -G "https://api.buy3.io/api/v1/reports/summary" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  --data-urlencode "groupBy=publisher,day" \
  --data-urlencode "from=2026-09-14T05:00:00Z" \
  --data-urlencode "to=2026-09-21T05:00:00Z" \
  --data-urlencode "tz=America/Chicago" \
  --data-urlencode "campaignId=6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d"
200 OK (one row shown)
{
  "groupBy": ["publisher", "day"],
  "from": "2026-09-14T05:00:00.000Z",
  "to": "2026-09-21T05:00:00.000Z",
  "timezone": "America/Chicago",
  "rows": [
    {
      "dimensions": [
        { "dimension": "publisher", "key": "c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b", "label": "Northwind Media" },
        { "dimension": "day", "key": "2026-09-19", "label": "2026-09-19" }
      ],
      "calls": 212,
      "connected": 171,
      "converted": 96,
      "unrouted": 14,
      "connectRate": 0.8066,
      "conversionRate": 0.5614,
      "revenueCents": 403200,
      "payoutCents": 264000,
      "profitCents": 139200,
      "usageCents": 2871,
      "avgTalkSeconds": 201,
      "totalTalkSeconds": 34371,
      "rpcCents": 1902,
      "epcCents": 1245
    }
  ],
  "totals": {
    "calls": 1388,
    "connected": 1102,
    "converted": 640,
    "unrouted": 97,
    "connectRate": 0.7939,
    "conversionRate": 0.5808,
    "revenueCents": 2688000,
    "payoutCents": 1760000,
    "profitCents": 928000,
    "usageCents": 18230,
    "avgTalkSeconds": 201,
    "totalTalkSeconds": 221502,
    "rpcCents": 1937,
    "epcCents": 1268
  },
  "truncated": false
}

Read the row: Northwind sent 212 calls that day, a buyer answered 171 of them (connectRate 0.8066) and 96 of those paid (conversionRate 0.5614). Revenue less payout is profitCents, and usageCents — what the platform charged for the connected minutes — is shown separately rather than netted off, so you can see both. rpcCents is what an average call from that publisher was worth before anybody knew how it would end, which is the figure to compare against what you pay them.

The window is written as instants with the Chicago offset, so "the 19th" here is Chicago's 19th and reconciles with a buyer's invoice cut on the same calendar.

Time series, for the chart

GET /api/v1/reports/timeseries answers the same measures per bucket instead of per group, which is what the chart above the table is drawn from.

Three days, one of them quiet
{
  "interval": "day",
  "from": "2026-09-18T05:00:00.000Z",
  "to": "2026-09-21T04:59:59.000Z",
  "timezone": "America/Chicago",
  "points": [
    { "bucket": "2026-09-18", "calls": 0, "connected": 0, "converted": 0, "unrouted": 0, "connectRate": null, "conversionRate": null, "revenueCents": 0, "payoutCents": 0, "profitCents": 0, "usageCents": 0, "avgTalkSeconds": 0, "totalTalkSeconds": 0, "rpcCents": 0, "epcCents": 0 },
    { "bucket": "2026-09-19", "calls": 212, "connected": 171, "converted": 96, "unrouted": 14, "connectRate": 0.8066, "conversionRate": 0.5614, "revenueCents": 403200, "payoutCents": 264000, "profitCents": 139200, "usageCents": 2871, "avgTalkSeconds": 201, "totalTalkSeconds": 34371, "rpcCents": 1902, "epcCents": 1245 },
    { "bucket": "2026-09-20", "calls": 187, "connected": 149, "converted": 81, "unrouted": 11, "connectRate": 0.7968, "conversionRate": 0.5436, "revenueCents": 340200, "payoutCents": 222750, "profitCents": 117450, "usageCents": 2466, "avgTalkSeconds": 198, "totalTalkSeconds": 29502, "rpcCents": 1819, "epcCents": 1191 }
  ]
}
  • interval is hour, day or week. An hourly series may span 31 days; day and week may span 366.
  • Empty buckets are present as zeroes. A chart drawn from a series with holes joins Monday to Thursday with a straight line and invents two days of traffic, so the series is generated from the window itself and the calls are joined onto it.
  • bucket is a wall-clock label in the report's time zone — 2026-09-19, or 2026-09-19T14:00 for an hour — not an instant. Weeks start on Monday.
  • A quiet bucket has zero counts and null rates, exactly as a quiet group does.

Coming back to a report

The whole report lives in the page's query string: the grouping, the columns, the window and every filter. Copy the address and it opens the same report on anybody's screen, which is the portable way to share one.

Starter viewsSaved views
What they areSix one-click reports: Publisher performance, Buyer performance, Campaign P&L, Hour of day, State map, Unrouted reasons.Reports you save yourself.
What they setOnly the grouping, the columns, the highlighted measure and the sort. Your window and your entity filters are left exactly where they were.Everything, window and filters included — that is what you meant when you saved it.
Where they liveIn the product.In this browser only. There is no saved-reports route on the API, so a saved view does not follow you to another machine. The link does.

Reports are live

A report is computed from the calls as they stand now. A conversion or an adjustment on a past call changes any report that covers its date — which is right, and which means a report is not a record.

If you invoice from a report, export it at the time and keep the file. See Exports.

Next steps