API reference
Targets
Read your targets and their live cap counters, change what a target is worth and how much it may take, pause and resume it, and reset its counters.
A target is one destination a buyer receives calls on: a phone number, a SIP address, or a bidding endpoint that prices each call as it arrives. Targets are what a routing plan orders, so this is the group an integration reaches for to pace spend, open and close a buyer's floor by the hour, or stop a target that is having a bad day. The concepts are in Targets.
Two surfaces
Five routes are on the REST API, taken with an API key. Creating a target, editing it in full, moving it through its whole status machine and testing it are the console's own routes, taken with a signed-in owner's or admin's session token. Each endpoint below says which credential it takes.
| Endpoint | Surface | Credential |
|---|---|---|
GET /api/v1/targets | REST API | API key with targets:read |
GET /api/v1/targets/:id | REST API | API key with targets:read |
PATCH /api/v1/targets/:id | REST API | API key with targets:write |
POST /api/v1/targets/:id/status | REST API | API key with targets:write |
POST /api/v1/targets/:id/reset-caps | REST API | API key with targets:write |
POST /api/tracking/targets | Console | Session token |
PATCH /api/tracking/targets/:id | Console | Session token |
POST /api/tracking/targets/:id/status | Console | Session token |
POST /api/tracking/targets/:id/test | Console | Session token |
The target object
One shape, whatever the target's type. type is fixed when the target is created and can never be changed; kind is derived from it and from the price, and tells you which of the three sorts of target you are looking at.
| Field | Type | Meaning |
|---|---|---|
id | uuid | The target's id, used in every path below. |
name | string | What you call it. |
type | string | number, sip or rtb. Immutable. |
kind | string | static for a number or SIP target with a price, delivery for one priced at 0, external_rtb for a bidder. |
status | string | draft, active, paused or archived. |
externalRef | string or null | Your own reference for this target. It is frozen onto each call that is routed to it, so a later rename does not rewrite old records. |
buyerId, buyer | uuid, object or null | The buyer that owns the target, as { id, name, status }. |
health | string | unknown, healthy, degraded or down, from the target's recent calls and tests. |
lastError, consecutiveFailures, suppressedUntil | string or null, integer, timestamp or null | The circuit breaker. After enough failures in a row a target is switched off for a short time and skipped with the reason suppressed; it is retried automatically. |
revenueCents | integer or null | What a converted call on this target is worth. null on an rtb target, where the bid is the price. |
durationThresholdSeconds | integer | Talk time at which a call converts. 0 converts on connection. |
destination | object | { type, value, display } — a short description of where calls go. For a bidder it is the host of the bid URL. |
endpoint | object or null | The dialling details of a number or sip target. null for a bidder. Secrets are never in it: hasSipSecret and sipSecretLast4 are all that comes back. |
rtb | object or null | The bidding configuration of an rtb target — see How a bidder reads to a key. |
concurrencyCap, hourlyCap, dailyCap, monthlyCap, totalCap, dailyRevenueCapCents | integer | The caps. 0 is unlimited, everywhere. |
capCountsOn | string | When a call counts against the call caps: claimed at the dial, connected when the buyer's line answers, converted at settlement. Revenue against dailyRevenueCapCents is always counted at settlement. |
usage | object | The live counters: { liveCalls, hour, day, month, total, dayRevenueCents }. |
liveCalls | integer | Calls on this target right now. |
states | array of strings | Two-letter state codes the target accepts. Empty accepts every state. |
timezone, hoursSchedule | string, object or null | When the target is open. null is always open. |
dedupeWindowSeconds | integer | A caller who already reached this target inside this window is skipped with the reason duplicate. 0 switches it off. |
today | object | Only on a single-target read: the day's counts and money, on the workspace's calendar. |
routes | array | Only on a single-target read: the campaigns whose routing plan this target is on, each { id, campaignId, campaign, priority, weight, active, revenueOverrideCents }. |
createdAt, updatedAt | timestamp | When the target was created and last changed. |
How a bidder reads to a key
Every response on this page that carries an rtb target has two fields blanked for an API key: each value in rtb.bidHeaders and, when it is set, rtb.bidBodyTemplate. Both come back as •••, and the header names stay so you can see which are in place. In practice those two fields are where a buyer's static token ends up — an X-Api-Key header, or a body template with a token inside it — and a key is a long-lived credential pasted into other people's tools. A key cannot write those fields either, so nothing round-trips through them. A signed-in owner sees them in full in the console.
Three sorts of target
type | Where the call goes | What it is worth |
|---|---|---|
number | A phone number you dial, in E.164. | revenueCents, fixed. A price of 0 makes it a delivery target: the call is handed over but books nothing. |
sip | A SIP address, with a transport, a port and optional credentials. | As above. |
rtb | Nowhere by itself. The buyer's endpoint is asked for a price and a destination as each call arrives. | Whatever the buyer bids, clamped by minBidCents and maxBidCents. revenueCents is null and is refused on a write. |
Endpoints
- GET
/api/v1/targetsList targets - GET
/api/v1/targets/:idRead a target - PATCH
/api/v1/targets/:idUpdate a target - POST
/api/v1/targets/:id/statusPause or resume a target - POST
/api/v1/targets/:id/reset-capsReset a target's cap counters - POST
/api/tracking/targetsConsole API · Create a target - PATCH
/api/tracking/targets/:idConsole API · Update a target in full - POST
/api/tracking/targets/:id/statusConsole API · Move a target through its statuses - POST
/api/tracking/targets/:id/testConsole API · Test a target
List targets
/api/v1/targetsEvery target in your workspace, filtered and paged, each with its caps and its live counters. Archived targets are left out unless you ask for them by name. Use campaignId to read only the targets on one campaign's routing plan.
Query parameters
| Name | Type | Description |
|---|---|---|
qOptional | string | Matches name or externalRef, case-insensitively, anywhere in the value.up to 120 characters |
statusOptional | enum | One status.draft, active, paused, archived · default: every status except archived |
buyerIdOptional | uuid | Only targets owned by this buyer. |
typeOptional | enum | The sort of destination.number, sip, rtb |
kindOptional | enum | Priced, unpriced or bidding.static, delivery, external_rtb |
healthOptional | enum | The target's health badge.unknown, healthy, degraded, down |
campaignIdOptional | uuid | Only targets on this campaign's routing plan. A campaign that is not yours answers 404. |
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/targets?status=active&type=number&pageSize=25" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200A page of target objects with
todaybeside each one.routesis only on a single-target read. Anrtbtarget's header values and body template are blanked.
{
"data": [
{
"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"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"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",
"today": {
"calls": 31,
"connected": 24,
"converted": 14,
"connectRate": 0.7742,
"conversionRate": 0.5833,
"revenueCents": 58800,
"payoutCents": 38500
}
}
],
"page": 1,
"pageSize": 25,
"total": 7,
"totalPages": 1
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type or is not one of the listed values. details names the field. |
| 404 | not_found | campaignId names a campaign that is not a tracking campaign of your workspace. |
| 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 targets:read, or the request came from an address outside the key's allowlist. insufficient_scope carries the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Read a target
/api/v1/targets/:idOne target, with today and with routes — the campaigns whose routing plan it sits on, and at what priority and weight. This is the read to make before changing a cap: usage tells you where the counters stand right now.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Example request
curl "https://api.buy3.io/api/v1/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200The target, its day so far, and every route that points at it.
- 200 (a bidder, read with a key)The same shape for an external RTB target.
rtb.bidHeaderskeeps its names and loses its values, andrtb.bidBodyTemplatereads•••when one is set.
{
"target": {
"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"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"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",
"today": {
"calls": 31,
"connected": 24,
"converted": 14,
"connectRate": 0.7742,
"conversionRate": 0.5833,
"revenueCents": 58800,
"payoutCents": 38500
},
"routes": [
{
"id": "d0c1b2a3-9e8f-4d7c-b6a5-4e3f2d1c0b9a",
"campaignId": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound",
"status": "live",
"routingMode": "priority"
},
"priority": 1,
"weight": 3,
"active": true,
"revenueOverrideCents": null
}
]
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No target with that id in your workspace. A malformed id, an id that does not exist and another workspace's target 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 targets:read, or the request came from an address outside the key's allowlist. insufficient_scope carries the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Update a target
/api/v1/targets/:idChange what a target is worth and how much it may take. Nine fields are writable through a key — revenueCents, concurrencyCap, hourlyCap, dailyCap, monthlyCap, totalCap, dailyRevenueCapCents, hoursSchedule and states — and they are the ones that pace and price a buyer: the two things anybody automates a target for. Send only the fields you are changing; anything you leave out keeps its value. A field the console's form knows and a key may not write is refused, not quietly dropped, so a program that tries to move a destination and reads back 200 cannot exist. The write is recorded in the activity log under the key's name.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Request body
| Name | Type | Description |
|---|---|---|
revenueCentsOptional | integer | What a converted call on this target is worth, in cents. Refused on an rtb target, where the bid is the price.0 to 100000000 |
concurrencyCapOptional | integer | How many of this target's calls may be live at once.0 to 100000 · 0 is unlimited |
hourlyCapOptional | integer | Calls this target 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 |
totalCapOptional | integer | Calls over the target's whole life. It never resets on its own.0 to 100000000 · 0 is unlimited |
dailyRevenueCapCentsOptional | integer | Revenue this target may earn in one UTC day, in cents. Revenue is always counted at settlement, whatever capCountsOn says.0 to 100000000000 · 0 is unlimited |
hoursScheduleOptional | object or null | When the target accepts calls. { tz, days: { mon: [["09:00", "17:00"]], … } }, or null for always open. See Hours and caps.at most 6 windows a day · a schedule that closes all seven days is refused — send null instead |
statesOptional | array of strings or null | Two-letter state codes the target accepts, from the caller's area code. An empty array accepts every state.at most 62 codes |
Example request
curl -X PATCH "https://api.buy3.io/api/v1/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d" \
-H "Authorization: Bearer $BUY3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dailyCap": 75,
"concurrencyCap": 8
}'Responses
- 200The target as it now stands, with
todayandroutes. A bidder's header values and body template are blanked as everywhere else.
{
"target": {
"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"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"rtb": null,
"concurrencyCap": 8,
"hourlyCap": 0,
"dailyCap": 75,
"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-20T18:31:07.000Z",
"today": {
"calls": 31,
"connected": 24,
"converted": 14,
"connectRate": 0.7742,
"conversionRate": 0.5833,
"revenueCents": 58800,
"payoutCents": 38500
},
"routes": [
{
"id": "d0c1b2a3-9e8f-4d7c-b6a5-4e3f2d1c0b9a",
"campaignId": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound",
"status": "live",
"routingMode": "priority"
},
"priority": 1,
"weight": 3,
"active": true,
"revenueOverrideCents": null
}
]
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | field_not_writable | The body named a field only the console may change — the destination, the SIP or bidding credentials, the buyer, the type, capCountsOn, timezone, durationThresholdSeconds, dedupeWindowSeconds, name and so on. details names every refused field and says what a key may change instead. Nothing was written. |
| 400 | validation_error | A writable field has the wrong type or is out of range. details names the field. |
| 400 | bad_request | Nothing to update — the body carried no writable field. Or a cross-field rule failed: revenueCents on an rtb target, an hoursSchedule that closes all seven days, or a state code that is not two letters. |
| 404 | not_found | No target with that id in your workspace. A malformed id, an id that does not exist and another workspace's target all answer exactly the same, before any query runs. |
| 409 | conflict | The target is archived. Restore it in the console first. |
| 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 targets:write, or the request came from an address outside the key's allowlist. insufficient_scope carries the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Pause or resume a target
/api/v1/targets/:id/statusSwitch a target between active and paused, and nothing else. A paused target stays on every routing plan it is on and is skipped while it is paused — no route is deleted, so resuming it puts the calls back exactly where they were. Status is a request of its own, never a field on Update a target, so editing a cap can never switch a buyer off by accident. The change is recorded in the activity log.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Request body
| Name | Type | Description |
|---|---|---|
statusRequired | enum | The status to move to.active or paused |
noteOptional | string | Why, for the activity log and for the person who reads it later.up to 500 characters |
Example request
curl -X POST "https://api.buy3.io/api/v1/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d/status" \
-H "Authorization: Bearer $BUY3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "paused",
"note": "Queue backed up — pausing until 16:00"
}'Responses
- 200The target, and
changed—falsewhen it was already in that status, which is what a retry answers.removedRoutesis always0here: only archiving removes routes, and a key cannot archive.
{
"target": {
"id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"name": "Acme Health — Dallas floor",
"type": "number",
"kind": "static",
"status": "paused",
"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"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"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-20T18:33:41.000Z"
},
"changed": true,
"removedRoutes": 0
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | status is missing, or is not active or paused. |
| 404 | not_found | No target with that id in your workspace. A malformed id, an id that does not exist and another workspace's target all answer exactly the same, before any query runs. |
| 409 | conflict | The target is draft or archived. The API pauses an active target and resumes a paused one; everything else is a console act. details carries the current status and the allowed list. |
| 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 targets:write, or the request came from an address outside the key's allowlist. insufficient_scope carries the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Reset a target's cap counters
/api/v1/targets/:id/reset-capsZero the target's hour, day, month and lifetime call counters and the day's revenue, and start each window from now. The caps themselves are untouched, so is liveCalls, and so are the buyer's counters — a buyer-level cap is reset on the buyer. Use it after a test run, or when a buyer agrees to take more today than their cap allows. It takes no body, and it is recorded in the activity log: the entry keeps the figures that were cleared, because a reset cannot be undone.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Example request
curl -X POST "https://api.buy3.io/api/v1/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d/reset-caps" \
-H "Authorization: Bearer $BUY3_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'Responses
- 200The target with its counters at zero, and
reset— what they read before. Retrying is safe: the second call zeroes what is already zero and answersresetfull of zeroes.
{
"target": {
"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"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"rtb": null,
"concurrencyCap": 5,
"hourlyCap": 0,
"dailyCap": 40,
"monthlyCap": 0,
"totalCap": 0,
"dailyRevenueCapCents": 0,
"capCountsOn": "connected",
"usage": {
"liveCalls": 2,
"hour": 0,
"day": 0,
"month": 0,
"total": 0,
"dayRevenueCents": 0
},
"liveCalls": 2,
"states": [
"TX",
"OK"
],
"timezone": "America/Chicago",
"hoursSchedule": null,
"dedupeWindowSeconds": 86400,
"createdAt": "2026-07-02T16:20:11.000Z",
"updatedAt": "2026-09-20T18:35:02.000Z"
},
"reset": {
"liveCalls": 2,
"hour": 4,
"day": 31,
"month": 612,
"total": 4188,
"dayRevenueCents": 130200
}
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | No target with that id in your workspace. A malformed id, an id that does not exist and another workspace's target 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 targets:write, or the request came from an address outside the key's allowlist. insufficient_scope carries the scopes required and the ones held. |
| 429 | rate_limited | The key is over its per-minute limit. Wait for Retry-After seconds. |
Console API · Create a target
/api/tracking/targetsConsole 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 target and its destination are created together in one request, so there is never a target pointing at nothing. type is chosen here and is immutable afterwards. Fields that belong to a different type are refused rather than ignored. How many targets 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 target.2 to 200 characters |
buyerIdRequired | uuid | The buyer that owns it. It must be yours and must not be archived. |
typeRequired | enum | The sort of destination. Immutable once created.number, sip or rtb |
statusOptional | enum | The status to create it in. Create only — afterwards use the status route.draft, active, paused · default active |
externalRefOptional | string | Your own reference. It is frozen onto each call routed to this target.up to 200 characters |
destinationE164Optional | string | The number to dial, in E.164. Required for a number target. |
callerIdModeOptional | enum | What the buyer sees as the calling number.passthrough, tracking_number, fixed — number targets only |
callerIdE164Optional | string | The number to show. Required when callerIdMode is fixed. |
sendDigitsOptional | string | DTMF to send once the call is answered, for an extension or an IVR.up to 40 characters · digits, *, #, w and , |
sipUriOptional | string | The SIP address. Required for a sip target. The host must resolve to a public address.up to 500 characters · sip:user@host or sips:user@host |
sipTransportOptional | enum | SIP transport.udp, tcp, tls |
sipPortOptional | integer | SIP port.1 to 65535 |
sipAuthTypeOptional | enum | How the SIP endpoint authenticates. none clears any stored secret.none, basic, bearer, api_key_header, hmac_sha256, oauth2_client_credentials, ip_acl, digest |
sipUsernameOptional | string | SIP username.up to 200 characters |
sipRealmOptional | string | SIP realm.up to 200 characters |
sipSecretOptional | string | The SIP secret. Write-only — no response ever returns it, and the activity log records only that it changed. On an edit, sending it blank keeps the stored one.1 to 400 characters |
ringTimeoutSecondsOptional | integer | How long one attempt on this target may ring.5 to 120 · default 20 · number and sip targets only |
revenueCentsOptional | integer | What a converted call is worth. 0 files the target as delivery — calls are handed over and book nothing. Refused on an rtb target.0 to 100000000 |
rtbOptional | object | The bidding configuration of an rtb target. See External RTB targets for what each path means; errors on it are keyed rtb.<field>.bidUrl is required and must pass the public-URL check · bidPath and destinationPath are required, though a preset may supply them · a preset of ringba, retreaver, trackdrive or custom fills blank paths only, and never overrides what you sent |
durationThresholdSecondsOptional | integer | Talk time at which a call on this target converts.0 to 7200 |
concurrencyCapOptional | integer | How many of this target's calls may be live at once.0 to 100000 · 0 is unlimited |
hourlyCapOptional | integer | Calls this target 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 |
totalCapOptional | integer | Calls over the target's whole life. It never resets on its own.0 to 100000000 · 0 is unlimited |
dailyRevenueCapCentsOptional | integer | Revenue this target may earn in one UTC day, in cents. Revenue is always counted at settlement, whatever capCountsOn says.0 to 100000000000 · 0 is unlimited |
hoursScheduleOptional | object or null | When the target accepts calls. { tz, days: { mon: [["09:00", "17:00"]], … } }, or null for always open. See Hours and caps.at most 6 windows a day · a schedule that closes all seven days is refused — send null instead |
statesOptional | array of strings or null | Two-letter state codes the target accepts, from the caller's area code. An empty array accepts every state.at most 62 codes |
capCountsOnOptional | enum | When a call counts against the call caps.claimed, connected, converted |
timezoneOptional | string | An IANA zone for the target's hours.up to 64 characters |
dedupeWindowSecondsOptional | integer | How long a caller who already reached this target is skipped for.0 to 2592000 · 0 switches it off |
Example request
curl -X POST "https://api.buy3.io/api/tracking/targets" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Health — Dallas floor",
"buyerId": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
"type": "number",
"destinationE164": "+12145550188",
"revenueCents": 4200,
"durationThresholdSeconds": 90,
"concurrencyCap": 5,
"dailyCap": 40,
"capCountsOn": "connected",
"states": [
"TX",
"OK"
]
}'Responses
- 201The target that was created, with an empty
routes— add it to a routing plan to start sending it calls.
{
"target": {
"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": "unknown",
"lastError": null,
"consecutiveFailures": 0,
"suppressedUntil": null,
"revenueCents": 4200,
"durationThresholdSeconds": 90,
"destination": {
"type": "number",
"value": "+12145550188",
"display": "+12145550188"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"rtb": null,
"concurrencyCap": 5,
"hourlyCap": 0,
"dailyCap": 40,
"monthlyCap": 0,
"totalCap": 0,
"dailyRevenueCapCents": 0,
"capCountsOn": "connected",
"usage": {
"liveCalls": 0,
"hour": 0,
"day": 0,
"month": 0,
"total": 0,
"dayRevenueCents": 0
},
"liveCalls": 0,
"states": [
"TX",
"OK"
],
"timezone": "America/Chicago",
"hoursSchedule": null,
"dedupeWindowSeconds": 86400,
"createdAt": "2026-07-02T16:20:11.000Z",
"updatedAt": "2026-09-19T11:02:44.000Z",
"routes": []
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A field has the wrong type or is out of range. details names it. |
| 400 | bad_request | A field belongs to a different type than the one you sent, a required field for this type is missing, or a cross-field rule failed. details names each field. |
| 400 | unsafe_url | A rtb.bidUrl or rtb.authTokenUrl is not a public https address. details.rule says which rule it broke. |
| 402 | plan_upgrade_required | The workspace has as many targets as its plan allows. details carries the limit and the current count. |
| 404 | not_found | buyerId is not a buyer of this workspace. |
| 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 target with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Update a target in full
/api/tracking/targets/:idConsole API. The same path as Update a target but with a session token behind it, and every field of the create form is writable — the destination, the SIP or bidding credentials, the buyer and the rest. This is the route to change where a target's calls go. type is tolerated when it states what the target already is and refused when it does not. A URL is re-checked against the public-address rules only when it changes.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
any create fieldOptional | — | Every field of Create a target except status, which has its own route. Cross-field rules are judged on the row as it will be after the change, not as it is now.at least one field |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"destinationE164": "+12145550190",
"ringTimeoutSeconds": 25
}'Responses
- 200The target as it now stands, with
todayandroutes.
{
"target": {
"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": "+12145550190",
"display": "+12145550190"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"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-20T18:40:19.000Z",
"today": {
"calls": 31,
"connected": 24,
"converted": 14,
"connectRate": 0.7742,
"conversionRate": 0.5833,
"revenueCents": 58800,
"payoutCents": 38500
},
"routes": [
{
"id": "d0c1b2a3-9e8f-4d7c-b6a5-4e3f2d1c0b9a",
"campaignId": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound",
"status": "live",
"routingMode": "priority"
},
"priority": 1,
"weight": 3,
"active": true,
"revenueOverrideCents": null
}
]
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error, bad_request, unsafe_url | As for create, plus bad_request with "Nothing to update" when the body carried no change, and a refused type that does not match the target. |
| 409 | conflict | The target 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. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No target with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Move a target through its statuses
/api/tracking/targets/:id/statusConsole API. The whole state machine, not just pause and resume. Activating a target clears its circuit breaker — suppressedUntil and consecutiveFailures go back to nothing on both the target and its endpoint — so a target that was switched off after repeated failures is given a clean start. Archiving removes the target from every routing plan it is on, in the same transaction, and the response counts them: those routes are gone, and restoring the target later does not put them back.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
statusRequired | enum | The status to move to.draft, active, paused, archived |
noteOptional | string | Why, for the activity log.up to 1000 characters |
Example request
curl -X POST "https://api.buy3.io/api/tracking/targets/a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d/status" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "archived",
"note": "Buyer ended the contract"
}'Responses
- 200The target, whether anything
changed, and how many routes archiving removed.
{
"target": {
"id": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"name": "Acme Health — Dallas floor",
"type": "number",
"kind": "static",
"status": "archived",
"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"
},
"endpoint": {
"id": "5b4a3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d",
"mode": "pstn_did",
"destinationE164": "+12145550188",
"callerIdMode": "passthrough",
"callerIdE164": null,
"sendDigits": null,
"sipUri": null,
"sipTransport": null,
"sipPort": null,
"sipAuthType": null,
"sipUsername": null,
"sipRealm": null,
"hasSipSecret": false,
"sipSecretLast4": null,
"ringTimeoutSeconds": 20,
"lastHealthCheckAt": "2026-09-20T09:14:02.000Z"
},
"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-20T18:42:55.000Z"
},
"changed": true,
"removedRoutes": 2
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | status is missing or is not one of the four. |
| 402 | plan_upgrade_required | Restoring an archived target would take the workspace past the number of targets its plan allows. |
| 409 | conflict | You are activating a target whose buyer is archived. Restore the buyer 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 target with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Test a target
/api/tracking/targets/:id/testConsole API. A real check against the buyer, run before you trust a target with live callers. For an rtb target it sends one real bid request through the router's own code and shows you the request, the raw response and how each configured path was read — the fastest way to find a bidPath that points at the wrong field. No auction, bid or call is written, though the target's health counters do move. For a number or sip target it climbs a reachability ladder and stops at the first rung that fails. Nothing is dialled either way. The test is recorded in the activity log.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The target's id, as returned by List targets. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
callerIdOptional | string | A caller's number to put in the bid request. rtb targets.up to 32 characters |
stateOptional | string | A two-letter state for the pretend caller. rtb targets. |
zipOptional | string | A postal code for the pretend caller. rtb targets.up to 12 characters |
tagsOptional | object | Tags to send with the bid request, so a buyer's rules can be exercised. rtb targets. |
campaignIdOptional | uuid | Test as if the call had arrived on this campaign, so the route's maxBidCents and the campaign's ping timeout apply. It must be a tracking campaign of yours. rtb targets. |
levelOptional | enum | Run one rung of the ladder instead of all of them. number and sip targets.static, dns, tcp_tls, sip_options, sip_auth, hours, caps · omitted runs the whole ladder: static for a number; static, dns, tcp_tls and sip_options for SIP |
Example request
curl -X POST "https://api.buy3.io/api/tracking/targets/c7b6a5d4-e3f2-4a1b-8c9d-0e1f2a3b4c5d/test" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"callerId": "+15555550142",
"state": "TX",
"tags": {
"utm_source": "search"
}
}'Responses
- 200 (rtb target)What was sent, what came back, and how it was read.
passedistrueonly when the buyer actually bid. Authorization header values are replaced before the request is shown back to you. - 200 (number or sip target)One entry per rung that ran, in order, stopping at the first failure. A full SIP ladder also refreshes the target's health badge.
{
"test": true,
"type": "rtb",
"passed": true,
"status": "bid",
"request": {
"method": "POST",
"url": "https://bids.example.com/v1/ping",
"headers": {
"Content-Type": "application/json",
"X-Api-Key": "«redacted»"
},
"body": "{\"callerId\":\"+15555550142\",\"state\":\"TX\"}"
},
"response": {
"httpStatus": 200,
"latencyMs": 214,
"body": {
"bidAmount": 38.5,
"phoneNumber": "+12145550190",
"bidTerms": [
{
"callMinDuration": 90
}
]
}
},
"parsed": {
"status": "bid",
"rawBidCents": 3850,
"bidCents": 3850,
"durationSeconds": 90,
"destinationKind": "did",
"destinationSip": null,
"destinationE164": "+12145550190",
"rejectReason": null
},
"note": "The buyer bid 3850 cents and gave a number to send the call to.",
"target": {
"id": "c7b6a5d4-e3f2-4a1b-8c9d-0e1f2a3b4c5d",
"name": "BlueSky Legal — bidder",
"type": "rtb",
"kind": "external_rtb",
"status": "active",
"externalRef": null,
"buyerId": "e5d4c3b2-a1f0-4e9d-8c7b-6a5f4e3d2c1b",
"buyer": {
"id": "e5d4c3b2-a1f0-4e9d-8c7b-6a5f4e3d2c1b",
"name": "BlueSky Legal",
"status": "active"
},
"health": "healthy",
"lastError": null,
"consecutiveFailures": 0,
"suppressedUntil": null,
"revenueCents": null,
"durationThresholdSeconds": 60,
"destination": {
"type": "rtb",
"value": "bids.example.com",
"display": "Bids at bids.example.com"
},
"endpoint": null,
"rtb": {
"preset": "ringba",
"bidUrl": "https://bids.example.com/v1/ping",
"bidMethod": "POST",
"bidContentType": "application/json",
"bidHeaders": {
"X-Api-Key": "•••"
},
"bidBodyTemplate": "•••",
"pingTimeoutMs": 1200,
"authType": "none",
"authUsername": null,
"authHeaderName": null,
"authTokenUrl": null,
"hasAuthSecret": false,
"authSecretLast4": null,
"acceptMode": "http_2xx",
"acceptPath": null,
"acceptEquals": null,
"bidPath": "bidAmount",
"bidUnit": "dollars",
"durationPath": "bidTerms.0.callMinDuration",
"durationOffsetSeconds": 0,
"destinationPath": "phoneNumber",
"destinationKind": "did",
"rejectReasonPath": "rejectReason",
"minBidCents": 0,
"maxBidCents": 0,
"bidMultiplier": null,
"lastBidAt": "2026-09-20T18:19:55.000Z",
"lastWinAt": "2026-09-20T18:12:03.000Z"
},
"concurrencyCap": 10,
"hourlyCap": 0,
"dailyCap": 0,
"monthlyCap": 0,
"totalCap": 0,
"dailyRevenueCapCents": 0,
"capCountsOn": "converted",
"usage": {
"liveCalls": 0,
"hour": 1,
"day": 9,
"month": 188,
"total": 1042,
"dayRevenueCents": 41000
},
"liveCalls": 0,
"states": [],
"timezone": "America/New_York",
"hoursSchedule": null,
"dedupeWindowSeconds": 0,
"createdAt": "2026-08-11T12:00:00.000Z",
"updatedAt": "2026-09-18T08:31:20.000Z"
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The target is an rtb target with no bidUrl configured. |
| 400 | unsafe_url | The bid URL no longer resolves to a public address. It is re-checked at send time, not only when it was saved. |
| 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 target with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
