Exports

CSV exports for calls, reports, the ping ledger and the activity log — what each contains, how big it may be, and the quoting rules a parser needs.

After this page you can take any table in the console away as a file, write a job that pulls one on a schedule, and parse the result without being surprised. Anything you can see as a table, you can export — with the filters that are on screen, built on the server so it holds every matching row rather than the page you happen to be looking at.

Every export is also written to the activity log before its first byte, with the filter that produced it and the row count. A file of your callers' data leaving the platform is an act somebody can always account for afterwards.

The four exports

ExportOne row perCapCredential
Calls, from the call logCall, with its tags, reason, buyer, target, duration and books.100,000API key with calls:read, or a session
Report, from the report builderGroup, with the fourteen measures.5,000Session only
Pings, from the ping ledgerPing, inbound or outbound, with its outcome, reject code and latency.50,000Session only
Activity, from the activity logChange, with who made it and the values before and after.50,000Session only
Only the call export is on the REST API. The other three are console routes, called with a signed-in owner's or admin's session token.

All four are reads, so they stay open to a workspace whose subscription has lapsed. Being unable to get your own history out because a card expired would be the wrong kind of gate.

The call export

The log exactly as it is filtered, streamed. It is the export people actually use, and the only one a program can pull.

curl -G "https://api.buy3.io/api/v1/calls/export.csv" \
  -H "Authorization: Bearer $BUY3_API_KEY" \
  --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" \
  -o calls-2026-09-21.csv
calls-2026-09-21.csv (two rows)
ref,startedAt,startedAtLocal,status,outcome,callerNumber,dialedNumber,campaign,publisher,buyer,target,ringSeconds,talkSeconds,converted,revenueCents,payoutCents,profitCents,usageCents,reasonCode,reasonSentence,hasRecording,tags
CA-482137,2026-09-20T14:03:11.000Z,2026-09-20 09:03:11,completed,converted,+14155550142,+18885550100,Acme Health — Inbound,Northwind Media,Acme Health,Acme Health — Dallas floor,8,184,true,4200,2750,1450,22,,,true,"{""gclid"":""Cj0KCQjw"",""utm_source"":""google""}"
CA-482201,2026-09-20T21:14:02.000Z,2026-09-20 16:14:02,no_answer,unrouted,+14155550188,+18885550100,Acme Health — Inbound,Northwind Media,,,0,0,false,0,0,0,0,campaign_closed,The call arrived outside the campaign's hours of operation.,false,
ColumnIs
refThe call's public reference. Quote it in a ticket.
startedAtA full ISO 8601 instant in UTC.
startedAtLocalThe same moment as YYYY-MM-DD HH:MM:SS in the time zone the export was run in — a format a spreadsheet sorts correctly.
statusThe carrier-level status.
outcomeOne of converted, connected, unrouted, missed, live.
callerNumber, dialedNumberE.164. callerNumber is empty when the caller withheld it.
campaign, publisher, buyer, targetNames as frozen on the call, so a buyer you have since renamed still reads as it did. Empty where the call had none.
ringSeconds, talkSecondsSeconds ringing before an answer, and seconds with the buyer.
convertedtrue or false.
revenueCents, payoutCents, profitCents, usageCentsInteger cents. The first three are books; the fourth was charged.
reasonCode, reasonSentenceOnly on an unrouted call: the code and the sentence the router stored at the time.
hasRecordingtrue or false.
tagsThe call's tags as a JSON object in one cell, or empty when it has none. That is why the cell is quoted and holds doubled quotes.
  • It takes the same filters as the call log, so "export what I am looking at" is literally the same query.
  • Rows are streamed in batches, newest first, so a hundred thousand of them never become one enormous allocation on a server that is also routing calls.
  • Over the cap, the newest 100,000 rows are written. The activity entry records both the number that matched and the number exported, so you can tell a full file from a capped one.
  • The file is named calls-YYYY-MM-DD.csv, dated today rather than after the window.

The report export

The summary on screen, written out: the same query, the same rows, the same numbers — so a total added up in a spreadsheet matches the footer on the page.

