API reference
Activity
Read and export the workspace activity log: every configuration change, who or what made it, and the values before and after.
The Activity API reads the workspace's activity log: who changed what, when, from where, and from what value to what value. It is what you reach for when a campaign stopped routing overnight, when a buyer disputes a cap, or when an integration started writing things nobody asked for.
It is read-only by construction. Entries are written by the platform as changes happen; the table itself refuses updates, and deletes outside the retention job. There is no route here that writes one, because there is no way to write one.
A console surface
All three endpoints are the console's own, under /api/tracking, called with the session token of a signed-in owner or admin. No API key can read the log — including the key whose own actions are in it.
https://api.buy3.io/api/tracking/activity- Every request may carry an optional
agencyId(a UUID, in the query string or the body). Leave it out when you administer one workspace. - All three are reads, so a workspace whose subscription has lapsed can still read and export its own history.
- Entries are newest first, and the list and the export take exactly the same filters — so exporting what is on screen is the same query string with a different path.
An entry
| Field | Type | Meaning |
|---|---|---|
id | string | A large integer, sent as a string. |
at | ISO 8601 | When it happened. |
kind | string | The noun the entry is about — see Kinds. |
action | string | A sentence in the past tense: "changed the routing plan of Medicare — Inbound". |
severity | string | low, medium or high. |
actor | string | A person's name, System, or an API key's prefix. |
actorKind | string | user, api_key, publisher_member, system or staff. |
actorRole | string | The seat the actor held. |
actorUserId | uuid | null | The person. null for a staff actor. |
apiKeyId | uuid | null | The key, when a key did it. |
target | string | The label of the thing that changed, falling back to its id, then to an empty string. |
targetType, targetId | string | null | What changed, by type and id. |
changes | object | null | { field: [before, after] }. A secret reads ["•••", "•••"] — that it changed, never to what. |
fields | string[] | The field names touched or read. Never values. |
metadata | object | null | Extra facts about the action. null for a staff actor. |
ip | string | null | Where the request came from. null for a staff actor. |
requestId | string | null | The id of the request that made the change — the same id in that reply's error body, and on the matching row of an API key's request log. |
Kinds
Every entry is filed under the noun it is about, so "everything that happened to my targets" is one filter. A status change is filed under the thing whose status changed, with changes.status on it, rather than under some generic kind of its own.
kind | Written when |
|---|---|
workspace | The workspace's own settings changed, such as its time zone. |
campaign | A campaign was created, edited or had its status changed; a tag filter was added, changed or removed. |
routing | A campaign's routing plan was saved — from the console or through the REST API. |
buyer | A buyer was created, edited or had its status changed. |
target | A target was created, edited or had its status changed; its cap counters were reset; it was tested. |
publisher | A lane was opened onto a campaign, or its payout terms changed. |
key | A publisher's routing key was issued or revoked with a lane, the single-step ping URL was switched on or off, or a key was widened onto the auction surface. |
number | A number was bought, assigned, moved, relabelled, or a release was requested. |
pool | A number pool was created, edited, paused, resumed or deleted. |
blocked_caller | A caller was blocked or unblocked. |
call | A conversion was marked, tags were edited, or the books were adjusted — by a person or by an API key. |
webhook | An endpoint was created, edited, paused, resumed or deleted; a test was sent; deliveries were replayed; a secret was rotated. Also the dispatcher marking an endpoint degraded, with system as the actor. |
api_key | A key was created, edited, rotated or revoked. |
export | A CSV left the building: calls, a report, pings, or this log. |
GET /activity/summary returns this list as trackingKinds. A workspace's log can also hold kinds written by other parts of the platform about the same account — billing, wallet, publisher-portal seats — so build a filter menu from trackingKinds first and then add whatever kinds counts.What is never in an entry
- A secret's value. A rotated signing secret or key is recorded as
["•••", "•••"]. - Field values you did not change.
fieldsis names only. - The platform's own side of an action. Internal metadata — margins, provider costs, staff notes, risk and fraud figures — is stripped on the way out, by key, at any depth.
- Another workspace's rows. Every read is pinned to your workspace before any filter is applied.
Endpoints
- GET
/api/tracking/activityList activity - GET
/api/tracking/activity/summarySummarise activity - GET
/api/tracking/activity/export.csvExport activity as CSV
List activity
/api/tracking/activityThe workspace's entries, newest first, in the standard list envelope. Changes nothing and writes nothing.
Query parameters
| Name | Type | Description |
|---|---|---|
kindOptional | string | The kind of thing the entry is about — see Kinds. A kind this workspace has no entries under is not an error: it simply matches nothing.a lowercase slug such as target or api_key · up to 40 characters |
severityOptional | string | How much the change mattered.low, medium or high |
actorKindOptional | string | What kind of actor did it.staff, user, publisher_member, api_key or system |
actorUserIdOptional | uuid | Only entries by this person. |
apiKeyIdOptional | uuid | Only entries by this API key. This is how you answer "what has that integration changed". |
targetTypeOptional | string | Only entries about this type of record, such as campaign, rtb_target, phone_number, webhook_endpoint or api_key.up to 60 characters |
targetIdOptional | string | Only entries about one record. With targetType, this is the history of a single campaign or target.up to 120 characters |
qOptional | string | Matches part of the action sentence. % and _ are ordinary characters here, not wildcards.up to 200 characters |
fromOptional | date | Only entries at or after this moment. |
toOptional | date | Only entries at or before this moment. |
pageOptional | integer | The page to return.from 1, default 1 |
pageSizeOptional | integer | Rows per page. A larger value is clamped, not refused.1–200 · default 25 |
Example request
curl "https://api.buy3.io/api/tracking/activity?kind=target&severity=high&from=2026-09-13T00:00:00Z" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200One page of entries.
{
"data": [
{
"id": "8841207",
"at": "2026-09-20T14:02:51.000Z",
"kind": "target",
"action": "updated the target Acme Health — Dallas floor",
"severity": "medium",
"actor": "Dana Okafor",
"actorKind": "user",
"actorRole": "admin",
"actorUserId": "5e4d3c2b-1a09-4887-b6c5-d4e3f2a1b0c9",
"apiKeyId": null,
"target": "Acme Health — Dallas floor",
"targetType": "rtb_target",
"targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"changes": {
"dailyCap": [
50,
80
],
"status": [
"paused",
"active"
]
},
"fields": [
"dailyCap",
"status"
],
"metadata": null,
"ip": "203.0.113.24",
"requestId": "5d1c0d2e-8a4f-4c0b-9e1d-2f3a4b5c6d7e"
},
{
"id": "8841206",
"at": "2026-09-20T13:58:02.000Z",
"kind": "call",
"action": "marked call CA-482137 converted",
"severity": "medium",
"actor": "b3_live_Qx7m",
"actorKind": "api_key",
"actorRole": "api_key",
"actorUserId": null,
"apiKeyId": "9b2f4c1d-3e5a-4f6b-8c7d-0a1b2c3d4e5f",
"target": "CA-482137",
"targetType": "call",
"targetId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
"changes": {
"converted": [
false,
true
],
"revenueCents": [
0,
5500
]
},
"fields": [
"converted",
"revenueCents"
],
"metadata": {
"source": "api"
},
"ip": "203.0.113.24",
"requestId": "1f0a9b8c-7d6e-4f50-a1b2-c3d4e5f60718"
}
],
"page": 1,
"pageSize": 25,
"total": 2,
"totalPages": 1
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | kind is not a lowercase slug, severity or actorKind is not one of the allowed words, an id filter is not a UUID, or from or to is not a date. 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 | An agencyId that is not a workspace you administer answers the same 404. |
Summarise activity
/api/tracking/activity/summaryCounts over a window, for the tiles and the filter menus above the list: how many entries, how many carried a before and after, and the breakdown by severity, kind and actor. It also returns the vocabularies a menu can offer before the window has any entries in it at all. Changes nothing.
Query parameters
| Name | Type | Description |
|---|---|---|
daysOptional | integer | How many days back to count.1–365 · default 7 |
Example request
curl "https://api.buy3.io/api/tracking/activity/summary?days=30" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200
kindsandactorKindssay what the workspace has in the window;trackingKinds,knownKindsandknownActorKindssay what a menu may offer.actorsholds at most 50, busiest first.
{
"days": 7,
"since": "2026-09-13T14:22:09.412Z",
"total": 412,
"configChanges": 188,
"severity": {
"low": 210,
"medium": 172,
"high": 30
},
"kinds": {
"target": 96,
"campaign": 54,
"call": 180,
"webhook": 42,
"api_key": 6,
"export": 34
},
"actorKinds": {
"user": 318,
"api_key": 82,
"system": 12
},
"actors": [
{
"name": "Dana Okafor",
"kind": "user",
"role": "admin",
"entries": 204
},
{
"name": "b3_live_Qx7m",
"kind": "api_key",
"role": "api_key",
"entries": 82
}
],
"trackingKinds": [
"workspace",
"campaign",
"routing",
"buyer",
"target",
"publisher",
"key",
"number",
"pool",
"blocked_caller",
"call",
"webhook",
"api_key",
"export"
],
"knownKinds": [
"billing",
"wallet",
"member",
"campaign",
"target",
"call",
"webhook",
"api_key",
"export"
],
"knownActorKinds": [
"staff",
"user",
"publisher_member",
"api_key",
"system"
]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | days is not an integer between 1 and 365. |
| 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 | An agencyId that is not a workspace you administer answers the same 404. |
Export activity as CSV
/api/tracking/activity/export.csvThe filtered log as a file — the same filters as the list, so this is "export what I am looking at". It writes its own entry before it answers (export, medium severity) with the filter that produced it, so the trail says what left the building and not merely that something did. That entry is the only write on this page. It is still a read for gating purposes, so a lapsed workspace can export its history.
Query parameters
| Name | Type | Description |
|---|---|---|
kindOptional | string | The kind of thing the entry is about — see Kinds. A kind this workspace has no entries under is not an error: it simply matches nothing.a lowercase slug such as target or api_key · up to 40 characters |
severityOptional | string | How much the change mattered.low, medium or high |
actorKindOptional | string | What kind of actor did it.staff, user, publisher_member, api_key or system |
actorUserIdOptional | uuid | Only entries by this person. |
apiKeyIdOptional | uuid | Only entries by this API key. This is how you answer "what has that integration changed". |
targetTypeOptional | string | Only entries about this type of record, such as campaign, rtb_target, phone_number, webhook_endpoint or api_key.up to 60 characters |
targetIdOptional | string | Only entries about one record. With targetType, this is the history of a single campaign or target.up to 120 characters |
qOptional | string | Matches part of the action sentence. % and _ are ordinary characters here, not wildcards.up to 200 characters |
fromOptional | date | Only entries at or after this moment. |
toOptional | date | Only entries at or before this moment. |
limitOptional | integer | The most entries to write. Narrow the filter rather than raising this if a file is cut off.1–50000 · default 10000 |
Example request
curl "https://api.buy3.io/api/tracking/activity/export.csv?kind=target&from=2026-03-01T00:00:00Z&limit=50000" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-o activity.csvResponses
- 200
Content-Type: text/csv; charset=utf-8, sent as an attachment namedactivity-<YYYY-MM-DD>.csv. UTF-8 with a byte-order mark, RFC 4180 quoting, and a leading apostrophe on any cell a spreadsheet would read as a formula — the rules are in The file.changesis flattened into one readable cell,field: before → after; …, andfieldsis a space-separated list.
id,at,severity,kind,actor,actorKind,action,targetType,target,targetId,changes,fields,ip,requestId
8841207,2026-09-20T14:02:51.000Z,medium,target,Dana Okafor,user,updated the target Acme Health — Dallas floor,rtb_target,Acme Health — Dallas floor,a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d,dailyCap: 50 → 80; status: paused → active,dailyCap status,203.0.113.24,5d1c0d2e-8a4f-4c0b-9e1d-2f3a4b5c6d7eErrors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type, or limit is outside 1–50000. A bad query is refused before anything is written to the log. |
| 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 | An agencyId that is not a workspace you administer answers the same 404. |
