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

# Test your voice agent

> Check your WebSocket connection, your answer URL and your number before a real call: none of these checks places a call.

Test your WebSocket connection without placing a phone call. For a voice agent listening locally on port 7860:

```bash theme={null}
plivo voice streams test --to ws://localhost:7860/ws
```

A WebSocket is a persistent connection that carries audio frames between Plivo and your voice agent. Three checks run from your machine, place no call and don't change your Plivo account:

* `plivo voice streams test`: your WebSocket endpoint, with Plivo-shaped audio frames.
* `plivo voice streams simulate`: your answer URL and its XML, as Plivo requests them, then the WebSocket in its `<Stream>`. It still requests your own webhook, so your server runs its normal handler.
* `plivo voice streams readiness`: a number, or a caller ID for an outbound call, is ready for a streamed call. Then it simulates the call.

## Test a WebSocket endpoint

`plivo voice streams test [flags]` opens a WebSocket to your server and sends synthetic frames in Plivo's format: one `start` frame, a `media` frame every 20 ms for `--duration` seconds, then one `stop` frame. It reports connection latency, frames sent and, with `--bidirectional`, whether your server sent audio back and how fast. Plivo's backend is not involved, so you can run it in continuous integration (CI).

| Flag | Description |
| - | - |
| `--bidirectional` | also read frames back from the endpoint (test the agent-to-caller path) |
| `--codec <string>` | audio codec: mulaw \| l16 (default "mulaw") |
| `--duration <int>` | seconds of synthetic audio to stream (max 30) (default 3) |
| `--expect-audio` | exit 1 unless playAudio comes back (implies --bidirectional); the test audio is a tone, not speech, so only an echo-style agent or one that speaks first answers |
| `--insecure` | skip TLS verification (self-signed dev certs only) |
| `--rate <int>` | sample rate in Hz (mulaw: 8000; l16: 8000 or 16000) (default 8000) |
| `--to <string>` | WebSocket URL of the endpoint to test (ws\:// or wss\://, required) |

```bash theme={null}
plivo voice streams test --to wss://my-agent.example.com/ws
plivo voice streams test --to wss://localhost:7860/ws --insecure   # self-signed dev cert
plivo voice streams test --to wss://my-agent.example.com/ws --bidirectional
plivo voice streams test --to ws://localhost:8765/ws --expect-audio -o json   # exit 1 unless playAudio comes back: gates a CI job
```

* The input is a 1 kHz tone, not speech. If no `playAudio` returns, an agent that waits for speech may stay silent even when it works on real calls.
* A `wss://` URL that reaches a server without TLS fails with a hint to use `ws://` for a local server.
* On normal completion the test sends `stop` before closing. A failed write ends it with exit code `3`, and so does an endpoint that closes the stream before `stop`; the error gives the frame it closed at and the close code and reason.

### Signed like a real stream

When you are logged in, the test signs the WebSocket upgrade as Plivo signs a stream, with `X-Plivo-Signature-V3` keyed with your profile's auth token, so an endpoint that checks Plivo's signature lets it in. `--profile` signs for another account; logged out, the upgrade is unsigned. An endpoint that refuses the upgrade with 401 or 403 fails the test with `AUTH_FORBIDDEN` and exit code `2`.

### Read the result in a script or CI

With `-o json`, the test prints one result on stdout and its progress on stderr. `--expect-audio` makes it exit `1` when no `playAudio` comes back, so it can gate a CI job.

| Field | What it holds |
| - | - |
| `connected`, `handshake_sent` | Whether the WebSocket opened and the `start` frame went out |
| `latency_ms` | Time to open the WebSocket connection |
| `frames_sent` | Audio frames sent |
| `frames_read_back` | Frames your server sent back (with `--bidirectional`) |
| `play_audio_frames` | `playAudio` frames that carried audio |
| `first_response_ms` | Time from the first frame sent to the first `playAudio` back; `null` when none came |
| `signed` | Whether the upgrade carried Plivo's signature |
| `warnings[]` | Anything unusual the test noticed |

## Simulate a call to your answer URL

`voice streams simulate` does what Plivo does when a call reaches your answer URL, without placing a call. Requires Plivo CLI v1.3.2 or later.

1. It requests `--answer-url` with the fields Plivo sends (`CallUUID`, `From`, `To`, `Direction`, `CallStatus`), signed with `X-Plivo-Signature-V3` when you are logged in.
2. It checks the reply: a 2xx within Plivo's 15 seconds, its content type, and Plivo XML with a `<Stream>`.
3. It opens the WebSocket in the `<Stream>` and streams a tone in its `contentType`, as `voice streams test` does.

```bash theme={null}
plivo voice streams simulate --answer-url https://agent.example.com/answer
plivo voice streams simulate --answer-url https://agent.example.com/answer --xml-only
plivo voice streams simulate --answer-url https://agent.example.com/answer --answer-method GET --to +14155551234 -o json
```

```text theme={null}
Simulated call <call_uuid>: POST https://agent.example.com/answer (signed)
CHECK              STATUS  DETAIL
answer_url         pass    HTTP 200 in 42 ms
content_type       pass    application/xml
xml                pass    <Response> with Stream
stream             pass    wss://agent.example.com/ws
statusCallbackUrl  warn    not set: nothing tells you when a stream drops (DroppedStream)
websocket          pass    connected in 35 ms; 150 frames sent, 150 back (150 playAudio); first playAudio after 20 ms

result: pass
```

| Flag | Description |
| - | - |
| `--answer-url <string>` | the answer URL to request, as Plivo would (required) |
| `--answer-method <string>` | GET\|POST, as set on the application (default "POST") |
| `--direction <string>` | inbound\|outbound, sent as Direction (default "inbound") |
| `--from <string>` | the caller, sent as From (default "+14155551234") |
| `--to <string>` | the number called, sent as To (default "+14155555678") |
| `--xml-only` | stop after the answer URL and its XML: open no WebSocket |
| `--duration`, `--expect-audio`, `--insecure` | as on `voice streams test`; `--expect-audio` fails the simulation unless playAudio comes back |

* Only the answer document is read: no `<Redirect>` or action URL is followed. A Plivo-hosted answer URL, such as a flow built in the console, is refused with nothing sent, because a request there would run it for real.
* A pass means your server answers your machine as Plivo needs. It doesn't prove that Plivo can reach your server, or that a caller hears the audio.
* Exit codes: `0` on a pass, `1` when a check fails, `2` when the WebSocket refuses the upgrade, `3` when a host can't be reached or the WebSocket closes before the `stop` frame.
* With `-o json` it prints `checks[]` and the stream summary under `stream`, with the same fields as `voice streams test`. When a check fails it prints only the error, with `checks[]` in `error.context`.

## Check a number is ready for a call

`voice streams readiness` checks what a streamed call needs on your account, then simulates the call as `voice streams simulate` does. It places no call and changes nothing. Requires Plivo CLI v1.3.2 or later.

**Inbound:** give the number.

```bash theme={null}
plivo voice streams readiness +14155551234
plivo voice streams readiness +14155551234 --xml-only -o json
```

It checks that the number is on your account with voice enabled, that an Indian (+91) number has an accepted compliance application, and that the number is routed to an application with an answer URL, then simulates the call with the application's method. A missing fallback answer URL or hangup URL, and a development tunnel, are warnings. A host Plivo can't reach, such as your own machine or a private address, fails.

```text theme={null}
CHECK                STATUS  DETAIL
number               pass    +14155551234 is on this account
voice                pass    voice is enabled
country              info    no country-specific check applies to United States
application          pass    routed to application "my-agent"
fallback_answer_url  warn    not set: when the answer URL fails, the call ends with 7011 instead of trying another URL
hangup_url           pass    https://agent.example.com/hangup
```

The simulated call's checks and `result: ready` follow.

**Outbound:** give the caller ID, the number to call and the answer URL.

```bash theme={null}
plivo voice streams readiness --from +14155551234 --to +14155555678 \
  --answer-url https://agent.example.com/answer --answer-method POST
```

```text theme={null}
CHECK            STATUS  DETAIL
caller_id        pass    +14155551234 is a number on this account
voice            pass    voice is enabled
country          info    no country-specific check applies to +14155551234
geo_permissions  info    not checked, as no API reports them: Voice > Geo Permissions in the console, https://cx.plivo.com/geo-permissions/voice
balance          pass    cash_credits is above 0
concurrency      info    not checked, as no API reports the concurrent call limit: Organization settings > Account limits in the console, https://cx.plivo.com/organization-settings/limits
```

The caller ID must be a number on your account with voice enabled, or a verified caller ID. An Indian caller ID needs an accepted compliance application and can call only Indian numbers. The account needs cash credits. Geo permissions and the concurrent call limit have no API, so info lines say where to check them in the console. The simulated call follows, as for inbound.

| Readiness option | Meaning |
| - | - |
| `[number]` | Inbound: your rented number. Uses the application's answer URL and method. |
| `--from <number>` | Outbound: your caller ID. Use with `--to` and `--answer-url`. |
| `--to <number>` | Outbound: the destination number. |
| `--answer-url <url>` | Outbound: your public answer URL. |
| `--answer-method <method>` | Outbound: `GET` or `POST`; default `GET`. `voice calls make` uses POST for a custom answer URL and GET for its built-in demo, so pass `--answer-method POST` to both when your handler expects POST. |
| `--xml-only`, `--duration`, `--expect-audio`, `--insecure` | As on `simulate`. |

| `result` | Meaning |
| - | - |
| `ready` | Every check passed, and so did the simulated call |
| `not_ready` | A check failed. The command exits `1` |
| `unknown` | Nothing failed, but a check could not be made or the call was not simulated. The command can exit `0`; inspect `result` as well as the exit code. |

* A Plivo-hosted answer URL, such as a flow built in the console, is not requested in either direction: the result is `unknown`. `--dry-run` makes the account reads but sends nothing to the answer URL.
* With `-o json` it prints `checks[]` and the simulation. When a check fails it prints only the error, with both in `error.context`.

## The audio contract

The XML attribute and the WebSocket frame spell the audio format differently, which is easy to get wrong. `<Stream>` has one combined `contentType` attribute holding the codec and sample rate. There is no `sampleRate` attribute on `<Stream>`.

```xml theme={null}
<Response>
  <Stream bidirectional="true" contentType="audio/x-mulaw;rate=8000">wss://your-agent.example.com/ws</Stream>
</Response>
```

The WebSocket `start` frame spells them out separately, under `mediaFormat`:

```json theme={null}
{"event":"start","start":{"streamId":"...","callId":"...","accountId":"...",
  "mediaFormat":{"encoding":"audio/x-mulaw","sampleRate":8000,"channels":1}}}
```

A codec defines how audio samples are encoded; the sample rate is samples per second, in hertz (Hz). The l16 media type is `audio/x-l16`, not `audio/l16`. Exactly three combinations are supported:

| `contentType` | `--codec` | `--rate` | Bytes per 20 ms frame |
| - | - | - | - |
| `audio/x-mulaw;rate=8000` | `mulaw` | `8000` | 160 |
| `audio/x-l16;rate=8000` | `l16` | `8000` | 320 |
| `audio/x-l16;rate=16000` | `l16` | `16000` | 640 |

There is no mu-law 16 kHz stream. Any other pair is rejected before the connection opens, with `BAD_FLAG` and exit code 1, so a bad combination fails on your laptop instead of as a dropped stream mid-call. `--codec l16` sends real little-endian 16-bit pulse-code modulation (PCM) audio, so an agent that decodes the payload sees a genuine waveform.
