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.

EndpointSurfaceCredential
GET /api/v1/reports/summaryREST APIAPI key with reports:read
GET /api/v1/reports/timeseriesREST APIAPI key with reports:read
GET /api/tracking/reports/dimensionsConsoleSession token
GET /api/tracking/reports/export.csvConsoleSession token
The console calls the first two as well, at /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

  • 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.
  • A bare date such as 2026-09-14 is read as midnight UTC, not midnight in tz. 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 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. A time series by the hour may span at most 31 days. A wider range answers 400.
  • tz is an IANA zone name. It sets where a day, an hour and a dow begin, and the buckets of a time series. It does not move the from and to you send. It defaults to the workspace's time zone.
  • A tz that is not a real zone answers 400 with details.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.

DimensionGroups calls bykey and label on a row
campaignThe campaign.The campaign's id, and its name as frozen on the call. Calls with none: No campaign.
publisherThe publisher.Id and name. Calls with none: Direct / owned media.
buyerThe buyer the call was sent to.Id and name. Calls with none: No buyer.
targetThe target the call was sent to.Id and name. Calls with none: No target.
numberThe tracking number that was dialled.The number's id, labelled with the number itself. Otherwise Unknown number.
stateThe caller's state, from the area code.The two-letter code. Otherwise Unknown.
dayThe calendar day the call started, in tz.2026-09-19 for both.
hourThe hour of the day, in tz.A number from 0 to 23, labelled 14:00.
dowThe day of the week, in tz.A number from 0 to 6, where 0 is Sunday, labelled with the day's name.
statusThe call's status.The status. Otherwise Unknown.
reasonWhy 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 answers 400.
  • 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 null key, the name as label, and deleted: true.
  • totals covers every call in the filtered window, not only the rows returned — so it still holds when a report is cut off at limit, and on a reason report it counts routed calls too.

Metrics

FieldMeaning
callsEvery call that reached one of your tracking numbers or bids.
connectedCalls 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.
convertedCalls that earned revenue: the buyer's duration threshold was met, or a conversion was posted or marked by hand.
unroutedCalls that ended without reaching a buyer, each under a reason code.
connectRateconnected ÷ calls. A fraction to four places, such as 0.8066. null when there were no calls.
conversionRateconverted ÷ connected: of the calls a buyer took, how many paid. null when no call connected.
revenueCentsWhat buyers owe you for these calls.
payoutCentsWhat you owe publishers for these calls.
profitCentsrevenueCents minus payoutCents. Platform usage is shown separately.
usageCentsWhat the platform charged for connected minutes, at your plan's per-minute rate — see pricing.
avgTalkSecondstotalTalkSeconds ÷ connected, rounded. 0 when no call connected.
totalTalkSecondsSeconds callers spent connected to a buyer.
rpcCentsRevenue per call: revenueCents ÷ calls, rounded. 0 when there were no calls.
epcCentsPayout per call: payoutCents ÷ calls, rounded. 0 when there were no calls.
Money is integer cents. The two rates are fractions, never percentages, and are 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 fileWhat it is
Media typetext/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 _.
EncodingUTF-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.
RecordsOne record per line, separated by CRLF, to RFC 4180. The last record ends with CRLF too.
Header rowTwo 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 rowsOne 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 rowThe 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 cap5,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 tags column, 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 startedAtLocal beside its startedAt — 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

Summary report

GET/api/v1/reports/summary

One 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.

API key. Send Authorization: Bearer <api key>. See Authentication.Scopereports:read

Query parameters

