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

# VoiceLink Management

> Set up inbound VoiceLink DIDs and manage clients, mapping, routing, and readiness

# VoiceLink Management

Use the VoiceLink management API to connect purchased DIDs to Vocobase agents, safely configure inbound or outbound WebSocket routing, and inspect VoiceLink drift without opening the VoiceLink panel.

<Note>
  VoiceLink remains the source of truth for remote clients, DID ownership, expiration, bots, and routing. Vocobase remains the source of truth for local `PhoneNumber.agentId` assignment. Sync imports or updates local DID rows, but it does not silently delete local data.
</Note>

## Day-one scope

These APIs cover operational setup only:

| Supported                                          | Not included                                                                                     |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| List/create/update clients, bots, and call routing | DID purchase                                                                                     |
| List purchased and available DIDs                  | DID renewal                                                                                      |
| Map a DID to a client                              | KYC                                                                                              |
| Sync remote state into Vocobase                    | Wallet or credit allocation                                                                      |
| Read readiness and drift                           | Silent local deletion                                                                            |
| Make a purchased, mapped DID outbound call-ready   | Client auto-creation or implicit DID mapping (mapping happens only with an explicit `client_id`) |

## Connection selection

All endpoints use the authenticated partner's active VoiceLink connection. If the partner has more than one active VoiceLink connection, pass `connection_id` so the backend can choose deterministically.

For `GET` endpoints, pass it in the query string:

```bash theme={null}
curl -X GET "https://api.vocobase.com/api/v2/voicelink/clients?connection_id=9b7f1c44-c87f-4f0c-9124-3c802a9c1a20" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

For `POST` endpoints, pass it in the JSON body:

```json theme={null}
{
  "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20"
}
```

`connectionId` is accepted for compatibility, but use `connection_id` in new integrations.

## Endpoint overview

| Method | Endpoint                                    | Purpose                                  |
| ------ | ------------------------------------------- | ---------------------------------------- |
| `GET`  | `/api/v2/voicelink/clients`                 | List VoiceLink clients                   |
| `POST` | `/api/v2/voicelink/clients`                 | Create a VoiceLink client                |
| `POST` | `/api/v2/voicelink/clients/:clientId`       | Update a VoiceLink client                |
| `GET`  | `/api/v2/voicelink/dids/purchased`          | List purchased or assigned DIDs          |
| `GET`  | `/api/v2/voicelink/dids/available`          | List available DIDs                      |
| `POST` | `/api/v2/voicelink/dids/map`                | Map a DID to a client                    |
| `GET`  | `/api/v2/voicelink/bots`                    | List WebSocket bots                      |
| `POST` | `/api/v2/voicelink/bots`                    | Create a WebSocket bot                   |
| `POST` | `/api/v2/voicelink/bots/:botId`             | Update a WebSocket bot                   |
| `GET`  | `/api/v2/voicelink/call-routing`            | List DID call routing                    |
| `POST` | `/api/v2/voicelink/call-routing`            | Create call routing for a DID            |
| `POST` | `/api/v2/voicelink/call-routing/:routingId` | Update call routing                      |
| `POST` | `/api/v2/voicelink/sync`                    | Sync remote state and readiness          |
| `POST` | `/api/v2/voicelink/call-ready`              | Make a purchased DID outbound call-ready |
| `POST` | `/api/v2/voicelink/inbound-ready`           | Make a purchased DID inbound call-ready  |

## Create a client

Create a remote VoiceLink client before mapping DIDs to it.

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/clients \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "first_name": "Acme",
    "last_name": "Sales",
    "username": "acme-sales",
    "email": "voice@example.com",
    "password": "temporary-client-password",
    "channel_count": 5,
    "phone": "+918011223344",
    "is_active": 1
  }'
```

Required fields: `first_name`, `last_name`, `username`, `email`, `password`, and `channel_count`.

## List DIDs

Purchased DIDs are DIDs already owned or assigned in VoiceLink:

```bash theme={null}
curl -X GET "https://api.vocobase.com/api/v2/voicelink/dids/purchased?connection_id=9b7f1c44-c87f-4f0c-9124-3c802a9c1a20&per_page=200" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

Available DIDs are read-only inventory returned by VoiceLink:

```bash theme={null}
curl -X GET "https://api.vocobase.com/api/v2/voicelink/dids/available?connection_id=9b7f1c44-c87f-4f0c-9124-3c802a9c1a20" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

<Warning>
  The available-DID endpoint does not purchase, renew, KYC, or allocate wallet credit. Complete those operations in VoiceLink.
</Warning>

## Map a DID to a client

