API reference
Reports
Grouped summaries and time series over your calls — counts, rates, talk time, revenue, payout, profit and usage — with a CSV export.
The Reports API is the report builder for programs. You name a window, a time zone, any filters and what to group by. It answers with one row per group, and every row carries the same fourteen measures. Each measure is defined once, in Metrics, and means the same thing on the overview, the call log and every report — so a figure you pull here reconciles with the one on screen.
Two surfaces
The two reports a program needs are on the REST API. The builder's vocabulary and the CSV export are the console's own routes, called with a signed-in owner's or admin's session token — an API key cannot reach them. Each endpoint below says which credential it takes.
| Endpoint | Surface | Credential |
|---|---|---|
GET /api/v1/reports/summary | REST API | API key with reports:read |
GET /api/v1/reports/timeseries | REST API | API key with reports:read |
GET /api/tracking/reports/dimensions | Console | Session token |
GET /api/tracking/reports/export.csv | Console | Session token |
/api/tracking/reports/summary and /api/tracking/reports/timeseries. They are the same handlers, so the query and the answer are identical.The window: from, to and tz
fromandtotake an ISO 8601 instant, such as2026-09-14T05:00:00Z. A call is in the window when it started inside it.- A bare date such as
2026-09-14is read as midnight UTC, not midnight intz. To start a report at the beginning of your own day, send the instant with its offset. - With no
from, the window starts at midnight six days beforeto, in the report's time zone: the last seven days, today included. With noto, it ends now. - The window may span at most 366 days. A time series by the hour may span at most 31 days. A wider range answers
400. tzis an IANA zone name. It sets where aday, anhourand adowbegin, and the buckets of a time series. It does not move thefromandtoyou send. It defaults to the workspace's time zone.- A
tzthat is not a real zone answers400withdetails.tz. It is never quietly replaced with UTC: a report that is wrong by a bar and says nothing is worse than an error.
Filters
Every report endpoint takes the same filters as the call log, so a report and the list of calls behind it can be asked for with one query string. Filters only ever narrow: the workspace, and the fact that these are tracking calls, are fixed before any filter is read. The full list is on each endpoint below.
Grouping with groupBy
groupBy names one or two dimensions. Write them with a comma (groupBy=publisher,day) or repeat the parameter (groupBy=publisher&groupBy=day). The two must differ. The default is campaign.
| Dimension | Groups calls by | key and label on a row |
|---|---|---|
campaign | The campaign. | The campaign's id, and its name as frozen on the call. Calls with none: No campaign. |
publisher | The publisher. | Id and name. Calls with none: Direct / owned media. |
buyer | The buyer the call was sent to. | Id and name. Calls with none: No buyer. |
target | The target the call was sent to. | Id and name. Calls with none: No target. |
number | The tracking number that was dialled. | The number's id, labelled with the number itself. Otherwise Unknown number. |
state | The caller's state, from the area code. | The two-letter code. Otherwise Unknown. |
day | The calendar day the call started, in tz. | 2026-09-19 for both. |
hour | The hour of the day, in tz. | A number from 0 to 23, labelled 14:00. |
dow | The day of the week, in tz. | A number from 0 to 6, where 0 is Sunday, labelled with the day's name. |
status | The call's status. | The status. Otherwise Unknown. |
reason | Why the call went unrouted. | The reason code and its label, with a sentence and an owner beside them. Only unrouted calls are counted: calls that were routed have no reason and are left out of the rows. |
tag:<key> | The value of one tag, such as tag:utm_source. | The tag's value for both. Calls without the tag: a null key, labelled (not set). |
- A tag key may hold up to 64 letters, digits,
_,.and-. Anything else answers400. - Entities are grouped on their id and labelled with the name that was frozen on the call, so a renamed campaign stays one row. A record that has since been deleted keeps its name: its row has a
nullkey, the name aslabel, anddeleted: true. totalscovers every call in the filtered window, not only the rows returned — so it still holds when a report is cut off atlimit, and on areasonreport it counts routed calls too.
Metrics
| Field | Meaning |
|---|---|
calls | Every call that reached one of your tracking numbers or bids. |
connected | Calls a buyer's line answered. A call that ended unrouted is never connected — not even when the fallback number it was forwarded to picked up. |
converted | Calls that earned revenue: the buyer's duration threshold was met, or a conversion was posted or marked by hand. |
unrouted | Calls that ended without reaching a buyer, each under a reason code. |
connectRate | connected ÷ calls. A fraction to four places, such as 0.8066. null when there were no calls. |
conversionRate | converted ÷ connected: of the calls a buyer took, how many paid. null when no call connected. |
revenueCents | What buyers owe you for these calls. |
payoutCents | What you owe publishers for these calls. |
profitCents | revenueCents minus payoutCents. Platform usage is shown separately. |
usageCents | What the platform charged for connected minutes, at your plan's per-minute rate — see pricing. |
avgTalkSeconds | totalTalkSeconds ÷ connected, rounded. 0 when no call connected. |
totalTalkSeconds | Seconds callers spent connected to a buyer. |
rpcCents | Revenue per call: revenueCents ÷ calls, rounded. 0 when there were no calls. |
epcCents | Payout per call: payoutCents ÷ calls, rounded. 0 when there were no calls. |
null rather than 0 on an empty denominator: no calls yet and every call failed are different facts. The longer definitions are in the metrics glossary.The file
Export a report as CSV answers with a file rather than JSON. It is the summary report, written out — the same query, the same rows, the same numbers — so a total added up in a spreadsheet matches the one on screen. This section is the file's contract: what a program that reads it can rely on.
| Part of the file | What it is |
|---|---|
| Media type | text/csv; charset=utf-8, sent as an attachment. The file is named report-<dimensions>-<YYYY-MM-DD>.csv, such as report-publisher-day-2026-09-20.csv; a character that is not a letter, a digit, _, . or - becomes _. |
| Encoding | UTF-8 with a byte-order mark in front, so a spreadsheet opens an accented name as an accented name. Strip the mark before parsing if your reader does not. |
| Records | One record per line, separated by CRLF, to RFC 4180. The last record ends with CRLF too. |
| Header row | Two columns for each dimension you grouped by — <dimension>Key then <dimension> — followed by the fourteen measures in a fixed order. Grouping by publisher,day gives publisherKey,publisher,dayKey,day,calls,…. |
| Data rows | One per group, in the order the report returned them. A key column carries the id (or the date key, or the raw tag value); the column beside it carries the label you would read on screen. |
| Total row | The last line. total and All calls sit in the first dimension's two columns, a second dimension's columns are empty, and the measures are the totals for the whole window — not the sum of the lines above, which differ when the report hit its row cap. |
| Row cap | 5,000 data rows. The limit you send to the JSON report is ignored here. Group more coarsely, or narrow the window, if a report is cut off. |
Quoting, and the leading apostrophe
Every CSV this API writes goes through one writer, so the rules below hold for the call log export and the ping export as well.
- A cell is wrapped in double quotes only when it holds a comma, a double quote, a carriage return or a line feed. A double quote inside a quoted cell is doubled.
- An empty cell is written as nothing at all, so an absent label and an empty string look the same in the file.
- A cell whose value is an object is written as JSON — that is how the call log export writes its
tagscolumn, which is why that cell is quoted and holds doubled quotes inside. - A timestamp column is a full ISO 8601 instant in UTC. Where a file carries a second, local column — the call log export's
startedAtLocalbeside itsstartedAt— the local one is the same moment written in the time zone the export was run in.
Every export is written to the activity log before its first byte, with the grouping, the filters, the window and the row count. A file of your callers' data leaving the platform is something somebody can always account for afterwards.
Endpoints
- GET
/api/v1/reports/summarySummary report - GET
/api/v1/reports/timeseriesTime series - GET
/api/tracking/reports/dimensionsReport vocabulary - GET
/api/tracking/reports/export.csvExport a report as CSV
Summary report
/api/v1/reports/summaryOne row per group over the window, each with the full metric block, and a totals block over the whole filtered window. Read-only: it changes nothing and is not recorded in the activity log. truncated is true when more groups matched than limit allowed. The console calls the same handler at /api/tracking/reports/summary.
Query parameters
| Name | Type | Description |
|---|---|---|
groupByOptional | string | One or two dimensions, separated by a comma or sent as a repeated parameter. See Grouping.up to 160 characters · default campaign |
sortOptional | string | The measure to order rows by. Without it, a report whose first dimension is day, hour or dow reads in time order, and any other reads busiest first.calls, connected, converted, unrouted, revenueCents, payoutCents, usageCents, totalTalkSeconds or profitCents |
dirOptional | string | The direction of sort.asc or desc · default desc |
limitOptional | integer | The most rows to return.1–5000 · default 500 |
fromOptional | date | The start of the window. A call belongs to the moment it started. See The window.default: midnight six days ago, in the report's time zone |
toOptional | date | The end of the window, inclusive.default: now |
tzOptional | string | An IANA time zone such as America/Chicago. It decides where a day, an hour of the day and a day of the week begin.up to 64 characters · default: the workspace's time zone |
campaignIdOptional | uuid | Only calls on this campaign. |
publisherIdOptional | uuid | Only calls from this publisher. |
buyerIdOptional | uuid | Only calls sent to this buyer. |
targetIdOptional | uuid | Only calls sent to this target. |
numberIdOptional | uuid | Only calls that arrived on this tracking number. |
statusOptional | string | Only calls with this call status.initiated, routing, reserved, ringing, waiting, in_progress, completed, missed, rejected, abandoned, no_agent, no_answer, busy, failed or terminated |
outcomeOptional | string | Only calls with this outcome. As a filter, connected is every call a buyer answered, converted or not — never a call that ended unrouted.connected, converted, unrouted, missed or live |
minTalkOptional | integer | Only calls with at least this many seconds of talk time.0–86400, and not more than maxTalk |
maxTalkOptional | integer | Only calls with at most this many seconds of talk time.0–86400 |
callerOptional | string | A caller's number. A full number — E.164, ten digits or eleven — is matched exactly. Anything shorter matches the end of the number.up to 32 characters · at least 4 digits |
tagKeyOptional | string | Only calls that carry this tag. With tagValue, only calls where the tag has exactly that value.up to 64 letters, digits, _, . and - |
tagValueOptional | string | The exact value tagKey must have. It needs tagKey.up to 512 characters |
reasonOptional | string | Only calls that ended unrouted for this reason code. A reason one target was skipped for is not a reason a call ended on, and is refused.one of the eleven unrouted reason codes |
hasRecordingOptional | boolean | Only calls with, or without, a recording.true or false |
Example request
curl "https://api.buy3.io/api/v1/reports/summary?groupBy=publisher,day&from=2026-09-14T05:00:00Z&to=2026-09-21T05:00:00Z&tz=America/Chicago&campaignId=6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200
groupByechoes the dimensions as parsed,fromandtothe window that was used, andtimezonethe zone the buckets are in. Each row'sdimensionsare in the order ofgroupBy.
{
"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
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type, from or to is not a date, or sort, dir or limit is not an allowed value. Unlike a list's sort, an unknown report sort is refused rather than ignored. details maps each field to what was wrong. |
| 400 | bad_request | groupBy names something calls cannot be grouped by, names more than two dimensions or the same one twice, or carries a tag key with a character it may not hold. Also: from is after to, the range is wider than the endpoint allows, or tz is not a time zone we recognise. details names the field. A tagValue without a tagKey, a reason that is not an unrouted reason, a caller with fewer than 4 digits and a minTalk above maxTalk answer the same way. |
| 401 | missing_key | No Authorization: Bearer header was sent. |
| 401 | invalid_key | The key is malformed, not recognised, or revoked with its grace period over. |
| 402 | subscription_inactive | The workspace's subscription is not active. On the REST API that stops reads as well as writes. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking, or does not include API access. details.feature says which. |
| 403 | ip_not_allowed | The key has an IP allowlist and the request came from outside it. |
| 403 | insufficient_scope | The key lacks reports:read. details lists the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Time series
/api/v1/reports/timeseriesThe metric block per hour, day or week across the window, for charting. Empty buckets are present as zeroes, so a chart drawn from it never joins Monday to Thursday across two quiet days. bucket is a wall-clock label in the report's time zone, not an instant: 2026-09-19 for a day or a week, 2026-09-19T14:00 for an hour. Weeks start on Monday. Read-only. The console calls the same handler at /api/tracking/reports/timeseries.
Query parameters
| Name | Type | Description |
|---|---|---|
intervalOptional | string | The size of a bucket. hour limits the window to 31 days; day and week to 366.hour, day or week · default day |
fromOptional | date | The start of the window. A call belongs to the moment it started. See The window.default: midnight six days ago, in the report's time zone |
toOptional | date | The end of the window, inclusive.default: now |
tzOptional | string | An IANA time zone such as America/Chicago. It decides where a day, an hour of the day and a day of the week begin.up to 64 characters · default: the workspace's time zone |
campaignIdOptional | uuid | Only calls on this campaign. |
publisherIdOptional | uuid | Only calls from this publisher. |
buyerIdOptional | uuid | Only calls sent to this buyer. |
targetIdOptional | uuid | Only calls sent to this target. |
numberIdOptional | uuid | Only calls that arrived on this tracking number. |
statusOptional | string | Only calls with this call status.initiated, routing, reserved, ringing, waiting, in_progress, completed, missed, rejected, abandoned, no_agent, no_answer, busy, failed or terminated |
outcomeOptional | string | Only calls with this outcome. As a filter, connected is every call a buyer answered, converted or not — never a call that ended unrouted.connected, converted, unrouted, missed or live |
minTalkOptional | integer | Only calls with at least this many seconds of talk time.0–86400, and not more than maxTalk |
maxTalkOptional | integer | Only calls with at most this many seconds of talk time.0–86400 |
callerOptional | string | A caller's number. A full number — E.164, ten digits or eleven — is matched exactly. Anything shorter matches the end of the number.up to 32 characters · at least 4 digits |
tagKeyOptional | string | Only calls that carry this tag. With tagValue, only calls where the tag has exactly that value.up to 64 letters, digits, _, . and - |
tagValueOptional | string | The exact value tagKey must have. It needs tagKey.up to 512 characters |
reasonOptional | string | Only calls that ended unrouted for this reason code. A reason one target was skipped for is not a reason a call ended on, and is refused.one of the eleven unrouted reason codes |
hasRecordingOptional | boolean | Only calls with, or without, a recording.true or false |
Example request
curl "https://api.buy3.io/api/v1/reports/timeseries?interval=day&from=2026-09-18T05:00:00Z&to=2026-09-21T04:59:59Z&tz=America/Chicago&publisherId=c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200One point per bucket, oldest first. A quiet bucket has zero counts and
nullrates.
{
"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
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | interval is not hour, day or week, a filter has the wrong type, or from or to is not a date. |
| 400 | bad_request | from is after to, the range is wider than the endpoint allows, or tz is not a time zone we recognise. details names the field. An hourly series over more than 31 days is the usual cause. The filter rules of the summary report apply here too. |
| 401 | missing_key | No Authorization: Bearer header was sent. |
| 401 | invalid_key | The key is malformed, not recognised, or revoked with its grace period over. |
| 402 | subscription_inactive | The workspace's subscription is not active. On the REST API that stops reads as well as writes. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking, or does not include API access. details.feature says which. |
| 403 | ip_not_allowed | The key has an IP allowlist and the request came from outside it. |
| 403 | insufficient_scope | The key lacks reports:read. details lists the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Report vocabulary
/api/tracking/reports/dimensionsWhat a report can be built from: every dimension, every metric with its format and definition, the measures sort accepts, the intervals and their widest ranges, the outcomes and unrouted reasons a filter may name — and the tag keys your calls have actually carried. tagKeys is the 100 most common keys among the newest 5,000 tagged calls of the last 30 days. It takes no parameters and changes nothing. This is a console route: an API key cannot call it.
Example request
curl "https://api.buy3.io/api/tracking/reports/dimensions" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The example is abbreviated:
metricslists all fourteen measures andreasonsall eleven unrouted reasons. Only the two rates carry aformula.
{
"dimensions": [
{
"key": "campaign",
"label": "Campaign",
"kind": "entity"
},
{
"key": "publisher",
"label": "Publisher",
"kind": "entity"
},
{
"key": "buyer",
"label": "Buyer",
"kind": "entity"
},
{
"key": "target",
"label": "Target",
"kind": "entity"
},
{
"key": "number",
"label": "Tracking number",
"kind": "entity"
},
{
"key": "state",
"label": "Caller state",
"kind": "text"
},
{
"key": "day",
"label": "Day",
"kind": "time"
},
{
"key": "hour",
"label": "Hour of day",
"kind": "time"
},
{
"key": "dow",
"label": "Day of week",
"kind": "time"
},
{
"key": "status",
"label": "Call status",
"kind": "text"
},
{
"key": "reason",
"label": "Unrouted reason",
"kind": "text"
},
{
"key": "tag:<key>",
"label": "Tag",
"kind": "tag"
}
],
"metrics": [
{
"key": "calls",
"label": "Calls",
"format": "integer",
"description": "Every call that reached one of the workspace's tracking numbers or bids."
},
{
"key": "connectRate",
"label": "Connect rate",
"format": "percent",
"formula": "connected ÷ calls",
"description": "Connected ÷ calls: of every call that arrived, how many a buyer answered. Empty when there were no calls."
},
{
"key": "revenueCents",
"label": "Revenue",
"format": "cents",
"description": "What buyers owe the workspace for these calls."
}
],
"sortable": [
"calls",
"connected",
"converted",
"unrouted",
"revenueCents",
"payoutCents",
"usageCents",
"totalTalkSeconds",
"profitCents"
],
"intervals": [
{
"key": "hour",
"maxRangeDays": 31
},
{
"key": "day",
"maxRangeDays": 366
},
{
"key": "week",
"maxRangeDays": 366
}
],
"outcomes": [
"connected",
"converted",
"unrouted",
"missed",
"live"
],
"reasons": [
{
"code": "campaign_closed",
"label": "Outside campaign hours",
"sentence": "The call arrived outside the campaign's hours of operation.",
"owner": "campaign"
}
],
"maxGroupBy": 2,
"maxRangeDays": 366,
"tagKeys": [
{
"key": "utm_source",
"calls": 1204
},
{
"key": "gclid",
"calls": 987
}
],
"timezone": "America/Chicago"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | You named a workspace with ?agencyId= that is not yours, or does not exist — the same answer to both, so the parameter cannot be used to find out which workspaces exist. |
Export a report as CSV
/api/tracking/reports/export.csvThe summary report as a file: the same query, the same rows and the same numbers, so a total computed in a spreadsheet matches the one on screen. The row cap is fixed at 5,000 and limit is ignored. Every export is recorded in the activity log before the first byte is sent (export, medium severity) with the grouping, the filters, the window and the row count. It is a read, so it stays open to a workspace whose subscription has lapsed. This is a console route: an API key cannot call it. The file describes the columns, the quoting and the row cap.
Query parameters
| Name | Type | Description |
|---|---|---|
groupByOptional | string | One or two dimensions, separated by a comma or sent as a repeated parameter. See Grouping.up to 160 characters · default campaign |
sortOptional | string | The measure to order rows by. Without it, a report whose first dimension is day, hour or dow reads in time order, and any other reads busiest first.calls, connected, converted, unrouted, revenueCents, payoutCents, usageCents, totalTalkSeconds or profitCents |
dirOptional | string | The direction of sort.asc or desc · default desc |
fromOptional | date | The start of the window. A call belongs to the moment it started. See The window.default: midnight six days ago, in the report's time zone |
toOptional | date | The end of the window, inclusive.default: now |
tzOptional | string | An IANA time zone such as America/Chicago. It decides where a day, an hour of the day and a day of the week begin.up to 64 characters · default: the workspace's time zone |
campaignIdOptional | uuid | Only calls on this campaign. |
publisherIdOptional | uuid | Only calls from this publisher. |
buyerIdOptional | uuid | Only calls sent to this buyer. |
targetIdOptional | uuid | Only calls sent to this target. |
numberIdOptional | uuid | Only calls that arrived on this tracking number. |
statusOptional | string | Only calls with this call status.initiated, routing, reserved, ringing, waiting, in_progress, completed, missed, rejected, abandoned, no_agent, no_answer, busy, failed or terminated |
outcomeOptional | string | Only calls with this outcome. As a filter, connected is every call a buyer answered, converted or not — never a call that ended unrouted.connected, converted, unrouted, missed or live |
minTalkOptional | integer | Only calls with at least this many seconds of talk time.0–86400, and not more than maxTalk |
maxTalkOptional | integer | Only calls with at most this many seconds of talk time.0–86400 |
callerOptional | string | A caller's number. A full number — E.164, ten digits or eleven — is matched exactly. Anything shorter matches the end of the number.up to 32 characters · at least 4 digits |
tagKeyOptional | string | Only calls that carry this tag. With tagValue, only calls where the tag has exactly that value.up to 64 letters, digits, _, . and - |
tagValueOptional | string | The exact value tagKey must have. It needs tagKey.up to 512 characters |
reasonOptional | string | Only calls that ended unrouted for this reason code. A reason one target was skipped for is not a reason a call ended on, and is refused.one of the eleven unrouted reason codes |
hasRecordingOptional | boolean | Only calls with, or without, a recording.true or false |
Example request
curl "https://api.buy3.io/api/tracking/reports/export.csv?groupBy=publisher,day&from=2026-09-14T05:00:00Z&to=2026-09-21T05:00:00Z&tz=America/Chicago&campaignId=6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-o report.csvResponses
- 200
Content-Type: text/csv; charset=utf-8, sent as an attachment namedreport-<dimensions>-<YYYY-MM-DD>.csv.
publisherKey,publisher,dayKey,day,calls,connected,converted,unrouted,connectRate,conversionRate,revenueCents,payoutCents,profitCents,usageCents,avgTalkSeconds,totalTalkSeconds,rpcCents,epcCents
c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b,Northwind Media,2026-09-19,2026-09-19,212,171,96,14,0.8066,0.5614,403200,264000,139200,2871,201,34371,1902,1245
total,All calls,,,1388,1102,640,97,0.7939,0.5808,2688000,1760000,928000,18230,201,221502,1937,1268Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | As for the summary report. A bad query is refused before anything is written to the activity log. |
| 400 | bad_request | A bad groupBy, or: from is after to, the range is wider than the endpoint allows, or tz is not a time zone we recognise. details names the field. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | You named a workspace with ?agencyId= that is not yours, or does not exist — the same answer to both, so the parameter cannot be used to find out which workspaces exist. |
