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 is a container, not a destination: a buyer owns one or more targets, and the calls go to those. 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.
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 |
/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. |
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
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,
"targets": {
"total": 4,
"active": 3
},
"today": {
"calls": 88,
"connected": 66,
"converted": 41,
"connectRate": 0.75,
"conversionRate": 0.6212,
"revenueCents": 172200,
"payoutCents": 112750
},
"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,
"targets": {
"total": 4,
"active": 3
},
"today": {
"calls": 88,
"connected": 66,
"converted": 41,
"connectRate": 0.75,
"conversionRate": 0.6212,
"revenueCents": 172200,
"payoutCents": 112750
},
"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. A buyer is created with no targets; add at least one before any call can reach it. 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 |
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",
"concurrencyCap": 12,
"dailyCap": 150,
"externalRef": "CRM-4471"
}'Responses
- 201The buyer that was created, with its counters at zero.
{
"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,
"targets": {
"total": 0,
"active": 0
},
"today": {
"calls": 0,
"connected": 0,
"converted": 0,
"connectRate": null,
"conversionRate": null,
"revenueCents": 0,
"payoutCents": 0
},
"createdAt": "2026-09-20T18:50:11.000Z",
"updatedAt": "2026-09-20T18:50:11.000Z"
}
}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. |
| 402 | plan_upgrade_required | The workspace already holds as many buyers as its plan allows. details carries the limit and the current count. |
| 409 | conflict | Another buyer of yours that is not archived already has that name. details.name says so. |
| 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. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 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,
"targets": {
"total": 4,
"active": 3
},
"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. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 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,
"targets": {
"total": 4,
"active": 3
},
"today": {
"calls": 88,
"connected": 66,
"converted": 41,
"connectRate": 0.75,
"conversionRate": 0.6212,
"revenueCents": 172200,
"payoutCents": 112750
},
"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. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No buyer with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
