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

EndpointSurfaceCredential
GET /api/v1/numbersREST APIAPI key with numbers:read
GET /api/v1/blocked-callersREST APIAPI key with blocklist:write
POST /api/v1/blocked-callersREST APIAPI key with blocklist:write
DELETE /api/v1/blocked-callers/:idREST APIAPI key with blocklist:write
POST /api/tracking/numbers/searchConsoleSession token
POST /api/tracking/numbers/purchaseConsoleSession token
PATCH /api/tracking/numbers/:idConsoleSession token
POST /api/tracking/numbers/:id/releaseConsoleSession 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

FieldTypeMeaning
iduuidThe number's id, used in every path below.
e164, national, formattedstringThe number, and its two display forms.
typestringlocal, toll_free, mobile, national or shared_cost.
statusstringactive, past_due, suspended, released or draft.
rolestringWhat 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.
labelstring or nullYour own name for it — the billboard, the landing page, the radio spot.
region, localitystring or nullWhere the number is from.
campaign, publisher, poolobject or nullWhat the number is attributed to, each as { id, name } — the campaign also carries its status.
webhookStatusstringWhether the carrier is pointed at the platform yet: pending, ready, failed or skipped. A number that is not ready will not deliver its calls.
webhookErrorstring or nullWhat went wrong when webhookStatus is failed.
monthlyCentsintegerThe number's monthly rent, in cents. See pricing.
renewsAttimestampWhen the rent is next billed. Present only when a billing date is known.
purchasedAttimestamp or nullWhen the number was bought.
calls30dintegerCalls in the last 30 days. It is 0 on a single-number response, which is not given the statistics.
lastCallAttimestamp or nullWhen 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 to unassigned and 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

List numbers

GET/api/v1/numbers

Every 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.

API key. Send Authorization: Bearer <api key>. See Authentication.Scopenumbers:read

Query parameters

NameTypeDescription
qOptionalstringDigits match the number itself; text matches the label.up to 40 characters
statusOptionalenumOne status.active, past_due, suspended, released, draft · default: every status except released
campaignIdOptionaluuidOnly numbers on this campaign.
publisherIdOptionaluuidOnly numbers attributed to this publisher.
poolIdOptionaluuidOnly numbers in this pool.
assignedOptionalbooleantrue for numbers on a campaign, false for unassigned ones. It is ignored when campaignId is sent.
pageOptionalintegerPage number, from 1.default 1
pageSizeOptionalintegerRows per page.default 25 · at most 200; a larger value is clamped rather than refused
sortOptionalenumThe field to sort by. An unknown value falls back to the default rather than failing.createdAt, e164, status, purchasedAt · default createdAt
dirOptionalenumSort 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 calls30d and lastCallAt. monthlyCents is 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

StatusCodeWhen
400validation_errorA filter has the wrong type, or status is not one of the five. details names the field.
401missing_key, invalid_keyNo bearer key was sent, or the key is malformed, unknown, or revoked with its grace period over. See Authentication.
402subscription_inactive, plan_upgrade_requiredThe workspace's subscription is not active, or its plan does not include call tracking and API access.
403insufficient_scope, ip_not_allowedThe key does not hold numbers:read, or the request came from an address outside the key's allowlist.
429rate_limitedThe key is over its per-minute limit. Wait for Retry-After seconds.

List blocked callers

GET/api/v1/blocked-callers

The 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.

API key. Send Authorization: Bearer <api key>. See Authentication.Scopeblocklist:write

Query parameters

NameTypeDescription
qOptionalstringDigits match the last ten digits of the number; text matches the reason.up to 40 characters
campaignIdOptionaluuidOnly blocks on this campaign.
scopeOptionalenumOnly workspace-wide blocks, or only campaign blocks. It is ignored when campaignId is sent.workspace, campaign
pageOptionalintegerPage number, from 1.default 1
pageSizeOptionalintegerRows 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

StatusCodeWhen
400validation_errorA filter has the wrong type, or scope is not workspace or campaign.
401missing_key, invalid_keyNo bearer key was sent, or the key is malformed, unknown, or revoked with its grace period over. See Authentication.
402subscription_inactive, plan_upgrade_requiredThe workspace's subscription is not active, or its plan does not include call tracking and API access.
403insufficient_scope, ip_not_allowedThe key does not hold blocklist:write, or the request came from an address outside the key's allowlist.
429rate_limitedThe key is over its per-minute limit. Wait for Retry-After seconds.

Block a caller

POST/api/v1/blocked-callers

Add 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.

