API reference
Buyers
Read the buyers you sell calls to, their caps and their live counters; create, edit, pause and archive them through the console.
A buyer is a company you sell calls to. It has one destination — its phone, or its dashboard — which is a target of its own (primaryTargetId) made with the buyer in one save, and it may own further targets. What a buyer adds on top is a set of caps that count across every target it owns, and one switch that takes all of them out of routing at once. The concepts are in Buyers and A buyer's destination.
Two surfaces
| Endpoint | Surface | Credential |
|---|---|---|
GET /api/v1/buyers | REST API | API key with buyers:read |
GET /api/v1/buyers/:id | REST API | API key with buyers:read |
POST /api/tracking/buyers | Console | Session token |
PATCH /api/tracking/buyers/:id | Console | Session token |
POST /api/tracking/buyers/:id/status | Console | Session token |
GET, PATCH /api/tracking/buyers/:id/destination | Console | Session token |
POST /api/tracking/buyers/:id/test-call | Console | Session token |
GET, POST /api/tracking/buyers/:id/seats, PATCH, DELETE …/seats/:membershipId, invitation resend and withdraw | Console | Session token |
GET, PATCH /api/tracking/buyers/:id/alerts | Console | Session token |
GET /api/tracking/buyers/:id/numbers, POST …/draft-campaign | Console | Session token |
GET /api/tracking/workspace/checklist | Console | Session token |
/api/tracking/buyers and /api/tracking/buyers/:id. They are the same handlers, so the query and the answer are identical.The buyer object
| Field | Type | Meaning |
|---|---|---|
id | uuid | The buyer's id, used in every path below. |
name | string | What you call the buyer. It is unique in your workspace among buyers that are not archived, case-insensitively. |
company, contactName, contactEmail, contactPhone | string or null | Who they are and who to ring. Contact details are for your team; the platform never writes to them. |
status | string | active, paused or archived. |
timezone | string | An IANA zone, shown beside the buyer's figures. |
concurrencyCap, hourlyCap, dailyCap, monthlyCap | integer | Call caps across every target the buyer owns. 0 is unlimited. |
dailyRevenueCapCents | integer | The buyer's revenue cap for one UTC day, in cents. 0 is unlimited. |
usage | object | The live counters behind those caps: { liveCalls, hour, day, month, total, dayRevenueCents }. |
liveCalls | integer | Calls to this buyer right now. |
externalRef | string or null | Your own reference for the buyer. |
notes | string or null | Free text for your team. |
primaryTargetId | uuid or null | The target behind the buyer's one destination; null for a buyer made before buyers were created with one. |
destination | object | { type, e164, seatName, ready, reason } — type is phone, dashboard or null; reason a code (no_destination, seat_missing, browser_not_allowed, seat_paused, softphone_not_ready, stale_heartbeat, mobile_not_allowed, paused, buyer_paused, suppressed). seatName is always null for an API key. |
seats | object | { active, invited } — the buyer's seat held, and invitations to it still out. Nothing else about a seat reaches a key. |
callsToday, qualifiedToday, revenueTodayCents | integer | On a list row, beside today. |
settings | object | On a read and a create: { revealAfterQualified, allowSeatMobile, allowSeatBrowser }. |
targets | object | On a list row and after an update: { total, active }, counting the buyer's targets that are not archived. |
today | object | On a list row: the day's counts and money, on the workspace's calendar. |
createdAt, updatedAt | timestamp | When the buyer was created and last changed. |
targets array of full target objects, rather than the two counts.Buyer caps and target caps
A buyer's caps and a target's caps are both checked, and the tighter one wins. A target is skipped with the reason capped when its own cap is full and buyer_capped when the buyer's is, so a waterfall always says which. Both sets of counters roll on UTC boundaries — the clock hour, the date, the first of the month — whatever time zone your workspace keeps, so a cap is a rate limit rather than a business day.
0means unlimited on every cap in this API. It does not mean "take no calls" — that is whatpausedis for.- A buyer's counters are reset on the buyer. Resetting a target's caps deliberately leaves the buyer's counters alone, so clearing one target cannot quietly open the buyer's whole budget.
dailyRevenueCapCentsis counted at settlement, whatever a target'scapCountsOnsays about calls.
Endpoints
- GET
/api/v1/buyersList buyers - GET
/api/v1/buyers/:idRead a buyer - POST
/api/tracking/buyersConsole API · Create a buyer - PATCH
/api/tracking/buyers/:idConsole API · Update a buyer - POST
/api/tracking/buyers/:id/statusConsole API · Change a buyer's status - GET
/api/tracking/buyers/:id/destinationConsole API · Read a buyer's destination - PATCH
/api/tracking/buyers/:id/destinationConsole API · Change a buyer's destination - POST
/api/tracking/buyers/:id/test-callConsole API · Send a test call - GET
/api/tracking/buyers/:id/seatsConsole API · Read a buyer's seat - POST
/api/tracking/buyers/:id/seatsConsole API · Invite someone to a buyer's seat - PATCH
/api/tracking/buyers/:id/seats/:membershipIdConsole API · Change a seat's switches - DELETE
/api/tracking/buyers/:id/seats/:membershipIdConsole API · Remove a buyer's seat - POST
/api/tracking/buyers/:id/seats/invitations/:invitationId/resendConsole API · Resend a seat invitation - DELETE
/api/tracking/buyers/:id/seats/invitations/:invitationIdConsole API · Withdraw a seat invitation - GET
/api/tracking/buyers/:id/alertsConsole API · Read a buyer's SMS alerts - PATCH
/api/tracking/buyers/:id/alertsConsole API · Change a buyer's SMS alerts - GET
/api/tracking/buyers/:id/numbersConsole API · Numbers that reach a buyer - POST
/api/tracking/buyers/:id/draft-campaignConsole API · Draft a campaign for a buyer - GET
/api/tracking/workspace/checklistConsole API · The setup checklist
List buyers
/api/v1/buyersEvery buyer in your workspace, filtered and paged, each with its caps, its live counters, a count of its targets and the day so far. Archived buyers are left out unless you ask for them by name.
Query parameters
| Name | Type | Description |
|---|---|---|
qOptional | string | Matches name, company, contactName or externalRef, case-insensitively, anywhere in the value.up to 120 characters |
statusOptional | enum | One status.active, paused, archived · default: every status except archived |
pageOptional | integer | Page number, from 1.default 1 |
pageSizeOptional | integer | Rows per page.default 25 · at most 200; a larger value is clamped rather than refused |
sortOptional | enum | The field to sort by. An unknown value falls back to the default rather than failing.createdAt, updatedAt, name, status · default createdAt |
dirOptional | enum | Sort direction.asc, desc · default desc |
Example request
curl "https://api.buy3.io/api/v1/buyers?status=active&pageSize=25" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200A page of buyer objects.
{
"data": [
{
"id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health",
"company": "Acme Health Insurance LLC",
"contactName": "Dana Reyes",
"contactEmail": "dana@example.com",
"contactPhone": "+1 555 0142",
"status": "active",
"timezone": "America/Chicago",
"concurrencyCap": 12,
"hourlyCap": 0,
"dailyCap": 150,
"monthlyCap": 0,
"dailyRevenueCapCents": 0,
"usage": {
"liveCalls": 3,
"hour": 9,
"day": 88,
"month": 1642,
"total": 11204,
"dayRevenueCents": 369600
},
"liveCalls": 3,
"externalRef": "CRM-4471",
"notes": null,
"primaryTargetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatName": null,
"ready": true,
"reason": null
},
"seats": {
"active": 1,
"invited": 0
},
"targets": {
"total": 4,
"active": 3
},
"today": {
"calls": 88,
"connected": 66,
"converted": 41,
"connectRate": 0.75,
"conversionRate": 0.6212,
"revenueCents": 172200,
"payoutCents": 112750
},
"callsToday": 88,
"qualifiedToday": 41,
"revenueTodayCents": 172200,
"createdAt": "2026-06-18T10:04:00.000Z",
"updatedAt": "2026-09-19T11:02:44.000Z"
}
],
"page": 1,
"pageSize": 25,
"total": 4,
"totalPages": 1
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type, or status is not one of the three. details names the field. |
| 401 | missing_key, invalid_key | No bearer key was sent, or the key is malformed, unknown, or revoked with its grace period over. See Authentication. |
| 402 | subscription_inactive, plan_upgrade_required | The workspace's subscription is not active, or its plan does not include call tracking and API access. |
| 403 | insufficient_scope, ip_not_allowed | The key does not hold buyers:read, or the request came from an address outside the key's allowlist. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Read a buyer
/api/v1/buyers/:idOne buyer, and its targets in full — every target it owns that is not archived, oldest first. This is the request to make before changing a cap: it shows the buyer's counters and each target's beside them, so you can see which limit a call is actually meeting. An external RTB target in targets is blanked the same way it is in the Targets group: the bid header values and the body template read •••.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Example request
curl "https://api.buy3.io/api/v1/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200The buyer, and its targets as full target objects.
{
"buyer": {
"id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health",
"company": "Acme Health Insurance LLC",
"contactName": "Dana Reyes",
"contactEmail": "dana@example.com",
"contactPhone": "+1 555 0142",
"status": "active",
"timezone": "America/Chicago",
"concurrencyCap": 12,
"hourlyCap": 0,
"dailyCap": 150,
"monthlyCap": 0,
"dailyRevenueCapCents": 0,
"usage": {
"liveCalls": 3,
"hour": 9,
"day": 88,
"month": 1642,
"total": 11204,
"dayRevenueCents": 369600
},
"liveCalls": 3,
"externalRef": "CRM-4471",
"notes": null,
"primaryTargetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatName": null,
"ready": true,
"reason": null
},
"seats": {
"active": 1,
"invited": 0
},
"targets": {
"total": 4,
"active": 3
},
"today": {
"calls": 88,
"connected": 66,
"converted": 41,
"connectRate": 0.75,
"conversionRate": 0.6212,
"revenueCents": 172200,
"payoutCents": 112750
},
"callsToday": 88,
"qualifiedToday": 41,
"revenueTodayCents": 172200,
"createdAt": "2026-06-18T10:04:00.000Z",
"updatedAt": "2026-09-19T11:02:44.000Z"
},
"targets": [
{
"id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"name": "Acme Health — Dallas floor",
"type": "number",
"kind": "static",
"status": "active",
"externalRef": "acme-dal-01",
"buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"buyer": {
"id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health",
"status": "active"
},
"health": "healthy",
"lastError": null,
"consecutiveFailures": 0,
"suppressedUntil": null,
"revenueCents": 4200,
"durationThresholdSeconds": 90,
"destination": {
"type": "number",
"value": "+12145550188",
"display": "+12145550188"
},
"rtb": null,
"concurrencyCap": 5,
"hourlyCap": 0,
"dailyCap": 40,
"monthlyCap": 0,
"totalCap": 0,
"dailyRevenueCapCents": 0,
"capCountsOn": "connected",
"usage": {
"liveCalls": 2,
"hour": 4,
"day": 31,
"month": 612,
"total": 4188,
"dayRevenueCents": 130200
},
"liveCalls": 2,
"states": [
"TX",
"OK"
],
"timezone": "America/Chicago",
"hoursSchedule": null,
"dedupeWindowSeconds": 86400,
"createdAt": "2026-07-02T16:20:11.000Z",
"updatedAt": "2026-09-19T11:02:44.000Z"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 401 | missing_key, invalid_key | No bearer key was sent, or the key is malformed, unknown, or revoked with its grace period over. See Authentication. |
| 402 | subscription_inactive, plan_upgrade_required | The workspace's subscription is not active, or its plan does not include call tracking and API access. |
| 403 | insufficient_scope, ip_not_allowed | The key does not hold buyers:read, or the request came from an address outside the key's allowlist. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Console API · Create a buyer
/api/tracking/buyersConsole API. This route takes a signed-in owner's or admin's session token, not an API key, and it may change with the console — treat the REST endpoints above as the stable surface. One save, one transaction: the buyer, its destination (a target and the line it rings) and — with invite — an invitation to its dashboard seat, emailed after the save commits. With a phone destination the buyer can take a call at once. Anything destination leaves out comes from your account's defaults for new buyers. How many buyers a workspace may hold depends on its plan; see pricing.
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Request body
| Name | Type | Description |
|---|---|---|
nameRequired | string | What you call the buyer. It must be unique among your buyers that are not archived, case-insensitively.2 to 120 characters |
companyOptional | string | The legal or trading name, when it differs from name.up to 200 characters |
contactNameOptional | string | Who to ring at the buyer.up to 200 characters |
contactEmailOptional | string | An email address. It is stored for your reference; the platform sends the buyer nothing. |
contactPhoneOptional | string | A contact number. This is not where calls go — that is a target.up to 40 characters |
timezoneOptional | string | An IANA zone, used when a person reads the buyer's hours.up to 64 characters · default: the workspace's time zone |
externalRefOptional | string | Your own id for this buyer — an account number in your CRM, say. It is never used for routing.up to 200 characters |
notesOptional | string | Free text for your team.up to 4000 characters |
concurrencyCapOptional | integer | How many of this buyer's calls may be live at once, across every target it owns.0 to 100000 · 0 is unlimited |
hourlyCapOptional | integer | Calls the buyer may take in one UTC clock hour.0 to 1000000 · 0 is unlimited |
dailyCapOptional | integer | Calls in one UTC day.0 to 1000000 · 0 is unlimited |
monthlyCapOptional | integer | Calls in one UTC month.0 to 10000000 · 0 is unlimited |
dailyRevenueCapCentsOptional | integer | Revenue the buyer may generate in one UTC day, in cents. Counted at settlement.0 to 100000000000 · 0 is unlimited |
revenueCentsOptional | integer | What a qualified call on the destination books — books only; the buyer pays you off-platform. Only with a destination.0 to 100000000 |
destinationOptional | object | The buyer's one destination: { type: "phone" | "dashboard", e164, callerIdPolicy, callerIdNumberId, whisperMode, whisperText, ringTimeoutSeconds, thresholdSeconds } — the fields of Change a buyer's destination. Omitted: a buyer with no destination, which routes nowhere until one is saved. |
inviteOptional | object | { email, name? } — an invitation to the buyer's seat. Required when destination.type is dashboard. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/buyers" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Health",
"company": "Acme Health Insurance LLC",
"contactName": "Dana Reyes",
"contactEmail": "dana@example.com",
"dailyCap": 150,
"revenueCents": 4200,
"destination": {
"type": "phone",
"e164": "+12145550188",
"whisperMode": "press_to_accept",
"thresholdSeconds": 90
},
"invite": {
"email": "dana@example.com",
"name": "Dana Reyes"
}
}'Responses
- 201The buyer that was created, with its counters at zero, its
destination(the Destination tab's shape, ornull) and — withinvite— theinvitation.warningssays when your default caller-ID number could not be used and the tracking number was chosen instead. The invitation's plaintexttokenis returned only on a development server.
{
"buyer": {
"id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health",
"company": "Acme Health Insurance LLC",
"contactName": "Dana Reyes",
"contactEmail": "dana@example.com",
"contactPhone": "+1 555 0142",
"status": "active",
"timezone": "America/Chicago",
"concurrencyCap": 12,
"hourlyCap": 0,
"dailyCap": 150,
"monthlyCap": 0,
"dailyRevenueCapCents": 0,
"usage": {
"liveCalls": 0,
"hour": 0,
"day": 0,
"month": 0,
"total": 0,
"dayRevenueCents": 0
},
"liveCalls": 0,
"externalRef": "CRM-4471",
"notes": null,
"primaryTargetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatName": null,
"ready": true,
"reason": null
},
"seats": {
"active": 1,
"invited": 0
},
"targets": {
"total": 0,
"active": 0
},
"today": {
"calls": 0,
"connected": 0,
"converted": 0,
"connectRate": null,
"conversionRate": null,
"revenueCents": 0,
"payoutCents": 0
},
"callsToday": 88,
"qualifiedToday": 41,
"revenueTodayCents": 172200,
"createdAt": "2026-09-20T18:50:11.000Z",
"updatedAt": "2026-09-20T18:50:11.000Z"
},
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatUserId": null,
"seatName": null,
"callerIdPolicy": "fixed",
"callerIdNumberId": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"callerIdNumber": {
"id": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"e164": "+18005550100",
"formatted": "(800) 555-0100",
"ready": true
},
"whisperMode": "press_to_accept",
"whisperText": null,
"ringTimeoutSeconds": 20,
"thresholdSeconds": 90,
"targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"endpointId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"ready": true,
"reason": null,
"changedAt": "2026-09-19T11:02:44.000Z",
"changedBy": {
"name": "Sam Ortiz",
"via": "publisher"
}
},
"invitation": {
"id": "8e7d6c5b-4a39-4827-9615-04938271650a",
"kind": "buyer",
"email": "dana@example.com",
"name": "Dana Reyes",
"status": "pending",
"sentAt": "2026-09-20T18:50:11.000Z",
"expiresAt": "2026-09-27T18:50:11.000Z",
"emailed": true
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A field has the wrong type or is out of range — a cap above its maximum, a name shorter than two characters. details names the field. |
| 400 | bad_request | details keyed destination.*, invite.* or revenueCents: a phone with no number, a dashboard with no invitation, fixed with no number, a revenue with no destination. |
| 402 | plan_upgrade_required | The workspace already holds as many buyers as its plan allows. details carries the limit and the current count. |
| 404 | not_found | The caller-ID number you named is another account's, or cannot be presented (Number not found). |
| 409 | conflict | Another buyer of yours that is not archived already has that name. details.name says so. |
| 409 | invite_pending | An invitation to the same address for this buyer is already out. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Update a buyer
/api/tracking/buyers/:idConsole API. Change any field of the create form. Send only what you are changing; anything you leave out keeps its value. Status has its own route, so an edit can never switch a buyer off by accident. Lowering a cap below what the buyer has already used today does not reverse anything that has happened — it simply means the next call is over the cap and the buyer's targets are skipped until the window rolls.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
nameOptional | string | A new name. It must still be unique among your buyers that are not archived.2 to 120 characters |
companyOptional | string | The legal or trading name, when it differs from name.up to 200 characters |
contactNameOptional | string | Who to ring at the buyer.up to 200 characters |
contactEmailOptional | string | An email address. It is stored for your reference; the platform sends the buyer nothing. |
contactPhoneOptional | string | A contact number. This is not where calls go — that is a target.up to 40 characters |
timezoneOptional | string | An IANA zone, used when a person reads the buyer's hours.up to 64 characters · default: the workspace's time zone |
externalRefOptional | string | Your own id for this buyer — an account number in your CRM, say. It is never used for routing.up to 200 characters |
notesOptional | string | Free text for your team.up to 4000 characters |
concurrencyCapOptional | integer | How many of this buyer's calls may be live at once, across every target it owns.0 to 100000 · 0 is unlimited |
hourlyCapOptional | integer | Calls the buyer may take in one UTC clock hour.0 to 1000000 · 0 is unlimited |
dailyCapOptional | integer | Calls in one UTC day.0 to 1000000 · 0 is unlimited |
monthlyCapOptional | integer | Calls in one UTC month.0 to 10000000 · 0 is unlimited |
dailyRevenueCapCentsOptional | integer | Revenue the buyer may generate in one UTC day, in cents. Counted at settlement.0 to 100000000000 · 0 is unlimited |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"dailyCap": 200,
"notes": "Raised for the Q4 push"
}'Responses
- 200The buyer as it now stands, with its target counts.
todayis not included on an update.
{
"buyer": {
"id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health",
"company": "Acme Health Insurance LLC",
"contactName": "Dana Reyes",
"contactEmail": "dana@example.com",
"contactPhone": "+1 555 0142",
"status": "active",
"timezone": "America/Chicago",
"concurrencyCap": 12,
"hourlyCap": 0,
"dailyCap": 200,
"monthlyCap": 0,
"dailyRevenueCapCents": 0,
"usage": {
"liveCalls": 3,
"hour": 9,
"day": 88,
"month": 1642,
"total": 11204,
"dayRevenueCents": 369600
},
"liveCalls": 3,
"externalRef": "CRM-4471",
"notes": null,
"primaryTargetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatName": null,
"ready": true,
"reason": null
},
"seats": {
"active": 1,
"invited": 0
},
"targets": {
"total": 4,
"active": 3
},
"callsToday": 88,
"qualifiedToday": 41,
"revenueTodayCents": 172200,
"createdAt": "2026-06-18T10:04:00.000Z",
"updatedAt": "2026-09-20T18:52:30.000Z"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A field has the wrong type or is out of range. |
| 400 | bad_request | Nothing to update — the body carried no change. |
| 409 | conflict | The buyer is archived, or the name you sent belongs to another of your buyers. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Change a buyer's status
/api/tracking/buyers/:id/statusConsole API. Move a buyer between active, paused and archived. Pausing writes one row. None of the buyer's targets is edited: each simply becomes ineligible, with the skip reason buyer_paused, until the buyer is resumed — so routing goes back exactly as it was, with no route to rebuild. The response says how many active targets that covers.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
statusRequired | enum | The status to move to.active, paused, archived |
noteOptional | string | Why, for the activity log and for whoever reads it later.up to 500 characters |
Example request
curl -X POST "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/status" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "paused",
"note": "Contract under review"
}'Responses
- 200The buyer, whether anything
changed, and — when it did — how many of its active targets the change affects. A repeat of the same request answerschanged: falseand leavestargetsAffectedout.
{
"buyer": {
"id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health",
"company": "Acme Health Insurance LLC",
"contactName": "Dana Reyes",
"contactEmail": "dana@example.com",
"contactPhone": "+1 555 0142",
"status": "paused",
"timezone": "America/Chicago",
"concurrencyCap": 12,
"hourlyCap": 0,
"dailyCap": 150,
"monthlyCap": 0,
"dailyRevenueCapCents": 0,
"usage": {
"liveCalls": 3,
"hour": 9,
"day": 88,
"month": 1642,
"total": 11204,
"dayRevenueCents": 369600
},
"liveCalls": 3,
"externalRef": "CRM-4471",
"notes": null,
"primaryTargetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatName": null,
"ready": true,
"reason": null
},
"seats": {
"active": 1,
"invited": 0
},
"targets": {
"total": 4,
"active": 3
},
"today": {
"calls": 88,
"connected": 66,
"converted": 41,
"connectRate": 0.75,
"conversionRate": 0.6212,
"revenueCents": 172200,
"payoutCents": 112750
},
"callsToday": 88,
"qualifiedToday": 41,
"revenueTodayCents": 172200,
"createdAt": "2026-06-18T10:04:00.000Z",
"updatedAt": "2026-09-20T18:54:02.000Z"
},
"changed": true,
"targetsAffected": 3
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | status is missing or is not one of the three. |
| 402 | plan_upgrade_required | Restoring an archived buyer would take the workspace past the number of buyers its plan allows. |
| 409 | conflict | You are restoring an archived buyer whose name has since been taken by another buyer. Rename one of them first. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Read a buyer's destination
/api/tracking/buyers/:id/destinationConsole API. The buyer's ONE destination — the Destination tab — and the three buyer settings its seat works with. ready and reason are the router's own answer about the line (see Ready, or why not); changedBy says who saved it last, you or the buyer's own seat.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/destination" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The destination and the buyer settings.
{
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatUserId": null,
"seatName": null,
"callerIdPolicy": "fixed",
"callerIdNumberId": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"callerIdNumber": {
"id": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"e164": "+18005550100",
"formatted": "(800) 555-0100",
"ready": true
},
"whisperMode": "press_to_accept",
"whisperText": null,
"ringTimeoutSeconds": 20,
"thresholdSeconds": 90,
"targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"endpointId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"ready": true,
"reason": null,
"changedAt": "2026-09-19T11:02:44.000Z",
"changedBy": {
"name": "Sam Ortiz",
"via": "publisher"
}
},
"settings": {
"revealAfterQualified": false,
"allowSeatMobile": true,
"allowSeatBrowser": true
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 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. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Change a buyer's destination
/api/tracking/buyers/:id/destinationConsole API. Rewrites the ONE destination in place — phone ↔ dashboard changes the same line, so its caps, history and routes stay — and any of the three buyer settings. Send any subset, flat or as { destination: {…}, settings: {…} }. The buyer row is locked for the save and the last save wins, yours or the seat's. A buyer with no destination yet is given one here, from your account's defaults for anything not sent.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Request body
| Name | Type | Description |
|---|---|---|
typeOptional | enum | Where calls ring.phone, dashboard |
e164Optional | string | Their phone number, E.164. Required for phone; refused for dashboard. |
callerIdPolicyOptional | enum | What their phone shows as the caller. Never the caller's own number.tracking_number, fixed |
callerIdNumberIdOptional | uuid | The number fixed presents: one of yours, active, voice-enabled and wired to the platform. Sent alone it means fixed; null means the tracking number. Anything else is a 404. |
whisperModeOptional | enum | Before the caller is put through. press_to_accept plays on a phone destination only.none, announce, press_to_accept |
whisperTextOptional | string | Your own prompt; one that never names the key has "Press 1 to accept" added.up to 500 characters |
ringTimeoutSecondsOptional | integer | How long one ring lasts.5 to 120 |
thresholdSecondsOptional | integer | Qualifies at: the talk time before a call counts.0 to 7200 |
revealAfterQualifiedOptional | boolean | Show the caller's number once a call qualifies — works together with the seat's canSeeCaller. |
allowSeatMobileOptional | boolean | Let their seat ring their own verified mobile. |
allowSeatBrowserOptional | boolean | Let their seat take calls in the browser — what lets a dashboard destination take calls at all. |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/destination" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"whisperMode": "none",
"revealAfterQualified": true
}'Responses
- 200The destination and settings as saved, with
warningswhen something was changed on the way in.
{
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatUserId": null,
"seatName": null,
"callerIdPolicy": "fixed",
"callerIdNumberId": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"callerIdNumber": {
"id": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"e164": "+18005550100",
"formatted": "(800) 555-0100",
"ready": true
},
"whisperMode": "none",
"whisperText": null,
"ringTimeoutSeconds": 20,
"thresholdSeconds": 90,
"targetId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"endpointId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"ready": true,
"reason": null,
"changedAt": "2026-09-27T09:12:40.000Z",
"changedBy": {
"name": "Sam Ortiz",
"via": "publisher"
}
},
"settings": {
"revealAfterQualified": false,
"allowSeatMobile": true,
"allowSeatBrowser": true
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Nothing to update, or a field is wrong — details is keyed destination.<field> or settings.<field>. |
| 404 | not_found | No such buyer — or a fixed caller ID naming another account's number, or one that cannot be presented (Number not found). |
| 409 | conflict | The buyer is archived. |
| 409 | seat_required | Switching to the dashboard while nobody holds the buyer's seat and no invitation to it is out — nothing could ever ring. |
| 409 | seat_mobile_destination | The save would leave calls ringing the seat's own verified mobile with allowSeatMobile off. details.fields names the switch or destination.e164. |
| 409 | seat_browser_destination | The save would leave the dashboard as the destination with allowSeatBrowser off. details.fields names the switch or destination.type. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Send a test call
/api/tracking/buyers/:id/test-callConsole API. Rings the buyer's destination for real, once — their phone, or their seat's browser — from the buyer's caller-ID number, else your first number that can make a call. Whoever answers hears "This is a test call from <your account>" and is hung up. The request waits for the end (at most the ring time plus 30 seconds). Not a call: no call row, no charge, nothing in reports; it is audited on the buyer with its outcome. Once per buyer per minute. No body.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/test-call" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200
outcomeisanswered,no_answer,busy,declined,failedortimeout;causeis the carrier's hang-up cause or ours;detaila sentence. A voicemail box that answers readsanswered.
{
"outcome": "answered",
"rangSeconds": 4,
"cause": null,
"detail": "Acme Health's phone answered and heard the test message."
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 409 | buyer_archived | The buyer is archived. |
| 409 | no_destination | The buyer has no destination to ring. |
| 409 | seat_not_ready | The destination is the dashboard and a real call would not ring the browser now. details.reason is the reason code: browser_not_allowed, seat_missing, seat_busy, seat_paused, softphone_not_ready or stale_heartbeat. |
| 409 | mobile_not_allowed | The destination is the seat's own verified mobile while allowSeatMobile is off — a real call would skip it too. |
| 409 | no_caller_id_number | You have no number the call can be placed from. |
| 429 | rate_limited | A test to this buyer in the last minute. details.retryAfterSeconds says how long to wait. |
| 503 | telephony_not_configured, webhook_unreachable | Telephony is not set up, or the carrier could not report back. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Read a buyer's seat
/api/tracking/buyers/:id/seatsConsole API. The buyer's one seat and its invitation. seat.delivery is worked out from the destination, never stored: browser when the dashboard is bound to this login, mobile when the destination is the mobile this login proved, null for anything else (a number you typed — the console reads that as "their phone"). invitation is the one that is out, or — while nobody holds the seat — the newest that lapsed or was declined.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/seats" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200
{ seat, invitation }, either of which may benull.
{
"seat": {
"membershipId": "4b3a2918-0f7e-4d6c-9b5a-493827160514",
"userId": "7c6b5a49-3827-4160-9f5e-4d3c2b1a0f9e",
"name": "Dana Reyes",
"email": "dana@example.com",
"status": "active",
"canSeeCaller": false,
"canEditDestination": true,
"delivery": null,
"createdAt": "2026-09-21T15:02:11.000Z"
},
"invitation": null
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 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. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Invite someone to a buyer's seat
/api/tracking/buyers/:id/seatsConsole API. Emails an invitation to the buyer's dashboard, after the save commits. One seat per buyer: while a seat is held or an invitation to it is out — to any address — a second is refused. The plaintext token (with a note) comes back only on a development server, never in production.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Request body
| Name | Type | Description |
|---|---|---|
emailRequired | string | Who to invite. Only a login with this address can accept. |
nameOptional | string | Their name, for the email and the Seats tab.up to 200 characters |
Example request
curl -X POST "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/seats" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "dana@example.com",
"name": "Dana Reyes"
}'Responses
- 201The invitation, and whether the email went out.
{
"seat": null,
"invitation": {
"id": "8e7d6c5b-4a39-4827-9615-04938271650a",
"email": "dana@example.com",
"name": "Dana Reyes",
"status": "pending",
"sentAt": "2026-09-27T09:00:00.000Z",
"expiresAt": "2026-10-04T09:00:00.000Z",
"canResend": true,
"canRevoke": true
},
"emailed": true
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | email is missing or is not an address. |
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 409 | seat_exists | The seat is held (details.membershipId) or an invitation to it is out (details.invitationId, email, expiresAt). |
| 409 | buyer_archived | The buyer is archived. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Change a seat's switches
/api/tracking/buyers/:id/seats/:membershipIdConsole API. The seat's two switches. Audited at high severity on the buyer.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
membershipIdRequired | uuid | The seat's membershipId, from Read a buyer's seat. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Request body
| Name | Type | Description |
|---|---|---|
canSeeCallerOptional | boolean | Can see the caller's number after a call qualifies. Default off; the buyer's revealAfterQualified must be on too. |
canEditDestinationOptional | boolean | Can change their own destination. Default on. |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/seats/4b3a2918-0f7e-4d6c-9b5a-493827160514" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"canSeeCaller": true
}'Responses
- 200The seat as it now stands.
warningssays whencanSeeCalleris on while the buyer'srevealAfterQualifiedis off — the seat still sees only the last four digits.
{
"seat": {
"membershipId": "4b3a2918-0f7e-4d6c-9b5a-493827160514",
"userId": "7c6b5a49-3827-4160-9f5e-4d3c2b1a0f9e",
"name": "Dana Reyes",
"email": "dana@example.com",
"status": "active",
"canSeeCaller": true,
"canEditDestination": true,
"delivery": null,
"createdAt": "2026-09-21T15:02:11.000Z"
},
"invitation": null,
"warnings": [
"Acme Health's \"reveal after qualifying\" is off, so the seat still sees only the last four digits — turn it on in the Destination tab."
]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Nothing to update. |
| 404 | not_found | No such buyer, or no such seat on it. |
| 409 | buyer_archived | The buyer is archived. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Remove a buyer's seat
/api/tracking/buyers/:id/seats/:membershipIdConsole API. Takes the seat away; the login stays a login. A dashboard destination bound to it is unbound in the same transaction and reads seat_missing; a phone destination is left as it is, even the mobile the seat proved; an alert number that was that mobile is cleared. note says what, if anything, you should change next. Allowed on an archived buyer.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
membershipIdRequired | uuid | The seat's membershipId, from Read a buyer's seat. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl -X DELETE "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/seats/4b3a2918-0f7e-4d6c-9b5a-493827160514" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200Who was removed, the destination as it now stands, and whether it was unbound or the alert number cleared.
{
"seat": null,
"invitation": null,
"removed": {
"membershipId": "4b3a2918-0f7e-4d6c-9b5a-493827160514",
"userId": "7c6b5a49-3827-4160-9f5e-4d3c2b1a0f9e",
"name": "Dana Reyes",
"email": "dana@example.com"
},
"destination": {
"type": "phone",
"e164": "+12145550188",
"seatName": null,
"ready": true,
"reason": null
},
"destinationUnbound": false,
"alertsCleared": false,
"note": null
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No such buyer, or no such seat on it. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Resend a seat invitation
/api/tracking/buyers/:id/seats/invitations/:invitationId/resendConsole API. A fresh link and seven fresh days, to the same address, for a pending or expired invitation. The old link stops working.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
invitationIdRequired | uuid | The invitation's id, from Read a buyer's seat. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/seats/invitations/8e7d6c5b-4a39-4827-9615-04938271650a/resend" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The invitation, pending again.
{
"invitation": {
"id": "8e7d6c5b-4a39-4827-9615-04938271650a",
"email": "dana@example.com",
"name": "Dana Reyes",
"status": "pending",
"sentAt": "2026-09-27T09:00:00.000Z",
"expiresAt": "2026-10-04T09:00:00.000Z",
"canResend": true,
"canRevoke": true
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | Another buyer's or account's invitation, or none. |
| 409 | seat_exists | The seat is held, or another invitation to it is out. |
| 409 | buyer_archived | The buyer is archived. |
| 409 | invitation_accepted, invitation_revoked, invitation_declined | The invitation is finished — send a new one. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Withdraw a seat invitation
/api/tracking/buyers/:id/seats/invitations/:invitationIdConsole API. The emailed link stops working at once.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
invitationIdRequired | uuid | The invitation's id, from Read a buyer's seat. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl -X DELETE "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/seats/invitations/8e7d6c5b-4a39-4827-9615-04938271650a" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The invitation, revoked.
{
"invitation": {
"id": "8e7d6c5b-4a39-4827-9615-04938271650a",
"email": "dana@example.com",
"name": "Dana Reyes",
"status": "revoked",
"sentAt": "2026-09-27T09:00:00.000Z",
"expiresAt": "2026-10-04T09:00:00.000Z",
"canResend": false,
"canRevoke": false
},
"revoked": true
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | Another buyer's or account's invitation, or none. |
| 409 | invitation_accepted, invitation_revoked, invitation_declined | The invitation is already finished. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Read a buyer's SMS alerts
/api/tracking/buyers/:id/alertsConsole API. The SMS alerts tab: the switch, the number they come from and go to, whether a text would go out now (ready, reason), and the newest 50 alerts, skipped ones included.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/alerts" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The alert settings and history.
reasonisalerts_off,no_alert_number,invalid_alert_number,opted_out,no_from_numberortexting_not_configuredwhenreadyis false.
{
"smsAlerts": true,
"fromNumberId": null,
"fromNumber": null,
"sender": {
"id": "5f4e3d2c-1b0a-4987-8654-3210fedcba98",
"e164": "+14805550166",
"formatted": "(480) 555-0166",
"chosen": false
},
"fromNumbers": [
{
"id": "5f4e3d2c-1b0a-4987-8654-3210fedcba98",
"e164": "+14805550166",
"formatted": "(480) 555-0166"
}
],
"alertE164": "+12145550117",
"optedOut": false,
"template": "{campaign}: qualified call, {talkTime} talk time, caller {caller}. {link}",
"ready": true,
"reason": null,
"history": [
{
"id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
"callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
"sentAt": "2026-09-27T14:07:02.000Z",
"createdAt": "2026-09-27T14:07:01.000Z",
"campaignName": "Medicare — Inbound",
"callerMasked": "***-***-0142",
"toE164": "+12145550117",
"fromE164": "+14805550166",
"status": "delivered",
"chargedCents": 1,
"reason": null,
"error": null
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 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. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Change a buyer's SMS alerts
/api/tracking/buyers/:id/alertsConsole API. Each field optional. A switch left on with no alert number, a number that replied STOP, or nothing to send from is saved and said in warnings — each such alert is recorded as skipped, with why.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Request body
| Name | Type | Description |
|---|---|---|
smsAlertsOptional | boolean | Text this buyer when one of their calls qualifies. |
alertE164Optional | string | Where the texts go: a US (+1) mobile. You attest it is the buyer's; nobody texts it a code. null clears it. |
fromNumberIdOptional | uuid | Which of your numbers sends them: yours, active, SMS-enabled, not quarantined. null is automatic — your oldest such number. |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/alerts" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"smsAlerts": true,
"alertE164": "+12145550117"
}'Responses
- 200As the read, with
warningswhen a text could not go out now.
{
"smsAlerts": true,
"fromNumberId": null,
"fromNumber": null,
"sender": {
"id": "5f4e3d2c-1b0a-4987-8654-3210fedcba98",
"e164": "+14805550166",
"formatted": "(480) 555-0166",
"chosen": false
},
"fromNumbers": [
{
"id": "5f4e3d2c-1b0a-4987-8654-3210fedcba98",
"e164": "+14805550166",
"formatted": "(480) 555-0166"
}
],
"alertE164": "+12145550117",
"optedOut": false,
"template": "{campaign}: qualified call, {talkTime} talk time, caller {caller}. {link}",
"ready": true,
"reason": null,
"history": [
{
"id": "9d8c7b6a-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
"callId": "0b9f6c1e-52a4-4a0e-9f0a-3d1c7e8b2a11",
"sentAt": "2026-09-27T14:07:02.000Z",
"createdAt": "2026-09-27T14:07:01.000Z",
"campaignName": "Medicare — Inbound",
"callerMasked": "***-***-0142",
"toE164": "+12145550117",
"fromE164": "+14805550166",
"status": "delivered",
"chargedCents": 1,
"reason": null,
"error": null
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Nothing to update, or details.alertE164 — not a US mobile. |
| 404 | not_found | No such buyer — or details.fromNumberId: a number that is not yours or cannot send texts (one answer for all). |
| 409 | buyer_archived | The buyer is archived. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Numbers that reach a buyer
/api/tracking/buyers/:id/numbersConsole API. Your numbers whose campaign has a switched-on route to one of this buyer's targets — worked out from the routing plans, never stored on the number — and the campaigns that reach the buyer. After buying a number, assign it to one of campaigns; when there are none, draft one with the next endpoint.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/numbers" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200
data: the numbers (each with its campaign and every buyer itreaches), at most 500, by number.campaigns:{ id, name, status }of each campaign that reaches the buyer.
{
"data": [
{
"id": "0c4d2e1f-9a8b-4c7d-8e6f-5a4b3c2d1e0f",
"e164": "+18005550100",
"formatted": "(800) 555-0100",
"status": "active",
"role": "tracking",
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Medicare — Inbound",
"status": "live"
},
"voiceEnabled": true,
"smsEnabled": false,
"callerIdReady": true,
"reaches": [
{
"buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"name": "Acme Health"
}
],
"…": "…"
}
],
"campaigns": [
{
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Medicare — Inbound",
"status": "live"
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 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. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Draft a campaign for a buyer
/api/tracking/buyers/:id/draft-campaignConsole API. A draft campaign whose routing plan is one route: this buyer's destination, priority 1. Setting it live stays the campaign's own step, behind its usual checks. Counts against your plan's campaign allowance.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The buyer's id, as returned by List buyers. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Request body
| Name | Type | Description |
|---|---|---|
nameOptional | string | The campaign's name.1 to 120 characters · default "<buyer name> calls" |
verticalIdOptional | string | A vertical you can use; default the one your newest campaign uses. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/buyers/f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b/draft-campaign" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Health calls"
}'Responses
- 201The campaign, in its detail shape with its setup checklist.
{
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Acme Health calls",
"status": "draft",
"kind": "tracking",
"…": "…"
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No buyer with that id in your workspace. A malformed id, an id that does not exist and another workspace's buyer all answer exactly the same, before any query runs. |
| 402 | plan_upgrade_required | Your plan's campaign allowance is full. |
| 409 | conflict | The buyer is archived. |
| 409 | no_destination | The buyer has no destination to route to. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | subscription_inactive | The workspace's subscription is not active, and this is a write. details carries status, planId and agencyId. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · The setup checklist
/api/tracking/workspace/checklistConsole API. The five steps Overview shows until the first qualified call, each read from what the account holds — nothing is ticked by hand. href is a path under the console, like an alert's to. complete is the last step: once a call qualifies, the checklist is done for good.
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. Only needed when you hold an owner or admin seat on more than one. |
Example request
curl "https://api.buy3.io/api/tracking/workspace/checklist" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN"Responses
- 200The steps, the next one to do, and whether it is complete.
{
"steps": [
{
"key": "number",
"label": "Buy a number",
"done": true,
"href": "numbers",
"to": "numbers"
},
{
"key": "campaign",
"label": "Create a campaign",
"done": true,
"href": "campaigns",
"to": "campaigns"
},
{
"key": "buyer",
"label": "Add a buyer",
"done": true,
"href": "buyers",
"to": "buyers"
},
{
"key": "route",
"label": "Route a campaign to a buyer",
"done": false,
"href": "campaigns/6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d?tab=routing",
"to": "campaigns/6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d?tab=routing"
},
{
"key": "first_call",
"label": "Take the first qualified call",
"done": false,
"href": "calls",
"to": "calls"
}
],
"nextStep": "route",
"complete": false
}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. |
| 404 | not_found | You hold no owner or admin seat to act from — an agent seat, a buyer seat or no seat at all — or you named a workspace with agencyId that is not yours. The same answer as a workspace that does not exist. |
