> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# VoIP Connections

> Bring your own phone numbers via Twilio, Telnyx, SignalWire, or Vonage.

VoIP connections let you plug your existing telephony provider into
ThunderPhone. Once connected, you can list, import, and verify phone
numbers from your provider account; imported numbers become
[Phone number](/api-reference/phone-numbers) resources you can assign
agents to.

## Supported providers

| Provider               | `provider` id | Setup methods              |
| ---------------------- | ------------- | -------------------------- |
| Twilio                 | `twilio`      | `api_key`                  |
| Telnyx                 | `telnyx`      | `api_key`, `guided_telnyx` |
| SignalWire             | `signalwire`  | `api_key`                  |
| Vonage                 | `vonage`      | `api_key`                  |
| Manual (any SIP trunk) | `manual`      | `manual`                   |

## Endpoints

| Method   | Path                                                     | Description                                               |
| -------- | -------------------------------------------------------- | --------------------------------------------------------- |
| `GET`    | `/v1/voip-connections`                                   | List connections                                          |
| `POST`   | `/v1/voip-connections`                                   | Create a connection                                       |
| `PATCH`  | `/v1/voip-connections/{connection_id}`                   | Update credentials / name                                 |
| `DELETE` | `/v1/voip-connections/{connection_id}`                   | Remove a connection                                       |
| `POST`   | `/v1/voip-connections/test`                              | Test raw credentials (no persisted connection)            |
| `POST`   | `/v1/voip-connections/suggest-name`                      | Infer a display name from credentials                     |
| `GET`    | `/v1/voip-connections/{connection_id}/available-numbers` | List numbers already owned in the provider account        |
| `POST`   | `/v1/voip-connections/{connection_id}/import-numbers`    | Import selected numbers                                   |
| `GET`    | `/v1/voip-connections/{connection_id}/search-numbers`    | Search purchasable numbers (guided provisioning)          |
| `POST`   | `/v1/voip-connections/{connection_id}/provision-number`  | Buy a number through the connection (guided provisioning) |

<Note>
  There is no `GET /v1/voip-connections/{connection_id}` retrieve
  endpoint — fetch the list and select by `id`.
</Note>

## Connection object

```json theme={null}
{
  "id": 5,
  "name": "Acme Telnyx Main",
  "provider": "telnyx",
  "status": "connected",
  "setup_method": "api_key",
  "inferred_name": false,
  "credentials": { "apiKey": "********", "connectionId": "123456" },
  "sip_config": { "domain": "acme.sip.telnyx.com" },
  "test_result": { "checks": { "credentials_valid": true } },
  "last_tested_at": "2026-04-20T18:24:10.113Z",
  "numbers_imported": 3,
  "provisioning_supported": true,
  "provisioning_message": null,
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z"
}
```