Use the VoiceLink DID ID and client ID:

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/dids/map \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "client_id": 415,
    "did_id": 812,
    "call_recording": 1,
    "user_status": 2,
    "description": "Sales outbound DID"
  }'
```

`user_status` is VoiceLink's mapping status: `2` = assigned (active) and `4` = inactive. VoiceLink rejects any other value.

## Manage WebSocket bots

A VoiceLink WebSocket bot connects a client's calls to Vocobase. These endpoints never purchase or renew DIDs, run KYC, allocate wallet credit, or delete local Vocobase data.

List bots, optionally filtered by `client_id`, `status` (0 or 1), `reseller_id`, `search`, `per_page` (1–1000), and `page`:

```bash theme={null}
curl -X GET "https://api.vocobase.com/api/v2/voicelink/bots?connection_id=9b7f1c44-c87f-4f0c-9124-3c802a9c1a20&client_id=415&status=1" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

Create a bot. `bot_name` and `client_id` are required; omit `websocket_url` to point the bot at the Vocobase bot endpoint:

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/bots \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "bot_name": "Vocobase +918011223344",
    "client_id": 415,
    "status": 1
  }'
```

Update a bot:

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/bots/93 \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "bot_name": "Vocobase +918011223344",
    "websocket_url": "wss://api.pipecat.daily.co/ws/twilio",
    "status": 1
  }'
```

<Warning>
  The VoiceLink bot update REPLACES the record — `bot_name`, `websocket_url`, and `status` are all required, and any field you omit is lost. Resend every field you want kept.
</Warning>

Numeric fields are validated strictly across these endpoints: zero, negative, non-integer, or non-finite values return a 400 `VALIDATION_ERROR` naming the field.

## Call routing

Call routing tells VoiceLink where a DID's inbound and outbound calls go. Mode `3` routes calls to a WebSocket bot.

<Note>
  For the common outbound case, prefer `POST /api/v2/voicelink/call-ready` — it is the recommended safe path because it preserves existing inbound settings automatically. The raw routing update REPLACES the record, so inbound fields must be resent every time.
</Note>

List routing records:

```bash theme={null}
curl -X GET "https://api.vocobase.com/api/v2/voicelink/call-routing?connection_id=9b7f1c44-c87f-4f0c-9124-3c802a9c1a20" \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

Create routing for a DID (one record per DID). Provide either `did_id` or `did_number`; `for_inbound_call` and `for_outbound_call` are required:

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/call-routing \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "did_id": 812,
    "for_inbound_call": 1,
    "for_outbound_call": 3,
    "outbound_websocket_bot_id": 93,
    "status": 1
  }'
```

Update routing. `for_inbound_call`, `for_outbound_call`, and `status` are all required because the update replaces the record — resend the current inbound values even if you only want to change outbound routing:

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/call-routing/120 \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "for_inbound_call": 1,
    "for_outbound_call": 3,
    "outbound_websocket_bot_id": 93,
    "status": 1
  }'
```

These endpoints never purchase or renew DIDs, run KYC, allocate wallet credit, or delete local Vocobase data.

## Sync and readiness

Sync fetches remote VoiceLink state and compares it with Vocobase local phone numbers.

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/sync \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "import_dids": true
  }'
```

Set `import_dids: false` for a read-only readiness check. When omitted, `import_dids` defaults to `true`.

```json theme={null}
{
  "success": true,
  "data": {
    "clients": [],
    "dids": [],
    "available_dids": [],
    "bots": [],
    "routing": [],
    "local_phone_numbers": [],
    "readiness": [],
    "drift": [],
    "warnings": []
  }
}
```

### Readiness states

| State                 | Meaning                                                                                 |
| --------------------- | --------------------------------------------------------------------------------------- |
| `READY`               | Remote DID, client, bot, routing, and local agent assignment are present.               |
| `NOT_IMPORTED`        | The DID exists in VoiceLink but is not imported into Vocobase local inventory.          |
| `NO_CLIENT_MAPPING`   | VoiceLink has not mapped this DID to a remote client.                                   |
| `NO_BOT`              | No active VoiceLink WebSocket bot exists for the mapped client.                         |
| `ROUTING_MISSING`     | VoiceLink has no call-routing record for this DID.                                      |
| `ROUTING_CHANGED`     | VoiceLink routing exists but outbound calls no longer point to an active WebSocket bot. |
| `NO_VOCOBASE_AGENT`   | The local Vocobase phone number has no assigned agent.                                  |
| `REMOTE_DID_EXPIRED`  | VoiceLink marks the DID as expired.                                                     |
| `REMOTE_DID_RELEASED` | VoiceLink marks the DID as released and no longer usable for calls.                     |
| `REMOTE_DID_DELETED`  | Vocobase has this DID locally, but VoiceLink no longer returns it.                      |