API key. Send Authorization: Bearer <api key>. See Authentication.Scopeblocklist:write

Request body

NameTypeDescription
callerNumberRequiredstringThe caller, in any spelling. It is stored as its last ten digits.up to 32 characters · 7 to 15 digits
campaignIdOptionaluuid or nullBlock on one campaign only. Omitted or null blocks across the whole workspace.
reasonOptionalstringWhy, 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

StatusCodeWhen
400bad_requestcallerNumber is not a phone number. details.callerNumber says what is expected.
400validation_errorcallerNumber is missing or too long, or campaignId is not a UUID.
404not_foundCampaign not foundcampaignId is not a tracking campaign of your workspace.
409conflictThe caller's block changed while the request was being saved — somebody unblocked them at the same moment. Send the request again.
401missing_key, invalid_keyNo bearer key was sent, or the key is malformed, unknown, or revoked with its grace period over. See Authentication.
402subscription_inactive, plan_upgrade_requiredThe workspace's subscription is not active, or its plan does not include call tracking and API access.
403insufficient_scope, ip_not_allowedThe key does not hold blocklist:write, or the request came from an address outside the key's allowlist.
429rate_limitedThe key is over its per-minute limit. Wait for Retry-After seconds.

Unblock a caller

DELETE/api/v1/blocked-callers/:id

Remove 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.

API key. Send Authorization: Bearer <api key>. See Authentication.Scopeblocklist:write

Path parameters

NameTypeDescription
idRequireduuidThe 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

StatusCodeWhen
404not_foundBlocked caller not found — no block with that id in your workspace. A malformed id answers the same.
401missing_key, invalid_keyNo bearer key was sent, or the key is malformed, unknown, or revoked with its grace period over. See Authentication.
402subscription_inactive, plan_upgrade_requiredThe workspace's subscription is not active, or its plan does not include call tracking and API access.
403insufficient_scope, ip_not_allowedThe key does not hold blocklist:write, or the request came from an address outside the key's allowlist.
429rate_limitedThe key is over its per-minute limit. Wait for Retry-After seconds.

Console API · Search for available numbers

POST/api/tracking/numbers/search

Console 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.

Console session. Called by the console with the signed-in user's session. Use the REST API with an API key for your own integrations.

Query parameters

NameTypeDescription
agencyIdOptionaluuidThe workspace to act in. Only needed when you hold an owner or admin seat on more than one.

Request body

NameTypeDescription
countryCodeOptionalstringTwo-letter country code.default US
areaCodeOptionalstring or integerThe area code to search in.1 to 8 digits
stateOptionalstringA state or region.up to 100 characters
localityOptionalstringA town or city.up to 100 characters
rateCenterOptionalstringA carrier rate centre.up to 100 characters
containsOptionalstring or integerDigits the number must contain. * stands for any digit.up to 15 characters
startsWithOptionalstring or integerDigits the number must start with, with an optional leading +.up to 16 characters
endsWithOptionalstring or integerDigits the number must end with.up to 15 characters
numberTypeOptionalenumThe sort of number.local, toll_free, mobile, national, shared_cost
featuresOptionalstring or array of stringsCapabilities the number must have.at most 8 · voice, sms, mms, fax, emergency, hd_voice, international_sms, local_calling
limitOptionalintegerHow many results to return.1 to 250 · default 20
bestEffortOptionalbooleanLet the carrier widen the search when an exact match is scarce.
quickshipOptionalbooleanOnly numbers the carrier can activate immediately.
reservableOptionalbooleanOnly numbers the carrier will hold.
excludeHeldNumbersOptionalbooleanLeave 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 carries setupCents and monthlyCents; 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

StatusCodeWhen
400validation_errorA filter has the wrong type or is out of range. details names the field.
502upstream_errorThe carrier refused the search or could not be reached. The message is the carrier's own.
503errorNumber ordering is not configured on this deployment.
401unauthorizedNo session token, or one that has expired.
402subscription_inactiveThe workspace's subscription is not active, and this is a write. details carries status, planId and agencyId.
402plan_upgrade_requiredThe workspace's plan does not include call tracking. details.feature is callTracking.
403forbiddenYou named no workspace and hold no owner or admin seat on any.
404not_foundThe workspace was not found, or you named a workspace with ?agencyId= that is not yours.

Console API · Buy a number

POST/api/tracking/numbers/purchase

Console 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.

Console session. Called by the console with the signed-in user's session. Use the REST API with an API key for your own integrations.

Query parameters

NameTypeDescription
agencyIdOptionaluuidThe workspace to act in.

