For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.telcoflow.com/reference/llms.txt. For full documentation content, see https://docs.telcoflow.com/reference/llms-full.txt.

# Error Handling

The SDK provides a hierarchy of custom exceptions for robust error handling. Catch `WSSError` to handle any SDK-raised error, or catch specific subclasses when you need call- or buffer-level recovery.

## Exception Hierarchy

```text
WSSError (base)
|- WSSConnectionError          # Connection-related errors
|- WSSAuthenticationError      # Authentication failures
|- WSSMediaError               # Media connection errors
|- BufferFullError             # Outgoing audio buffer is full
|- BufferClosedError           # Outgoing audio buffer has already closed
`- WSSCallError                # Call-related errors
   |- WSSCallClosedError       # Operation on a closed call
   |- WSSCallCommandError      # Server rejected the command
   `- WSSCallCommandTimeoutError  # Command response timeout
```

## Exception Details

| Exception | When It's Raised |
|---|---|
| `WSSError` | Base exception for all SDK errors |
| `WSSConnectionError` | The control or media connection fails |
| `WSSAuthenticationError` | API key or mTLS authentication fails |
| `WSSMediaError` | The media connection cannot be established or drops unexpectedly |
| `BufferFullError` | The outgoing audio buffer reaches capacity |
| `BufferClosedError` | Your code tries to write audio after the buffer has already closed |
| `WSSCallError` | Base class for call-specific errors |
| `WSSCallClosedError` | You attempted an operation on a call that has already ended |
| `WSSCallCommandError` | The server rejected a command, exposing `error_code` and `error_message` |
| `WSSCallCommandTimeoutError` | The client timed out waiting for a command response |

## Example

```python
from telcoflow_sdk.exceptions import (
    BufferClosedError,
    BufferFullError,
    WSSCallClosedError,
    WSSCallCommandError,
    WSSError,
)
import telcoflow_sdk.events as events

@client.on(events.INCOMING_CALL)
async def handle_call(call: ActiveCall):
    try:
        await call.answer()

        async for chunk in call.audio_stream():
            response = await ai_model.generate(chunk)
            try:
                await call.send_audio(response)
            except BufferFullError:
                await call.clear_send_audio_buffer()
            except BufferClosedError:
                break

    except WSSCallClosedError:
        print("Call was already closed")
    except WSSCallCommandError as exc:
        print(f"Command failed: {exc.error_code} {exc.error_message}")
    except WSSError as exc:
        print(f"SDK error: {exc}")
```

## Handling `CALL_UNANSWERED` for `connect()`

When you call `connect()`, the callee may not answer within the ring window. In that case the SDK raises `WSSCallCommandError` with `error_code == "CALL_UNANSWERED"` and the call remains active, so you can retry, try a different branch in your flow, or end the call.

```python
from telcoflow_sdk import WSSCallCommandError
import telcoflow_sdk.events as events

@client.on(events.INCOMING_CALL)
async def assistant_flow(call: ActiveCall):
    await call.answer()
    try:
        await call.connect(ring_time_seconds=30)
    except WSSCallCommandError as exc:
        if exc.error_code == "CALL_UNANSWERED":
            await call.disconnect()
        else:
            raise
```

## Best Practices

- **Catch specific exceptions first** - Put `WSSCallClosedError` or buffer exceptions before `WSSError`
- **Handle buffer pressure in the audio loop** - Recover from `BufferFullError` where you are calling `send_audio()`
- **Expect race conditions near call shutdown** - `BufferClosedError` and `WSSCallClosedError` can happen when a caller hangs up mid-response
- **Use `error_code` for command-specific recovery** - `CALL_UNANSWERED` is the clearest example for `connect()` flows
- **Choose the right shutdown method** - Use `close()` carefully because its behavior depends on call state: after `connect()` the agent leaves, but after `answer()` without `connect()` the caller is disconnected too. Use `disconnect()` when you explicitly want the whole call to end

## Next Steps

- [API Reference](/reference/api-reference) - Full method reference
- [Advanced Topics](/reference/advanced-topics) - Logging, concurrency, and operational notes
- [Call States](/concepts/call-states-and-lifecycle) - Valid states for each command