Phone Numbers
Provision, assign agents to, transfer, and verify phone numbers.
A phone number belongs to an organization and carries two agent assignments — one for the inbound direction (who answers) and one for the outbound direction (who is placed on the caller side). Numbers can be provisioned by ThunderPhone ("demo") or brought in through your own VoIP provider and verified via a VoIP connection.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/phone-numbers | List phone numbers |
POST | /v1/phone-numbers | Provision a new number |
GET | /v1/phone-numbers/{phone_number_id} | Retrieve a single number |
PUT / PATCH | /v1/phone-numbers/{phone_number_id} | Assign agents / update status |
DELETE | /v1/phone-numbers/{phone_number_id} | Release the number |
POST | /v1/phone-numbers/{phone_number_id}/transfer | Move a number to another org |
GET | /v1/phone-numbers/{phone_number_id}/versions | List prior assignments |
POST | /v1/phone-numbers/{phone_number_id}/verify-voip | Verify a VoIP-sourced number |
Phone number object
{
"id": 201,
"number": "+15551234567",
"label": "Support line",
"source": "voip",
"status": "active",
"inbound_agent_id": 12,
"outbound_agent_id": null,
"voip_connection_id": 5,
"voip_verification_status": "verified",
"voip_last_verified_at": "2026-04-20T18:24:10.113Z"
}| Field | Type | Description |
|---|---|---|
id | integer | Server-assigned number id |
number | string | E.164-formatted phone number (e.g. +15551234567) |
label | string | Your display label for the number (also settable via PATCH) |
source | string | demo (provisioned by ThunderPhone) or voip (brought via a VoIP connection) |
status | string | provisioning, active, failed, or released |
inbound_agent_id | integer | null | Agent that answers inbound calls to this number |
outbound_agent_id | integer | null | Agent used when placing outbound calls from this number. Always null for demo numbers (demo numbers are inbound-only) |
voip_connection_id | integer | null | Linked VoIP connection (only when source="voip") |
voip_verification_status | string | unverified, verified (an answered probe call), carrier_accepted (the carrier accepted the trunk, credentials and caller ID but would not route ThunderPhone's test destination — routable, shown with a caveat), or failed |
voip_last_verified_at | timestamp | null | Last successful verify |
List phone numbers
curl https://api.thunderphone.com/v1/phone-numbers \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"numbers = requests.get(
"https://api.thunderphone.com/v1/phone-numbers",
headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
).json()Returns an array of Phone number objects, sorted by created_at descending.
Provision a number
Requests a new demo number from ThunderPhone's pool. To bring your own number through a VoIP provider, see Import VoIP numbers.
curl -X POST https://api.thunderphone.com/v1/phone-numbers \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"area_code": "415"}'number = requests.post(
"https://api.thunderphone.com/v1/phone-numbers",
headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
json={"area_code": "415"},
).json()| Field | Type | Required | Description |
|---|---|---|---|
area_code | string | no | 3-digit area code to prefer |
city | string | no | US city name |
state | string | no | 2-letter US state code |
Returns 201 Created with the new Phone number object in
status="provisioning". The upstream provider usually transitions to
active within seconds; poll the detail endpoint to observe the change.
Retrieve a phone number
curl https://api.thunderphone.com/v1/phone-numbers/201 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"Returns 200 OK with a Phone number object, or 404 if not found.
Assign agents / update status
PATCH clears or changes inbound/outbound agent assignments. The new
agents must belong to the same org.
curl -X PATCH https://api.thunderphone.com/v1/phone-numbers/201 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inbound_agent_id": 12,
"outbound_agent_id": 13
}'number = requests.patch(
f"https://api.thunderphone.com/v1/phone-numbers/{phone_number_id}",
headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
json={"inbound_agent_id": 12, "outbound_agent_id": 13},
).json()| Field | Type | Description |
|---|---|---|
inbound_agent_id | integer | null | Pass null to clear |
outbound_agent_id | integer | null | Pass null to clear. Rejected with 400 on demo numbers (inbound-only) |
label | string | Display label |
status | string | Usually set by the server; write access is limited to toggling between active and released. Setting released also clears both agent assignments |
Returns 200 OK with the updated Phone number object.
Release a phone number
curl -X DELETE https://api.thunderphone.com/v1/phone-numbers/201 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"Returns 204 No Content. Demo numbers are returned to the pool; VoIP
numbers are detached from the phone record but remain in your VoIP
provider.
Transfer a number to another org
Atomically move a number between orgs. Requires admin+ in both orgs.
On transfer, the number's inbound/outbound agent assignments are
cleared (agents don't cross org boundaries).
curl -X POST https://api.thunderphone.com/v1/phone-numbers/201/transfer \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"target_org_id": 77}'result = requests.post(
f"https://api.thunderphone.com/v1/phone-numbers/{phone_number_id}/transfer",
headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
json={"target_org_id": 77},
).json(){
"source_org_id": 42,
"target_org_id": 77,
"cleared_agent_assignments": true,
"phone_number": { /* Phone number object in the target org */ }
}Version history
Every assignment change records a snapshot so you can audit who changed what.
curl 'https://api.thunderphone.com/v1/phone-numbers/201/versions?limit=20' \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"{
"count": 2,
"results": [
{
"id": 88,
"revision": 2,
"created_by_id": 7,
"created_at": "2026-04-20T18:24:10.113Z",
"snapshot": { /* Phone number object at this revision */ },
"changed_fields": ["inbound_agent_id"]
}
]
}created_by_id is the integer user id that made the change (null
for system-initiated changes).
Verify a VoIP-sourced number
For numbers imported through a VoIP connection, run (or re-run) the
verification flow. Provider connections (Twilio, Telnyx) re-check the
provider credentials and SIP reachability. Manual SIP connections
place a short probe call from this number, through the connection's
trunk, to a ThunderPhone number (LiveKit hangs it up after a few
seconds; your carrier bills it like any call). The carrier's SIP
answer is classified in the evidence — 401/407 credentials,
403 forbidden (credentials or caller ID), 404/484 unroutable,
480/408 no answer, 5xx provider error — and the same text is
returned as the failure reason. One exception is known to be a
post-authentication answer: DIDWW's 403 Destination number blacklisted by routing plan means the trunk and credentials were
accepted but the customer's plan does not route ThunderPhone's test
destination. That number becomes voip_verification_status = "carrier_accepted" (evidence outcome: "carrier_accepted"): routable,
but calls to other destinations were not tested. Only an answered call
yields verified.
Verification is admin-only, rate-limited, and serialised per number
(409 while a verification of the same number is in flight, and
409 if the number or its connection changed while the probe was
running — verify again). Numbers that are released cannot be
verified.
curl -X POST https://api.thunderphone.com/v1/phone-numbers/201/verify-voip \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"{
"phone_number_id": 201,
"status": "active",
"voip_verification_status": "verified",
"provider_charge_estimate_cents": 0,
"provider_charge_currency": "usd",
"provider_charge_note": ""
}Verification may take several seconds; the response reflects the final
state. If verification fails, voip_verification_status becomes
failed and the phone status returns to provisioning — fix the VoIP
connection (credentials, trunk, SIP allowlist) and call this endpoint
again.