Request body

NameTypeDescription
e164RequiredstringThe number to buy, in E.164, exactly as the search returned it.
quoteTokenRequiredstringThe token from Search for available numbers. It is valid for fifteen minutes and is bound to these digits and to you.
requestIdRequireduuidA UUID you mint. It is the idempotency key: send the same one again to retry safely after a timeout.
labelOptionalstringYour own name for the number.up to 120 characters
tagsOptionalarray of stringsTags for your own filing.at most 12 · each up to 40 characters
customerReferenceOptionalstringA 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.setupCents and number.monthlyCents are returned and are left out of this example.
  • 202The carrier has taken the order but has not finished it. order.status is pending, submitted or review, and number is null until 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 error explaining 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

StatusCodeWhen
400bad_requestThe quote expired — search again before purchasing — or the token does not match the number you sent.
400validation_errore164, quoteToken or requestId is missing or malformed.
402insufficient_fundsThe workspace wallet cannot cover the setup fee. Add funds and retry with the same requestId.
402plan_upgrade_requiredThe workspace already holds as many numbers as its plan allows, counting orders in flight. The check is skipped when you retry the same requestId.
409conflictThat requestId was used for a different number or a different owner, or an order for this number is already in progress.
503errorNumber ordering is not configured on this deployment.
401unauthorizedNo session token, or one that has expired.
402subscription_inactiveThe workspace's subscription is not active, and this is a write. details carries status, planId and agencyId.
402plan_upgrade_requiredThe workspace's plan does not include call tracking. details.feature is callTracking.
403forbiddenYou named no workspace and hold no owner or admin seat on any.
404not_foundThe workspace was not found, or you named a workspace with ?agencyId= that is not yours.

Console API · Assign, move or relabel a number

PATCH/api/tracking/numbers/:id

Console 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.

Console session. Called by the console with the signed-in user's session. Use the REST API with an API key for your own integrations.

Path parameters

NameTypeDescription
idRequireduuidThe number's id.

Query parameters

NameTypeDescription
agencyIdOptionaluuidThe workspace to act in.

Request body

NameTypeDescription
campaignIdOptionaluuid or nullA tracking campaign of yours that is not archived. null sends the number back to unassigned.
publisherIdOptionaluuid or nullA publisher of yours that already has a lane on that campaign. Calls on this number then earn that publisher their payout.
poolIdOptionaluuid or nullA pool of that campaign. On an unassigned number the pool's campaign is implied, and the number takes on the pool's publisher.
labelOptionalstring or nullYour 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

StatusCodeWhen
400bad_requestNothing to update, or you named a publisher or a pool without a campaign for them to belong to. details.campaignId says so.
404not_foundNumber 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.
404not_foundCampaign, Publisher or Number pool not found — one of the ids in the body is not yours.
409conflictThe 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.
409publisher_not_on_campaignThat publisher has no lane on that campaign. Add the lane first. details carries the publisherId and the campaignId.
409pool_campaign_mismatchThat pool belongs to a different campaign. details carries the poolId and its poolCampaignId.
409pool_publisher_mismatchThat pool is attributed to a different publisher. details carries the poolId and its poolPublisherId.
409number_is_fallbackThis number is a pool's fallback, and a fallback has to stay out of rotation. details carries the poolId.
401unauthorizedNo session token, or one that has expired.
402subscription_inactiveThe workspace's subscription is not active, and this is a write. details carries status, planId and agencyId.
402plan_upgrade_requiredThe workspace's plan does not include call tracking. details.feature is callTracking.
403forbiddenYou named no workspace and hold no owner or admin seat on any.
404not_foundNo number with that id in this workspace, or you named a workspace with ?agencyId= that is not yours.

Console API · Release a number

POST/api/tracking/numbers/:id/release

Console 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.

Console session. Called by the console with the signed-in user's session. Use the REST API with an API key for your own integrations.

Path parameters

NameTypeDescription
idRequireduuidThe number's id.

Query parameters

NameTypeDescription
agencyIdOptionaluuidThe 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. released is false — the digits have not gone back to the carrier — and note says 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

StatusCodeWhen
404not_foundNumber 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.
409conflictThe number is already released, is doing another feature's job, or was changed by somebody else while you were editing it.
401unauthorizedNo session token, or one that has expired.
402plan_upgrade_requiredThe workspace's plan does not include call tracking. details.feature is callTracking.
403forbiddenYou named no workspace and hold no owner or admin seat on any.
404not_foundNo number with that id in this workspace, or you named a workspace with ?agencyId= that is not yours.