| Field                    | Type              | Description                                                                                                                                          |
| ------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                     | integer           | Connection id                                                                                                                                        |
| `name`                   | string            | Display name                                                                                                                                         |
| `provider`               | string            | `twilio`, `telnyx`, `signalwire`, `vonage`, `manual`                                                                                                 |
| `status`                 | string            | `pending`, `testing`, `connected`, `error` (read-only)                                                                                               |
| `setup_method`           | string            | `api_key`, `manual`, `guided_telnyx`                                                                                                                 |
| `inferred_name`          | boolean           | `true` when we auto-generated the name (read-only)                                                                                                   |
| `credentials`            | object            | Returned with sensitive values **masked** — any key containing `key`, `token`, `secret`, or `password` reads `"********"`. Writable on create/update |
| `sip_config`             | object            | Provider-specific SIP config. Writable on create/update; sensitive values masked on read the same way                                                |
| `test_result`            | object            | Result payload of the most recent credential test (read-only)                                                                                        |
| `last_tested_at`         | timestamp \| null | Last credential test (read-only)                                                                                                                     |
| `numbers_imported`       | integer           | Count of phone numbers imported through this connection (read-only)                                                                                  |
| `provisioning_supported` | boolean           | Whether [guided number provisioning](#search-purchasable-numbers) works for this provider (currently Telnyx only)                                    |
| `provisioning_message`   | string \| null    | When unsupported: an explanation, e.g. `"Provisioning not yet supported for twilio; use Import."`                                                    |

<Note>
  The masked `credentials` object shows which keys are configured
  without revealing values. To rotate credentials, `PATCH` with a full
  new `credentials` object.
</Note>

***

## List connections

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.thunderphone.com/v1/voip-connections \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

Returns an array of [Connection objects](#connection-object).

***

## Create a connection

Creation is a two-phase flow:

1. Call [`POST /v1/voip-connections/test`](#test-credentials) with the
   credentials and get back a **verification evidence id** if the test
   passes.
2. Call this endpoint with the same credentials plus
   `verification_evidence_id` — the evidence proves the credentials
   were already validated, avoiding double-billing for the test.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/voip-connections \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Acme Telnyx Main",
      "provider": "telnyx",
      "setup_method": "api_key",
      "credentials": {
        "apiKey": "KEY...",
        "connectionId": "123456"
      },
      "sip_config": { "domain": "acme.sip.telnyx.com" },
      "verification_evidence_id": "b9a2..."
    }'
  ```
</CodeGroup>

### Request fields

| Field                      | Type   | Required | Description                                            |
| -------------------------- | ------ | -------- | ------------------------------------------------------ |
| `provider`                 | string | yes      | Provider id (see table above)                          |
| `name`                     | string | no       | Defaults to a provider-inferred value                  |
| `setup_method`             | string | no       | Defaults to `api_key`; Telnyx supports `guided_telnyx` |
| `credentials`              | object | yes      | Provider-specific key/secret/connection fields         |
| `sip_config`               | object | no       | Extra SIP config — e.g. `{"domain": "..."}`            |
| `verification_evidence_id` | UUID   | yes      | Id returned by `POST /voip-connections/test`           |

Returns `201 Created` with the [Connection object](#connection-object).

Errors:

| Status | Condition                                                       |
| ------ | --------------------------------------------------------------- |
| `400`  | Provider mismatch (evidence generated for a different provider) |
| `400`  | Evidence reused / stale / missing                               |
| `422`  | Credentials failed re-verification                              |

***

## Update a connection

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.thunderphone.com/v1/voip-connections/5 \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"name": "Renamed"}'
  ```
</CodeGroup>

| Field                      | Type   | Description                                                     |
| -------------------------- | ------ | --------------------------------------------------------------- |
| `name`                     | string |                                                                 |
| `credentials`              | object | Rotate credentials. Requires a fresh `verification_evidence_id` |
| `sip_config`               | object |                                                                 |
| `verification_evidence_id` | UUID   | Required if `credentials` is present                            |

***

## Delete a connection

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE https://api.thunderphone.com/v1/voip-connections/5 \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

Returns `204 No Content`. Phone numbers imported through the deleted
connection are **not** removed — they stay in your org with
`voip_connection_id` cleared.

***

## Test credentials

Probes the provider API with the supplied credentials. On success, the
response includes a `verification_evidence_id` you can pass to
[Create](#create-a-connection) or [Update](#update-a-connection) to
create/rotate the connection without a second round trip.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/voip-connections/test \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "provider":    "telnyx",
      "credentials": { "apiKey": "KEY...", "connectionId": "123456" },
      "sip_config":  { "domain": "acme.sip.telnyx.com" }
    }'
  ```
</CodeGroup>

```json Response (success) theme={null}
{
  "status": "pass",
  "verification_evidence_id": "b9a2...",
  "suggested_connection_name": "Telnyx: Acme Main (+15550001234)",
  "checks": {
    "credentials_valid": true,
    "inbound_reachable": true,
    "outbound_authorized": true
  }
}
```

***

## Suggest a connection name

Asks the provider for an inferred display name. Used by the dashboard
onboarding flow.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/voip-connections/suggest-name \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "provider":    "telnyx",
      "credentials": { "apiKey": "KEY..." }
    }'
  ```
</CodeGroup>

```json Response theme={null}
{
  "suggested_connection_name": "Telnyx: Acme Main",
  "suggested_connection_name_inferred": true
}
```

***

## List available numbers

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.thunderphone.com/v1/voip-connections/5/available-numbers \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

Returns the list of phone numbers visible to this connection's
credentials that are **not** yet imported into any ThunderPhone org:

```json theme={null}
[
  {
    "id": "provider-handle-abc",
    "number": "+15550001234",
    "friendlyName": "Main office",
    "type": "local",
    "capabilities": ["voice"],
    "region": "US-CA",
    "monthlyFee": 100
  }
]
```

***

## Import numbers

Moves the selected numbers into ThunderPhone as
[Phone number](/api-reference/phone-numbers) resources. Imports start
in `status="provisioning"` — call
[`POST /v1/phone-numbers/{id}/verify-voip`](/api-reference/phone-numbers#verify-a-voip-sourced-number)
to complete verification (inbound routing + outbound authorization
check).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/voip-connections/5/import-numbers \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"numbers": ["+15550001234", "+15550009999"]}'
  ```
</CodeGroup>

```json Response theme={null}
{
  "imported_count": 2,
  "provider_charge_estimate_cents": 0,
  "provider_charge_note": "Import does not create ThunderPhone charges. Provider charges are billed by your VoIP provider only.",
  "results": [
    { "id": 201, "number": "+15550001234", "status": "provisioning" },
    { "id": 202, "number": "+15550009999", "status": "provisioning" }
  ]
}
```

`numbers` accepts 1–50 entries per request.

***

## Search purchasable numbers

For connections where `provisioning_supported` is `true` (currently
**Telnyx** only), search the provider's inventory for numbers you can
buy without leaving ThunderPhone.

<CodeGroup>
  ```bash cURL theme={null}
  curl 'https://api.thunderphone.com/v1/voip-connections/5/search-numbers?country=US&area_code=415&type=local' \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```
</CodeGroup>

| Query param | Required | Description                 |
| ----------- | -------- | --------------------------- |
| `country`   | yes      | Two-letter ISO country code |
| `type`      | yes      | `local` or `toll-free`      |
| `area_code` | no       | Digits only                 |

```json Response theme={null}
{
  "results": [
    {
      "id": "provider-handle-abc",
      "number": "+14155550123",
      "friendlyName": "+1 415 555 0123",
      "type": "local",
      "capabilities": ["voice"],
      "region": "US-CA",
      "monthlyFee": 100,
      "monthlyFeeCurrency": "USD"
    }
  ],
  "provider_charge_note": "Purchasing this number and its recurring monthly service are billed directly by Telnyx. ThunderPhone does not add a provisioning fee."
}
```

| Status | Condition                                                                     |
| ------ | ----------------------------------------------------------------------------- |
| `200`  | Search results                                                                |
| `501`  | Provider does not support guided provisioning — use [Import](#import-numbers) |
| `502`  | Provider API error / temporarily unavailable                                  |

## Provision (buy) a number

Places a number order with the provider using the connection's
credentials, then registers the number in ThunderPhone. The provider —
not ThunderPhone — bills the purchase and monthly fee.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.thunderphone.com/v1/voip-connections/5/provision-number \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"number": "+14155550123"}'
  ```
</CodeGroup>

| Field    | Type   | Required | Description                                                               |
| -------- | ------ | -------- | ------------------------------------------------------------------------- |
| `number` | string | yes      | Explicit E.164 number from a [search](#search-purchasable-numbers) result |

```json Response (201 Created) theme={null}
{
  "order_id": "ord_abc123",
  "order_status": "pending",
  "provider_charge_note": "Purchasing this number and its recurring monthly service are billed directly by Telnyx. ThunderPhone does not add a provisioning fee.",
  "result": { "id": 203, "number": "+14155550123", "status": "provisioning" }
}
```

Returns `201 Created` when a new order was placed, or `200 OK` when
the number was already owned and only registration was needed.

| Status | Condition                                     |
| ------ | --------------------------------------------- |
| `400`  | Invalid number / order rejected               |
| `501`  | Provider does not support guided provisioning |
| `502`  | Provider API error / temporarily unavailable  |

***

## Related

<CardGroup cols={2}>
  <Card title="Phone Numbers" icon="phone" href="/api-reference/phone-numbers">
    Assign agents to imported numbers and run verify-voip.
  </Card>

  <Card title="Outbound Calls" icon="arrow-up-right" href="/api-reference/outbound-calls">
    Outbound calling requires a VoIP-backed number.
  </Card>
</CardGroup>
