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
| Choice | What it does | Default |
|---|---|---|
| Group by | What each row stands for. One dimension, or two. | campaign |
| Window | from and to. A call belongs to the moment it started. | The last seven days, today included, on your own calendar |
| Time zone | Where midnight falls, and where an hour of the day begins. | The workspace's time zone |
| Filters | Narrow 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.
| Dimension | A row is | When a call has none |
|---|---|---|
campaign | One campaign. | No campaign |
publisher | One publisher. | Direct / owned media |
buyer | The buyer the call was sent to. | No buyer |
target | The target the call was sent to. | No target |
number | The tracking number that was dialled. | Unknown number |
state | The caller's state, from their area code. | Unknown |
day | A calendar day in your time zone. | — |
hour | An hour of the day, 0 to 23, labelled 14:00. Every Tuesday at two is one row. | — |
dow | A day of the week, 0 (Sunday) to 6. | — |
status | The call's carrier status. | Unknown |
reason | Why 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
nullkey, the name as its label, anddeleted: true. day,houranddoware 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
reasoncounts only unrouted calls, because routed calls have no reason and a row reading "—: 9,412" answers a question nobody asked.totalsstill covers everything. - The two dimensions must differ, and a third is refused.
The window, and which day a call belongs to
fromandtotake an ISO 8601 instant, such as2026-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 beforetoin the report's time zone: the last seven days, today included. With noto, it ends now. - The window may span at most 366 days.
tzis an IANA zone name such asAmerica/Chicago. It decides where a day, an hour and a day of the week begin. It does not move thefromandtoyou sent.- A
tzthat is not a real zone answers400naming 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),minTalkandmaxTalkin seconds. - By tag:
tagKeyalone means the call carries it; withtagValueit 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.
| Counts | Rates | Money | Time |
|---|---|---|---|
calls, connected, converted, unrouted | connectRate, conversionRate | revenueCents, payoutCents, profitCents, usageCents, rpcCents, epcCents | avgTalkSeconds, totalTalkSeconds |
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 order | A report whose first dimension is day, hour or dow reads in time order. Anything else reads busiest first. |
sort | calls, connected, converted, unrouted, revenueCents, payoutCents, usageCents, totalTalkSeconds or profitCents, with dir=asc or desc. |
| Not sortable | The 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. |
limit | 1 to 5,000, default 500. truncated: true means more groups matched than came back. |
An unknown sort | Refused 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"{
"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.
{
"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 }
]
}intervalishour,dayorweek. 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.
bucketis a wall-clock label in the report's time zone —2026-09-19, or2026-09-19T14:00for an hour — not an instant. Weeks start on Monday.- A quiet bucket has zero counts and
nullrates, 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 views | Saved views | |
|---|---|---|
| What they are | Six one-click reports: Publisher performance, Buyer performance, Campaign P&L, Hour of day, State map, Unrouted reasons. | Reports you save yourself. |
| What they set | Only 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 live | In 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.