NameTypeDescription
groupByOptionalstringOne or two dimensions, separated by a comma or sent as a repeated parameter. See Grouping.up to 160 characters · default campaign
sortOptionalstringThe 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
dirOptionalstringThe direction of sort.asc or desc · default desc
limitOptionalintegerThe most rows to return.1–5000 · default 500
fromOptionaldateThe 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
toOptionaldateThe end of the window, inclusive.default: now
tzOptionalstringAn 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
campaignIdOptionaluuidOnly calls on this campaign.
publisherIdOptionaluuidOnly calls from this publisher.
buyerIdOptionaluuidOnly calls sent to this buyer.
targetIdOptionaluuidOnly calls sent to this target.
numberIdOptionaluuidOnly calls that arrived on this tracking number.
statusOptionalstringOnly calls with this call status.initiated, routing, reserved, ringing, waiting, in_progress, completed, missed, rejected, abandoned, no_agent, no_answer, busy, failed or terminated
outcomeOptionalstringOnly 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
minTalkOptionalintegerOnly calls with at least this many seconds of talk time.0–86400, and not more than maxTalk
maxTalkOptionalintegerOnly calls with at most this many seconds of talk time.0–86400
callerOptionalstringA 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
tagKeyOptionalstringOnly calls that carry this tag. With tagValue, only calls where the tag has exactly that value.up to 64 letters, digits, _, . and -
tagValueOptionalstringThe exact value tagKey must have. It needs tagKey.up to 512 characters
reasonOptionalstringOnly 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
hasRecordingOptionalbooleanOnly 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

  • 200groupBy echoes the dimensions as parsed, from and to the window that was used, and timezone the zone the buckets are in. Each row's dimensions are in the order of groupBy.
{
  "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

StatusCodeWhen
400validation_errorA 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.
400bad_requestgroupBy 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.
401missing_keyNo Authorization: Bearer header was sent.
401invalid_keyThe key is malformed, not recognised, or revoked with its grace period over.
402subscription_inactiveThe workspace's subscription is not active. On the REST API that stops reads as well as writes.
402plan_upgrade_requiredThe workspace's plan does not include call tracking, or does not include API access. details.feature says which.
403ip_not_allowedThe key has an IP allowlist and the request came from outside it.
403insufficient_scopeThe key lacks reports:read. details lists the scopes required and the ones held.
429rate_limitedThe key is over its per-minute limit. Wait for Retry-After seconds.

Time series

GET/api/v1/reports/timeseries

The 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.

API key. Send Authorization: Bearer <api key>. See Authentication.Scopereports:read

Query parameters

NameTypeDescription
intervalOptionalstringThe size of a bucket. hour limits the window to 31 days; day and week to 366.hour, day or week · default day
fromOptionaldateThe 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
toOptionaldateThe end of the window, inclusive.default: now
tzOptionalstringAn 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
campaignIdOptionaluuidOnly calls on this campaign.
publisherIdOptionaluuidOnly calls from this publisher.
buyerIdOptionaluuidOnly calls sent to this buyer.
targetIdOptionaluuidOnly calls sent to this target.
numberIdOptionaluuidOnly calls that arrived on this tracking number.
statusOptionalstringOnly calls with this call status.initiated, routing, reserved, ringing, waiting, in_progress, completed, missed, rejected, abandoned, no_agent, no_answer, busy, failed or terminated
outcomeOptionalstringOnly 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
minTalkOptionalintegerOnly calls with at least this many seconds of talk time.0–86400, and not more than maxTalk
maxTalkOptionalintegerOnly calls with at most this many seconds of talk time.0–86400
callerOptionalstringA 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
tagKeyOptionalstringOnly calls that carry this tag. With tagValue, only calls where the tag has exactly that value.up to 64 letters, digits, _, . and -
tagValueOptionalstringThe exact value tagKey must have. It needs tagKey.up to 512 characters
reasonOptionalstringOnly 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
hasRecordingOptionalbooleanOnly 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 null rates.
{
  "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

StatusCodeWhen
400validation_errorinterval is not hour, day or week, a filter has the wrong type, or from or to is not a date.
400bad_requestfrom 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.
401missing_keyNo Authorization: Bearer header was sent.
401invalid_keyThe key is malformed, not recognised, or revoked with its grace period over.
402subscription_inactiveThe workspace's subscription is not active. On the REST API that stops reads as well as writes.
402plan_upgrade_requiredThe workspace's plan does not include call tracking, or does not include API access. details.feature says which.
403ip_not_allowedThe key has an IP allowlist and the request came from outside it.
403insufficient_scopeThe key lacks reports:read. details lists the scopes required and the ones held.
429rate_limitedThe key is over its per-minute limit. Wait for Retry-After seconds.

Report vocabulary

GET/api/tracking/reports/dimensions

What 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.

Console session. Called by the console with the signed-in user's session. Use the REST API with an API key for your own integrations.

Example request

curl "https://api.buy3.io/api/tracking/reports/dimensions" \
  -H "Authorization: Bearer $BUY3_SESSION_TOKEN"

Responses

  • 200The example is abbreviated: metrics lists all fourteen measures and reasons all eleven unrouted reasons. Only the two rates carry a formula.
{
  "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

StatusCodeWhen
401unauthorizedNo session token, or one that has expired.
402plan_upgrade_requiredThe workspace's plan does not include call tracking. details.feature is callTracking.
403forbiddenYou named no workspace and hold no owner or admin seat on any.
404not_foundYou 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

GET/api/tracking/reports/export.csv

The 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.

Console session. Called by the console with the signed-in user's session. Use the REST API with an API key for your own integrations.

Query parameters

NameTypeDescription
groupByOptionalstringOne or two dimensions, separated by a comma or sent as a repeated parameter. See Grouping.up to 160 characters · default campaign
sortOptionalstringThe 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
dirOptionalstringThe direction of sort.asc or desc · default desc
fromOptionaldateThe 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
toOptionaldateThe end of the window, inclusive.default: now
tzOptionalstringAn 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
campaignIdOptionaluuidOnly calls on this campaign.
publisherIdOptionaluuidOnly calls from this publisher.
buyerIdOptionaluuidOnly calls sent to this buyer.
targetIdOptionaluuidOnly calls sent to this target.
numberIdOptionaluuidOnly calls that arrived on this tracking number.
statusOptionalstringOnly calls with this call status.initiated, routing, reserved, ringing, waiting, in_progress, completed, missed, rejected, abandoned, no_agent, no_answer, busy, failed or terminated
outcomeOptionalstringOnly 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
minTalkOptionalintegerOnly calls with at least this many seconds of talk time.0–86400, and not more than maxTalk
maxTalkOptionalintegerOnly calls with at most this many seconds of talk time.0–86400
callerOptionalstringA 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
tagKeyOptionalstringOnly calls that carry this tag. With tagValue, only calls where the tag has exactly that value.up to 64 letters, digits, _, . and -
tagValueOptionalstringThe exact value tagKey must have. It needs tagKey.up to 512 characters
reasonOptionalstringOnly 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
hasRecordingOptionalbooleanOnly 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.csv

Responses

  • 200Content-Type: text/csv; charset=utf-8, sent as an attachment named report-<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,1268

Errors

StatusCodeWhen
400validation_errorAs for the summary report. A bad query is refused before anything is written to the activity log.
400bad_requestA 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.
401unauthorizedNo session token, or one that has expired.
402plan_upgrade_requiredThe workspace's plan does not include call tracking. details.feature is callTracking.
403forbiddenYou named no workspace and hold no owner or admin seat on any.
404not_foundYou 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.