Use `readiness` for a per-DID checklist and `drift` for a flat action list. Do not delete local rows automatically when `REMOTE_DID_DELETED` appears; confirm in VoiceLink first.

Every readiness row also contains an `inbound` object with direction-specific readiness. `inbound.ready` is the canonical signal for inbound calling; the top-level `state` remains the outbound readiness signal for backward compatibility.

| Inbound state                        | Meaning                                                                                                  |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `INBOUND_RUNTIME_DISABLED`           | The backend deployment has not enabled VoiceLink inbound calling.                                        |
| `INBOUND_ORIGIN_AUTH_NOT_CONFIGURED` | The signed inbound endpoint cannot be built because its WSS URL or signing secret is missing or invalid. |
| `INBOUND_NOT_ENABLED_LOCALLY`        | The local PhoneNumber row exists but inbound calling is disabled.                                        |
| `INBOUND_BOT_URL_CHANGED`            | The active VoiceLink bot no longer points to the expected signed Vocobase endpoint.                      |
| Any shared readiness state           | The corresponding DID, client, bot, route, or agent prerequisite is missing.                             |

Each readiness row carries a `key` — a stable digit-only join key (no `+`) that links the remote DID, routing, and local phone-number rows for that number — and `action_items`, the same remediation steps as `actions` but as `{ "code", "label" }` objects for programmatic use (`code` is the readiness issue the step resolves).

Each drift row carries the same `key`, plus stable identifiers for repairing the drift without re-deriving them, when known: `did_id`, `client_id`, `routing_id`, `bot_id`, and `local_phone_number_id` (the local Vocobase PhoneNumber UUID).

### Sync warnings

`data.warnings` lists non-fatal upstream-fetch problems; it is empty on a fully successful sync. Each warning has:

| Field     | Values                                                 |
| --------- | ------------------------------------------------------ |
| `section` | `clients`, `dids`, `available_dids`, `bots`, `routing` |
| `code`    | `FETCH_FAILED`, `TRUNCATED`                            |
| `message` | Human-readable explanation                             |

When a section fetch fails or the purchased-DID list is truncated, the dependent readiness inferences are suppressed rather than guessed — sync never falsely reports states like `REMOTE_DID_DELETED` or `NO_BOT` off incomplete data.

## Make a DID inbound-ready

Use `POST /api/v2/voicelink/inbound-ready` for the recommended inbound setup path. The operation is idempotent and scoped to one purchased DID.

It:

1. Verifies that the deployment has enabled inbound calling and configured a secure WSS URL plus signing secret.
2. Resolves and imports the purchased DID, then enables inbound locally.
3. Maps a currently-unmapped DID only when you explicitly supply `client_id`.
4. Creates or updates the client WebSocket bot with a signed, connection-scoped endpoint.
5. Sets VoiceLink inbound mode to WebSocket bot (`3`) while preserving every existing outbound routing field.
6. Assigns the local number to the supplied Vocobase `agent_id`.

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/inbound-ready \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "did_id": 812,
    "client_id": 415,
    "agent_id": "f4df9f8c-df95-46d8-bb0e-6f9f83fd4f55",
    "bot_name": "Vocobase inbound +918011223344"
  }'
```

Provide either `did_id` or `did_number`. `agent_id` is required for a fully-ready DID. `client_id` is required only when VoiceLink has not mapped the DID yet. The endpoint never accepts a caller-supplied `websocket_url`; the backend always builds the signed endpoint from deployment configuration.

<Warning>
  Before using this endpoint in production, the Vocobase deployment must set `VOICELINK_INBOUND_ENABLED=true`, `VOICELINK_INBOUND_WSS_URL` to its public `wss://` VoiceLink bot endpoint, and `VOICELINK_INBOUND_SIGNING_SECRET` to a secret of at least 32 characters. Rotating the signing secret invalidates existing bot URLs, so rerun inbound setup once for each VoiceLink client bot after rotation.
</Warning>

The returned `steps` may include `verify_inbound_runtime` and `enable_inbound` in addition to the shared call-ready steps. A successful result has both `data.ready: true` and `data.readiness.inbound.ready: true`.

<Note>
  The backend places the signed token in Pipecat Cloud's base64-encoded `body` parameter, and the deployed bot reads the decoded value from its runner body. The token is a connection-scoped second factor in addition to the shared webhook authorization used by the call-metadata endpoint. It narrows a leaked credential from every VoiceLink DID to one connected VoiceLink account; it is not the endpoint's sole authentication boundary. Sync responses redact signed bot URLs. Do not copy a generated bot URL into logs, tickets, or another connection.
</Note>

## Make a DID call-ready