report-publisher-day-2026-09-20.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
  • Two columns per dimension: <dimension>Key then <dimension>. The key is the id a spreadsheet can join on; the column beside it is the label a person reads.
  • Then the fourteen measures in a fixed order. Rates are fractions and money is cents — the same numbers the API returns.
  • The last line is the total row: total and All calls in the first dimension's two columns, a second dimension's columns empty, and the measures for the whole window rather than the sum of the lines above. On a report that hit its cap the two differ, and the footer is the true one.
  • The cap is 5,000 data rows and the limit you send to the JSON report is ignored. Group more coarsely or narrow the window.
  • The file is named after the grouping: report-publisher-day-2026-09-20.csv.

The ping export

One file per direction, because inbound and outbound pings have different columns and a CSV with half its cells empty is two CSVs pretending to be one.

DirectionColumns
Inbound — publishers pinging youref, at, outcome, reasonCode, reason, reasonSentence, campaign, publisher, callerNumber, callerState, elapsedMs, statusCode, payoutCents, clearingPriceCents, winningTarget, targetsSolicited, bidsReceived
Outbound — you pinging buyersbidId, at, origin, auctionRef, campaign, publisher, target, buyer, status, won, bidCents, httpStatus, latencyMs, rejectCode, rejectReason, sentence, callId
  • Callers are left masked exactly as they are on screen. The export is not a way round a campaign's disclosure setting.
  • The cap is 50,000 rows, streamed like the call export.
  • The ledger itself is pruned after 30 days, so an older window exports nothing rather than failing. See Limits.

The activity export

The filtered history, with the changes column flattened into one readable cell rather than a blob of JSON: field: before → after; field: before → after.

activity-2026-09-20.csv
id,at,severity,kind,actor,actorKind,action,targetType,target,targetId,changes,fields,ip,requestId
88213,2026-09-20T16:22:04.118Z,medium,call,Dana Whitfield,user,marked call CA-482137 as converted,call,CA-482137,0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11,converted: false → true; revenueCents: 0 → 5000,,203.0.113.41,req_0f1e2d3c4b5a6978
88214,2026-09-20T16:41:52.117Z,high,export,API key b3_live_Zk3q,api_key,exported 1388 calls from the call log,call,1388 calls,,,caller_number,203.0.113.41,req_1a2b3c4d5e6f7080
  • Columns are id, at, severity, kind, actor, actorKind, action, targetType, target, targetId, changes, fields, ip, requestId.
  • fields names the fields an entry touched, never their values — it is how an entry says "a caller's number was involved" without repeating one.
  • limit defaults to 10,000 and may be raised to the cap of 50,000.
  • It redacts exactly as the log on screen does: a row written while our staff were acting on your account keeps its action and its changes, and loses their address and their internal notes.

The format

Media typetext/csv; charset=utf-8, sent as an attachment with a filename.
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 per line. CRLF for calls, reports and pings; LF for activity.
QuotingA 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.
Empty cellsWritten as nothing at all, so an absent value and an empty string look the same in the file.
ObjectsWritten as JSON — which is how the call export's tags column works.
TimestampsA full ISO 8601 instant in UTC. Where a file carries a second, local column, it is the same moment in the export's time zone.

The leading apostrophe is ours, not the data's

  • In the call, report and ping exports a phone-shaped cell is left alone: +18885550100 arrives as it is, because an export whose every number carries a stray apostrophe cannot be pasted anywhere.
  • In the activity export a leading + or - is always prefixed, phone-shaped or not. Actions and target names there are free text somebody typed.
  • This is why you should never compare an exported cell to a literal without trimming. '=HYPERLINK(...) in a file was =HYPERLINK(...) in a tag.

Worked example: a month-end file you can defend

You invoice buyers on the first and pay publishers on the fifth. A report is live — it follows any conversion or adjustment made later — so the report you read on the first will not be the report you read on the tenth.

  1. Set the window as instants with your own offset, not bare dates: from=2026-09-01T05:00:00Z, to=2026-10-01T05:00:00Z for a workspace in Chicago. This is the single most common cause of a figure that will not reconcile.
  2. Set tz to the same zone, so days bucket on the calendar your buyer invoices on.
  3. Export the report grouped by buyer for what you bill, and by publisher for what you pay. Two files, two conversations.
  4. Export the call log over the same window and filters. It is the line-level evidence behind both, and it is the file a buyer asks for when they dispute a total.
  5. Keep all three files. When a call is corrected on the eighth, the report changes and your invoice does not — and the export is the record of what the figures were when you raised it.
  6. Check the activity log afterwards: each export is an entry with its filter and its row count, which is how you prove later which file came from which query.

Next steps