API reference
Numbers
Read your tracking numbers and what each is attributed to, manage blocked callers from a program, and search, buy, assign and release numbers through the console.
A tracking number is a phone number rented through the platform. It is the front door of a campaign: a caller dials it, and what that number is attributed to — a campaign, and optionally a publisher or a pool — decides which rules the call is judged by and whose payout it earns. This group also covers blocked callers, the list of numbers your campaigns refuse. The concepts are in Buying numbers and Assigning numbers.
Two surfaces
| Endpoint | Surface | Credential |
|---|---|---|
GET /api/v1/numbers | REST API | API key with numbers:read |
GET /api/v1/blocked-callers | REST API | API key with blocklist:write |
POST /api/v1/blocked-callers | REST API | API key with blocklist:write |
DELETE /api/v1/blocked-callers/:id | REST API | API key with blocklist:write |
POST /api/tracking/numbers/search | Console | Session token |
POST /api/tracking/numbers/purchase | Console | Session token |
PATCH /api/tracking/numbers/:id | Console | Session token |
POST /api/tracking/numbers/:id/release | Console | Session token |
blocklist:read is what the list route asks for, and it is not a scope you can put on a key by name. blocklist:write includes it, so the key that manages the list can read it and no other key sees callers' numbers it has no business with.Phone number format
Numbers are always E.164 — a plus sign, the country code and the number, with no spaces or punctuation: +18885550100. The API returns them that way and expects them that way in a body. In a query string, write the leading + as %2B. Two display forms come back beside e164: national is the local spelling, and formatted is whatever the owner set, falling back to national.
The number object
| Field | Type | Meaning |
|---|---|---|
id | uuid | The number's id, used in every path below. |
e164, national, formatted | string | The number, and its two display forms. |
type | string | local, toll_free, mobile, national or shared_cost. |
status | string | active, past_due, suspended, released or draft. |
role | string | What the number is for: unassigned, tracking, or ingress, agent or quarantine when it is doing another feature's job. Only unassigned and tracking numbers can be moved from here. |
label | string or null | Your own name for it — the billboard, the landing page, the radio spot. |
region, locality | string or null | Where the number is from. |
campaign, publisher, pool | object or null | What the number is attributed to, each as { id, name } — the campaign also carries its status. |
webhookStatus | string | Whether the carrier is pointed at the platform yet: pending, ready, failed or skipped. A number that is not ready will not deliver its calls. |
webhookError | string or null | What went wrong when webhookStatus is failed. |
monthlyCents | integer | The number's monthly rent, in cents. See pricing. |
renewsAt | timestamp | When the rent is next billed. Present only when a billing date is known. |
purchasedAt | timestamp or null | When the number was bought. |
calls30d | integer | Calls in the last 30 days. It is 0 on a single-number response, which is not given the statistics. |
lastCallAt | timestamp or null | When the number last took a call. |
What a number can be attributed to
- A campaign. Required for the number to route at all. Clearing it (
campaignId: null) sends the number back tounassignedand takes the publisher and the pool with it. - A publisher, optionally. The publisher must already have a lane on that campaign. Calls on the number then earn that publisher their payout.
- A pool, optionally. A pool belongs to one campaign, so putting a number in a pool implies that campaign, and the number takes on the pool's publisher.
Moving a number to a different campaign drops its publisher and pool unless the same request restates them, because neither is meaningful on the new campaign. The assignment is written as one conditional update, so two administrators moving the same number cannot both succeed: the second is told the number changed under them.
Endpoints
- GET
/api/v1/numbersList numbers - GET
/api/v1/blocked-callersList blocked callers - POST
/api/v1/blocked-callersBlock a caller - DELETE
/api/v1/blocked-callers/:idUnblock a caller - POST
/api/tracking/numbers/searchConsole API · Search for available numbers - POST
/api/tracking/numbers/purchaseConsole API · Buy a number - PATCH
/api/tracking/numbers/:idConsole API · Assign, move or relabel a number - POST
/api/tracking/numbers/:id/releaseConsole API · Release a number
List numbers
/api/v1/numbersEvery number your workspace holds, filtered and paged, with what each is attributed to and how busy it has been. Released numbers are left out unless you ask for them by name. Numbers doing another feature's job are listed — you pay for them — and cannot be assigned from here.
Query parameters
| Name | Type | Description |
|---|---|---|
qOptional | string | Digits match the number itself; text matches the label.up to 40 characters |
statusOptional | enum | One status.active, past_due, suspended, released, draft · default: every status except released |
campaignIdOptional | uuid | Only numbers on this campaign. |
publisherIdOptional | uuid | Only numbers attributed to this publisher. |
poolIdOptional | uuid | Only numbers in this pool. |
assignedOptional | boolean | true for numbers on a campaign, false for unassigned ones. It is ignored when campaignId is sent. |
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, e164, status, purchasedAt · default createdAt |
dirOptional | enum | Sort direction.asc, desc · default desc |
Example request
curl "https://api.buy3.io/api/v1/numbers?assigned=false&pageSize=25" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200A page of number objects with
calls30dandlastCallAt.monthlyCentsis returned on every row and is left out of the example.
{
"data": [
{
"id": "7c0e5a3b-1d2f-4e6a-8b9c-0d1e2f3a4b5c",
"e164": "+18885550100",
"formatted": "(888) 555-0100",
"national": "(888) 555-0100",
"type": "toll_free",
"status": "active",
"role": "tracking",
"label": "Billboard — I-35",
"region": "TX",
"locality": null,
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound",
"status": "live"
},
"publisher": {
"id": "c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b",
"name": "Northwind Media"
},
"pool": null,
"webhookStatus": "ready",
"webhookError": null,
"renewsAt": "2026-10-20T14:00:02.000Z",
"purchasedAt": "2026-07-04T14:00:02.000Z",
"calls30d": 1204,
"lastCallAt": "2026-09-20T18:21:02.000Z"
},
{
"id": "8d1f2e3a-4b5c-4d6e-9f0a-1b2c3d4e5f60",
"e164": "+16195550134",
"formatted": "(619) 555-0134",
"national": "(619) 555-0134",
"type": "local",
"status": "active",
"role": "unassigned",
"label": "Billboard I-5",
"region": "CA",
"locality": "San Diego",
"campaign": null,
"publisher": null,
"pool": null,
"webhookStatus": "ready",
"webhookError": null,
"renewsAt": "2026-10-20T14:00:02.000Z",
"purchasedAt": "2026-07-04T14:00:02.000Z",
"calls30d": 0,
"lastCallAt": null
}
],
"page": 1,
"pageSize": 25,
"total": 12,
"totalPages": 1
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type, or status is not one of the five. 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 numbers: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. |
List blocked callers
/api/v1/blocked-callersThe callers your campaigns refuse, newest first. A block is either workspace-wide — every campaign refuses the caller — or on one campaign. A blocked caller's call ends with the reason caller_blocked and reaches no buyer.
Query parameters
| Name | Type | Description |
|---|---|---|
qOptional | string | Digits match the last ten digits of the number; text matches the reason.up to 40 characters |
campaignIdOptional | uuid | Only blocks on this campaign. |
scopeOptional | enum | Only workspace-wide blocks, or only campaign blocks. It is ignored when campaignId is sent.workspace, campaign |
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 |
Example request
curl "https://api.buy3.io/api/v1/blocked-callers?scope=workspace&pageSize=25" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200A page of blocked callers:
{ id, callerLast10, formatted, scope, campaign, reason, createdAt }.
{
"data": [
{
"id": "4d3c2b1a-0f9e-4d8c-b7a6-5e4f3d2c1b0a",
"callerLast10": "5555550142",
"formatted": "(555) 555-0142",
"scope": "campaign",
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound"
},
"reason": "Repeated abusive calls",
"createdAt": "2026-09-20T19:30:00.000Z"
}
],
"page": 1,
"pageSize": 25,
"total": 31,
"totalPages": 2
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type, or scope is not workspace or campaign. |
| 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 blocklist:write, 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. |
Block a caller
/api/v1/blocked-callersAdd a caller to the blocklist, for the whole workspace or for one campaign. The number is normalised to its last ten digits, so any spelling of the same caller is the same row. Blocking a caller who is already blocked at that scope is not an error here — it answers 200 with the row that is already there and created: false, writes nothing and records nothing. Programs block from a feed, feeds repeat, and a retry after a timeout must not look like a failure: the caller is blocked, which is what was asked for. A block that is genuinely new is recorded in the activity log under the key's name.
Request body
| Name | Type | Description |
|---|---|---|
callerNumberRequired | string | The caller, in any spelling. It is stored as its last ten digits.up to 32 characters · 7 to 15 digits |
campaignIdOptional | uuid or null | Block on one campaign only. Omitted or null blocks across the whole workspace. |
reasonOptional | string | Why, for whoever reads the list later.up to 300 characters |
Example request
curl -X POST "https://api.buy3.io/api/v1/blocked-callers" \
-H "Authorization: Bearer $BUY3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callerNumber": "+15555550142",
"campaignId": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"reason": "Repeated abusive calls"
}'Responses
- 201The caller was not blocked at this scope and now is.
- 200The caller was already blocked at this scope. The existing row is returned and nothing was written.
{
"blockedCaller": {
"id": "4d3c2b1a-0f9e-4d8c-b7a6-5e4f3d2c1b0a",
"callerLast10": "5555550142",
"formatted": "(555) 555-0142",
"scope": "campaign",
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound"
},
"reason": "Repeated abusive calls",
"createdAt": "2026-09-20T19:30:00.000Z"
},
"created": true
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | callerNumber is not a phone number. details.callerNumber says what is expected. |
| 400 | validation_error | callerNumber is missing or too long, or campaignId is not a UUID. |
| 404 | not_found | Campaign not found — campaignId is not a tracking campaign of your workspace. |
| 409 | conflict | The caller's block changed while the request was being saved — somebody unblocked them at the same moment. Send the request again. |
| 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 blocklist:write, 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. |
Unblock a caller
/api/v1/blocked-callers/:idRemove one block. The caller can reach the campaign again on their next call; calls already turned away are not revisited. Removing a campaign block does not lift a workspace-wide block on the same caller, and the other way round — they are separate rows, and a caller can hold both.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The blocked caller's id, as returned by List blocked callers. |
Example request
curl -X DELETE "https://api.buy3.io/api/v1/blocked-callers/4d3c2b1a-0f9e-4d8c-b7a6-5e4f3d2c1b0a" \
-H "Authorization: Bearer $BUY3_API_KEY"Responses
- 200The id that was removed.
{
"deleted": "4d3c2b1a-0f9e-4d8c-b7a6-5e4f3d2c1b0a"
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | Blocked caller not found — no block with that id in your workspace. A malformed id answers the same. |
| 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 blocklist:write, 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 · Search for available numbers
/api/tracking/numbers/searchConsole API. These routes take a signed-in owner's or admin's session token, not an API key, and they may change with the console. This asks the carrier what is available. It is a POST because the filters are a form, not because it writes anything — nothing is written, and no number is held for you. Each result carries a quoteToken, which is what Buy a number is given: it is valid for fifteen minutes, and it is bound to the signed-in user and to those exact digits.
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 |
|---|---|---|
countryCodeOptional | string | Two-letter country code.default US |
areaCodeOptional | string or integer | The area code to search in.1 to 8 digits |
stateOptional | string | A state or region.up to 100 characters |
localityOptional | string | A town or city.up to 100 characters |
rateCenterOptional | string | A carrier rate centre.up to 100 characters |
containsOptional | string or integer | Digits the number must contain. * stands for any digit.up to 15 characters |
startsWithOptional | string or integer | Digits the number must start with, with an optional leading +.up to 16 characters |
endsWithOptional | string or integer | Digits the number must end with.up to 15 characters |
numberTypeOptional | enum | The sort of number.local, toll_free, mobile, national, shared_cost |
featuresOptional | string or array of strings | Capabilities the number must have.at most 8 · voice, sms, mms, fax, emergency, hd_voice, international_sms, local_calling |
limitOptional | integer | How many results to return.1 to 250 · default 20 |
bestEffortOptional | boolean | Let the carrier widen the search when an exact match is scarce. |
quickshipOptional | boolean | Only numbers the carrier can activate immediately. |
reservableOptional | boolean | Only numbers the carrier will hold. |
excludeHeldNumbersOptional | boolean | Leave out numbers somebody is already holding. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/numbers/search" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"areaCode": "619",
"numberType": "local",
"features": [
"voice"
],
"limit": 20
}'Responses
- 200The carrier's results, the platform's flat tariff as
price: { setupCents, monthlyCents }, and what the carrier said about the search. Each result also carriessetupCentsandmonthlyCents; those three money fields are left out of this example — the figures are on pricing.
{
"results": [
{
"e164": "+16195550134",
"formatted": "(619) 555-0134",
"type": "local",
"countryCode": "US",
"region": "CA",
"locality": "San Diego",
"capabilities": {
"voice": true,
"sms": true,
"mms": false
},
"quoteToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.sample.token"
}
],
"meta": {
"total": 42,
"bestEffort": false
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | A filter has the wrong type or is out of range. details names the field. |
| 502 | upstream_error | The carrier refused the search or could not be reached. The message is the carrier's own. |
| 503 | error | Number ordering is not configured on this deployment. |
| 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 | The workspace was not found, or you named a workspace with ?agencyId= that is not yours. |
Console API · Buy a number
/api/tracking/numbers/purchaseConsole API. This one spends money. The setup fee comes out of the workspace wallet and the renewal worker bills that wallet each month afterwards. The number arrives unassigned — point it at a campaign with Assign a number before it can take a call. You mint the requestId yourself, and it is the idempotency key: retrying with the same id never charges twice. The purchase is recorded in the activity log — the money itself is in the wallet ledger, and this entry is who decided to spend it.
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
e164Required | string | The number to buy, in E.164, exactly as the search returned it. |
quoteTokenRequired | string | The token from Search for available numbers. It is valid for fifteen minutes and is bound to these digits and to you. |
requestIdRequired | uuid | A UUID you mint. It is the idempotency key: send the same one again to retry safely after a timeout. |
labelOptional | string | Your own name for the number.up to 120 characters |
tagsOptional | array of strings | Tags for your own filing.at most 12 · each up to 40 characters |
customerReferenceOptional | string | A reference passed to the carrier.up to 100 characters |
Example request
curl -X POST "https://api.buy3.io/api/tracking/numbers/purchase" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"e164": "+16195550134",
"quoteToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.sample.token",
"requestId": "3a1f0c52-7d9e-4b61-8f2a-5c6d7e8f9a0b",
"label": "Billboard I-5"
}'Responses
- 201The order completed and the number is yours.
order.setupCentsandnumber.monthlyCentsare returned and are left out of this example. - 202The carrier has taken the order but has not finished it.
order.statusispending,submittedorreview, andnumberisnulluntil it completes. Poll List numbers for the number to appear. - 422The order failed at the carrier. The body is still the order — not an error envelope — with
errorexplaining why. Nothing was charged.
{
"order": {
"id": "2e1d0c9b-8a7f-4e6d-9c5b-4a3f2e1d0c9b",
"e164": "+16195550134",
"status": "completed",
"error": null,
"createdAt": "2026-09-20T19:40:02.000Z"
},
"number": {
"id": "8d1f2e3a-4b5c-4d6e-9f0a-1b2c3d4e5f60",
"e164": "+16195550134",
"formatted": "(619) 555-0134",
"national": "(619) 555-0134",
"type": "local",
"status": "active",
"role": "unassigned",
"label": "Billboard I-5",
"region": "CA",
"locality": "San Diego",
"campaign": null,
"publisher": null,
"pool": null,
"webhookStatus": "ready",
"webhookError": null,
"renewsAt": "2026-10-20T14:00:02.000Z",
"purchasedAt": "2026-07-04T14:00:02.000Z",
"calls30d": 0,
"lastCallAt": null
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The quote expired — search again before purchasing — or the token does not match the number you sent. |
| 400 | validation_error | e164, quoteToken or requestId is missing or malformed. |
| 402 | insufficient_funds | The workspace wallet cannot cover the setup fee. Add funds and retry with the same requestId. |
| 402 | plan_upgrade_required | The workspace already holds as many numbers as its plan allows, counting orders in flight. The check is skipped when you retry the same requestId. |
| 409 | conflict | That requestId was used for a different number or a different owner, or an order for this number is already in progress. |
| 503 | error | Number ordering is not configured on this deployment. |
| 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 | The workspace was not found, or you named a workspace with ?agencyId= that is not yours. |
Console API · Assign, move or relabel a number
/api/tracking/numbers/:idConsole API. Point a number at a campaign, attribute it to a publisher or put it in a pool, or change its label. Only numbers of role unassigned or tracking can be moved. A field you leave out keeps its value; sending campaignId: null unassigns the number and drops the publisher and the pool with it. After a successful assignment, a number whose carrier wiring is pending or failed is pointed at the platform again automatically.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The number's id. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Request body
| Name | Type | Description |
|---|---|---|
campaignIdOptional | uuid or null | A tracking campaign of yours that is not archived. null sends the number back to unassigned. |
publisherIdOptional | uuid or null | A publisher of yours that already has a lane on that campaign. Calls on this number then earn that publisher their payout. |
poolIdOptional | uuid or null | A pool of that campaign. On an unassigned number the pool's campaign is implied, and the number takes on the pool's publisher. |
labelOptional | string or null | Your own name for the number.up to 120 characters |
Example request
curl -X PATCH "https://api.buy3.io/api/tracking/numbers/7c0e5a3b-1d2f-4e6a-8b9c-0d1e2f3a4b5c" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"campaignId": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"publisherId": "c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b",
"label": "Billboard — I-35"
}'Responses
- 200The number as it now stands, and
warnings— sentences about consequences that are not refusals: the carrier is not delivering this number's calls yet, a pool has lost its fallback number, or a live campaign now has no number at all.
{
"number": {
"id": "7c0e5a3b-1d2f-4e6a-8b9c-0d1e2f3a4b5c",
"e164": "+18885550100",
"formatted": "(888) 555-0100",
"national": "(888) 555-0100",
"type": "toll_free",
"status": "active",
"role": "tracking",
"label": "Billboard — I-35",
"region": "TX",
"locality": null,
"campaign": {
"id": "6a2d1f0e-8b7c-4c55-a1d3-0e9f8a7b6c5d",
"name": "Health cover — inbound",
"status": "live"
},
"publisher": {
"id": "c4e1a2b3-9d8f-4f6e-b5a4-1c2d3e4f5a6b",
"name": "Northwind Media"
},
"pool": null,
"webhookStatus": "ready",
"webhookError": null,
"renewsAt": "2026-10-20T14:00:02.000Z",
"purchasedAt": "2026-07-04T14:00:02.000Z",
"calls30d": 1204,
"lastCallAt": "2026-09-20T18:21:02.000Z"
},
"warnings": []
}Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | Nothing to update, or you named a publisher or a pool without a campaign for them to belong to. details.campaignId says so. |
| 404 | not_found | Number not found — a malformed id, an id that does not exist and another workspace's number all answer exactly the same, before any query runs. |
| 404 | not_found | Campaign, Publisher or Number pool not found — one of the ids in the body is not yours. |
| 409 | conflict | The number is released, is doing another feature's job (ingress, agent or quarantine), belongs to a campaign you do not own, is being moved to an archived campaign, or was changed by somebody else while you were editing it. |
| 409 | publisher_not_on_campaign | That publisher has no lane on that campaign. Add the lane first. details carries the publisherId and the campaignId. |
| 409 | pool_campaign_mismatch | That pool belongs to a different campaign. details carries the poolId and its poolCampaignId. |
| 409 | pool_publisher_mismatch | That pool is attributed to a different publisher. details carries the poolId and its poolPublisherId. |
| 409 | number_is_fallback | This number is a pool's fallback, and a fallback has to stay out of rotation. details carries the poolId. |
| 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 number with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
Console API · Release a number
/api/tracking/numbers/:id/releaseConsole API. Takes the number out of service: it leaves its campaign and its pool and stops taking calls at once. It does not hand the digits back to the carrier — that is irreversible and stays with platform staff — so the number still belongs to your workspace and is still billed until staff complete it. The request is recorded in the activity log for them to act on. It takes no body, and it is the one write on this surface that a workspace with a lapsed subscription can still make.
Path parameters
| Name | Type | Description |
|---|---|---|
idRequired | uuid | The number's id. |
Query parameters
| Name | Type | Description |
|---|---|---|
agencyIdOptional | uuid | The workspace to act in. |
Example request
curl -X POST "https://api.buy3.io/api/tracking/numbers/7c0e5a3b-1d2f-4e6a-8b9c-0d1e2f3a4b5c/release" \
-H "Authorization: Bearer $BUY3_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'Responses
- 200The number after it was taken out of service.
releasedisfalse— the digits have not gone back to the carrier — andnotesays what happens next.
{
"number": {
"id": "7c0e5a3b-1d2f-4e6a-8b9c-0d1e2f3a4b5c",
"e164": "+18885550100",
"formatted": "(888) 555-0100",
"national": "(888) 555-0100",
"type": "toll_free",
"status": "active",
"role": "unassigned",
"label": "Billboard — I-35",
"region": "TX",
"locality": null,
"campaign": null,
"publisher": null,
"pool": null,
"webhookStatus": "ready",
"webhookError": null,
"renewsAt": "2026-10-20T14:00:02.000Z",
"purchasedAt": "2026-07-04T14:00:02.000Z",
"calls30d": 1204,
"lastCallAt": "2026-09-20T18:21:02.000Z"
},
"released": false,
"note": "The number has stopped taking calls and is queued for release. It is still billed until the release completes.",
"warnings": [
"The campaign \"Health cover — inbound\" is live and now has no tracking number."
]
}Errors
| Status | Code | When |
|---|---|---|
| 404 | not_found | Number not found — a malformed id, an id that does not exist and another workspace's number all answer exactly the same, before any query runs. |
| 409 | conflict | The number is already released, is doing another feature's job, or was changed by somebody else while you were editing it. |
| 401 | unauthorized | No session token, or one that has expired. |
| 402 | plan_upgrade_required | The workspace's plan does not include call tracking. details.feature is callTracking. |
| 403 | forbidden | You named no workspace and hold no owner or admin seat on any. |
| 404 | not_found | No number with that id in this workspace, or you named a workspace with ?agencyId= that is not yours. |