Use `POST /api/v2/voicelink/call-ready` when a purchased DID is already mapped to a VoiceLink client and you want Vocobase to perform the safe outbound setup steps.

The endpoint:

1. Resolves the DID from purchased VoiceLink inventory.
2. Imports the DID into local Vocobase phone-number inventory if missing.
3. Maps the DID to an explicitly supplied `client_id`, only if the DID is currently unmapped.
4. Ensures an active VoiceLink WebSocket bot exists for the mapped client.
5. Ensures outbound routing points to that bot.
6. Optionally assigns the local PhoneNumber row to `agent_id`.

<Note>
  Existing inbound routing is preserved when routing is updated. DID mapping happens only with an explicit `client_id` and only for a currently-unmapped DID — a DID already mapped to a different client is never re-mapped, and clients are never auto-created. This endpoint does not purchase, renew, run KYC, or allocate wallet credit.
</Note>

```bash theme={null}
curl -X POST https://api.vocobase.com/api/v2/voicelink/call-ready \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "connection_id": "9b7f1c44-c87f-4f0c-9124-3c802a9c1a20",
    "did_id": 812,
    "client_id": 415,
    "agent_id": "f4df9f8c-df95-46d8-bb0e-6f9f83fd4f55",
    "bot_name": "Vocobase +918011223344"
  }'
```

Provide either `did_id` or `did_number`. `client_id`, `agent_id`, `bot_name`, and `websocket_url` are optional:

* `client_id` — maps the DID to this VoiceLink client only when the DID is currently unmapped. A DID already mapped to a different client is never re-mapped, and clients are never auto-created. Mapping initiated through this composer uses `call_recording=0`.
* `agent_id` — assigns the imported local PhoneNumber row to this Vocobase agent. If omitted, the workflow still imports the DID and prepares VoiceLink bot/routing, but readiness remains incomplete until you assign a local Vocobase agent.
* `bot_name` / `websocket_url` — used only when a new WebSocket bot must be created; both default sensibly (the bot points to the Vocobase endpoint).

```json theme={null}
{
  "success": true,
  "data": {
    "phone_number": "+918011223344",
    "key": "918011223344",
    "ready": true,
    "steps": [
      {
        "step": "resolve_did",
        "status": "done",
        "detail": "Resolved DID +918011223344",
        "data": {
          "did_id": 812,
          "client_id": 415
        }
      },
      {
        "step": "ensure_routing",
        "status": "done",
        "detail": "Updated routing for DID +918011223344 to outbound bot_id=93",
        "data": {
          "routing_id": 120
        }
      }
    ],
    "readiness_before": {
      "key": "918011223344",
      "phone_number": "+918011223344",
      "state": "ROUTING_MISSING",
      "issues": ["ROUTING_MISSING"],
      "actions": ["Create VoiceLink call routing for this DID"],
      "action_items": [
        {
          "code": "ROUTING_MISSING",
          "label": "Create VoiceLink call routing for this DID"
        }
      ]
    },
    "readiness": {
      "key": "918011223344",
      "phone_number": "+918011223344",
      "state": "READY",
      "issues": [],
      "actions": [],
      "action_items": []
    }
  }
}
```

`readiness_before` is the readiness snapshot captured before provisioning ran (so you can see exactly what changed); it is `null` when the DID could not be resolved. `readiness` is recomputed after all steps.

For outbound call-ready requests, `steps[].step` is one of `resolve_did`, `import_did`, `client_mapping`, `ensure_bot`, `ensure_routing`, or `assign_agent`. Inbound-ready requests may additionally return `verify_inbound_runtime` and `enable_inbound`. `steps[].status` can be `done`, `already`, `skipped`, or `failed`. A `failed` step means the workflow stopped at that point and returned the latest readiness state so you can show the next operator action.

## Call-ready checklist

A VoiceLink DID is call-ready for Vocobase outbound calling when:

1. The DID is present and not expired in VoiceLink.
2. The DID is mapped to a VoiceLink client.
3. The mapped client has an active WebSocket bot that points to Vocobase.
4. VoiceLink outbound routing points to that active WebSocket bot.
5. The local Vocobase PhoneNumber row exists.
6. The local PhoneNumber row is assigned to the intended Vocobase agent.

The dashboard and the two readiness composers can sync/import DIDs and safely create missing bot/routing setup. Agent assignment remains local to Vocobase.

## Next steps

<CardGroup cols={2}>
  <Card title="VoiceLink Setup" icon="phone" href="/telephony/voicelink-setup">
    Configure VoiceLink credentials and make test calls.
  </Card>

  <Card title="Telephony Connections" icon="settings" href="/telephony/connections">
    Create and select named telephony connections.
  </Card>
</CardGroup>
