Metadata-Version: 2.4
Name: vaani-sdk
Version: 0.3.0
Summary: Python SDK for the Vaani External API
License: MIT
Keywords: ai,calls,sdk,vaani,voice
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24.0
Requires-Dist: pydantic>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Description-Content-Type: text/markdown

# vaani-sdk

Python SDK for the [Vaani](https://vaanivoice.ai) API. Authenticate once with your API key and call all available endpoints from clean, typed Python methods — both synchronous and asynchronous.

## Installation

```bash
pip install vaani-sdk
```

Or install directly from source:

```bash
git clone https://github.com/your-org/vaani-sdk
cd vaani-sdk
pip install -e .
```

## Quick Start

```python
from vaani_sdk import VaaniClient

with VaaniClient(api_key="YOUR_API_KEY") as client:
    # Telephony call
    result = client.trigger_call(
        agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
        contact_number="+919876543210",
        name="Rahul",
    )
    print(result.call_id)  # "outbound-1776774443-863aa698"

    # WebRTC session (same endpoint, different medium)
    result = client.trigger_call(
        agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
        medium="webrtc",
        primary_language="en",
    )
    print(result.token)           # LiveKit participant JWT
    print(result.connection_url)  # wss://livekit.vaaniresearch.com
```

## Configuration

| Parameter  | Description                      | Default                     |
| ---------- | -------------------------------- | --------------------------- |
| `api_key`  | Your Vaani API key (`X-API-Key`) | _(required)_                |
| `base_url` | Base URL of the Vaani API        | `https://api.vaanivoice.ai` |
| `timeout`  | Request timeout in seconds       | `120.0`                     |

```python
client = VaaniClient(
    api_key="YOUR_API_KEY",
    base_url="https://api.vaanivoice.ai",
    timeout=60.0,
)
```

### Getting your API key

1. Log in to **[app.vaanivoice.ai](https://app.vaanivoice.ai)**
2. Navigate to **Settings > API Keys**
3. Click **Generate API Key**, give it a descriptive name, and copy the key immediately

Your key is shown only once at creation time. Store it securely in an environment variable or secrets manager — never commit it to version control.

```python
import os
from vaani_sdk import VaaniClient

client = VaaniClient(api_key=os.getenv("VAANI_API_KEY"))
```

---

## API Reference

### `client.health()`

Check the health of the Vaani API.

```python
resp = client.health()
print(resp.status)   # "healthy"
print(resp.service)  # "vaani-external-api"
```

---

### `client.trigger_call(...)`

Unified endpoint for **telephony** outbound calls and **WebRTC** voice sessions.
Select the mode with the `medium` parameter.

#### Telephony

| Parameter          | Type | Required | Description                                                                 |
| ------------------ | ---- | -------- | --------------------------------------------------------------------------- |
| `agent_id`         | str  | Yes      | Agent UUID from [app.vaanivoice.ai](https://app.vaanivoice.ai)              |
| `medium`           | str  | No       | `"telephony"` (default)                                                     |
| `contact_number`   | str  | Yes      | Phone number in E.164 format (e.g. `+919876543210`)                         |
| `name`             | str  | Yes      | Customer display name                                                       |
| `voice`            | str  | No       | Override the agent's default voice for this call                            |
| `metadata`         | dict | No       | Template variables injected into the agent's prompt at call time            |
| `outbound_number`  | str  | No       | Override the caller ID (E.164). Must be configured in Telephony settings    |
| `sip_trunk_id`     | str  | No       | SIP trunk identifier                                                        |
| `test_mode`        | bool | No       | When `True`, simulates the call without placing it                          |
| `dnd_check_skipped`| bool | No       | Skip Do-Not-Disturb registry check                                          |
| `modify_agent`     | [ModifyAgentConfig](#modifyagentconfig) | No | Inline agent config overrides for this call |

```python
result = client.trigger_call(
    agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
    contact_number="+919876543210",
    name="Rahul Sharma",
    metadata={
        "customer_name": "Rahul Sharma",
        "property_name": "Prateek Laurel",
    },
    outbound_number="+919873446283",
)
print(result.call_id)  # "outbound-1776774443-863aa698"
print(result.raw)      # full API response dict
```

Telephony response shape:

```json
{
  "success": true,
  "message": "Call initiated successfully",
  "output": {
    "agent_name": "my-sales-agent",
    "call_id": "outbound-1776774443-863aa698",
    "live_captions_url": "wss://api.vaanivoice.ai/api/live-transcripts/ws/outbound-..."
  }
}
```

#### WebRTC

| Parameter               | Type  | Required | Default    | Description                                |
| ----------------------- | ----- | -------- | ---------- | ------------------------------------------ |
| `agent_id`              | str   | Yes      |            | Agent UUID                                 |
| `medium`                | str   | Yes      |            | `"webrtc"`                                 |
| `voice_gender`          | str   | No       | `"female"` | `"male"` or `"female"`                     |
| `primary_language`      | str   | No       | `"hi"`     | Primary language code (e.g. `"en"`, `"hi"`)|
| `secondary_language`    | str   | No       | `"en"`     | Secondary language code                    |
| `welcome_message`       | str   | No       | None       | Custom opening message                     |
| `welcome_interruptible` | bool  | No       | `True`     | Can the user interrupt the welcome message |
| `bg_noise_enabled`      | bool  | No       | `False`    | Enable background noise                    |
| `bg_noise_volume`       | int   | No       | `60`       | Background noise volume (0–100)            |
| `voice_speed`           | float | No       | `1.0`      | Voice speed multiplier (0.6–1.4)           |
| `metadata`              | dict  | No       | `{}`       | Arbitrary key-value pairs                  |
| `modify_agent`          | [ModifyAgentConfig](#modifyagentconfig) | No | None | Inline agent overrides |

```python
result = client.trigger_call(
    agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
    medium="webrtc",
    primary_language="en",
    voice_gender="female",
    welcome_message="Hi! How can I help you today?",
)
print(result.token)            # LiveKit participant JWT
print(result.room_name)        # "room_<agent_id>_<hash>"
print(result.connection_url)   # "wss://livekit.vaaniresearch.com"
print(result.live_captions_url)# WebSocket URL for live transcription
```

WebRTC response shape:

```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "room_name": "room_d62cc9bd-2f51-4581-9f38-6c3b7d58a988_abc123",
  "agent_name": "my-agent",
  "connection_url": "wss://livekit.vaaniresearch.com",
  "live_captions_url": "ws://api.vaanivoice.ai/api/live-transcripts/ws/room_..."
}
```

#### ModifyAgentConfig

Inline agent configuration overrides applied for the duration of this call only. Works for both mediums.

```python
from vaani_sdk import VaaniClient
from vaani_sdk.models import ModifyAgentConfig

result = client.trigger_call(
    agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
    contact_number="+919876543210",
    name="Rahul",
    modify_agent=ModifyAgentConfig(
        persona={
            "identity": {
                "system_prompt": "You are a concise support agent. Keep answers under 2 sentences."
            }
        },
        training={
            "know_how": {
                "faq": {"What are your hours?": "9 AM to 5 PM."}
            }
        },
    ),
)
```

| Field        | Type | Description                    |
| ------------ | ---- | ------------------------------ |
| `persona`    | dict | Persona section override       |
| `training`   | dict | Training section override      |
| `experience` | dict | Experience section override    |
| `analysis`   | dict | Analysis section override      |

---

### `client.get_call_history(page, page_size)`

Retrieve paginated call history for the authenticated user's workspace.

| Parameter   | Type | Default | Description                |
| ----------- | ---- | ------- | -------------------------- |
| `page`      | int  | 1       | Page number (1-indexed)    |
| `page_size` | int  | 50      | Records per page (max 200) |

```python
history = client.get_call_history(page=1, page_size=20)

print(history.pagination.total)        # e.g. 1028
print(history.pagination.total_pages)  # e.g. 21

for call in history.data:
    print(call.call_id, call.raw.get("call_status"), call.raw.get("duration_ms"))
```

---

### `client.get_transcript(call_id)`

Retrieve the full conversation transcript for a completed call.

```python
transcript = client.get_transcript("outbound-1776774443-863aa698")
print(transcript.transcript)
```

---

### `client.get_call_details(call_id)`

Get detailed call information including transcript, extracted entities, evaluation, and AI-generated summary.

```python
details = client.get_call_details("outbound-1776774443-863aa698")
print(details.raw.get("summary"))
print(details.raw.get("entity"))
print(details.raw.get("call_eval_tag"))
```

---

### `client.stream_audio(call_id)`

Download the audio recording for a call (`audio/ogg` or `audio/mpeg`).

```python
audio = client.stream_audio("outbound-1776774443-863aa698")
with open("recording.ogg", "wb") as f:
    f.write(audio.content)

# Stream in chunks for large files
with open("recording.ogg", "wb") as f:
    for chunk in client.stream_audio_iter("outbound-1776774443-863aa698"):
        f.write(chunk)
```

---

### `client.create_agent(agent_display_name, config)`

Create a new agent.

```python
result = client.create_agent(
    agent_display_name="my-support-bot",
    config={
        "persona": {
            "identity": {
                "system_prompt": "You are a helpful support agent."
            }
        }
    },
)
print(result.agent_id)    # "33ac5907-0e44-43c4-934e-08e335cca6ee"
print(result.agent_name)  # "vaani123my-support-bot"
```

---

### Agent config update methods

Partially update any section of an agent's configuration:

```python
# Persona (identity, senses, actions, memories)
client.update_agent_persona(agent_id, persona={...})

# Training (knowledge base, FAQ, guardrails)
client.update_agent_training(agent_id, training={...})

# Experience (conversational settings, idle/end-call behavior)
client.update_agent_experience(agent_id, experience={...})

# Analysis (dispositions, evaluations, data extraction)
client.update_agent_analysis(agent_id, analysis={...})

# Deployment (phone call types, inbound/outbound numbers)
client.update_agent_deployment(agent_id, deployment={...})
```

Each returns `AgentConfigResponse` with `agent_id`, `agent_display_name`, and `raw` (full merged config).

---

## WebRTC Sessions

The `client.sessions` namespace provides the full session-based WebRTC flow for embedding voice chat in your own UI.

### Flow

1. `client.sessions.create(...)` — returns a `session_token` and embeddable `session_url`
2. `client.sessions.connect(session_token)` — exchange for LiveKit credentials
3. Pass `creds.lk_url` + `creds.lk_token` to the **LiveKit client SDK** in your browser/app
4. Optionally watch real-time events via `client.sessions.ws_url(session_token)`
5. `client.sessions.disconnect(session_token)` — end the session

```python
with VaaniClient(api_key="YOUR_API_KEY") as client:
    session = client.sessions.create(
        agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
        ui_type="widget",
        grace_period=60,
        session_ttl=1800,
        metadata={"user_id": "user_123"},
    )
    print(session.session_token)
    print(session.session_url)

    creds = client.sessions.connect(session.session_token)
    print(creds.lk_url)    # "wss://livekit.vaaniresearch.com"
    print(creds.lk_token)  # LiveKit participant JWT
    print(creds.room_name)

    client.sessions.disconnect(session.session_token, explicit_end=True)
```

### Sessions reference

| Method | Description | Returns |
| --- | --- | --- |
| `sessions.create(agent_id, ...)` | Create a session token + embeddable URL | `WebRTCSession` |
| `sessions.connect(token)` | Exchange token for LiveKit credentials | `WebRTCSessionConnect` |
| `sessions.get(token)` | Fetch current session state | `WebRTCSessionStatus` |
| `sessions.disconnect(token, explicit_end=False)` | Signal disconnect or end session | `WebRTCSessionStatus` |
| `sessions.ws_url(token)` | Build WebSocket URL for real-time events | `str` |

---

## Resource Namespaces

### `client.calls`

```python
with VaaniClient(api_key="YOUR_API_KEY") as client:
    # Trigger a telephony call
    resp = client.calls.trigger(
        agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
        contact_number="+919876543210",
        name="Rahul",
        medium="telephony",
        dnd_check_skipped=True,
    )
    print(resp.call_id)

    # Trigger a WebRTC session
    resp = client.calls.trigger(
        agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
        contact_number="+919876543210",
        medium="webrtc",
    )
    print(resp.token)

    # Get call details, transcript, history
    details = client.calls.get_details(resp.call_id)
    transcript = client.calls.get_transcript(resp.call_id)
    history = client.calls.get_history(page=1, page_size=50)
```

### `client.campaigns`

Manage outbound campaigns:

```python
with VaaniClient(api_key="YOUR_API_KEY") as client:
    campaign = client.campaigns.create(
        name="Q3 Outreach",
        agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
        csv_content=open("contacts.csv", "rb").read(),
    )
    client.campaigns.start(campaign.id)
    status = client.campaigns.get_status(campaign.id)
    print(status.status)   # "running"

    client.campaigns.pause(campaign.id)
    client.campaigns.resume(campaign.id)
    client.campaigns.stop(campaign.id)
```

### `client.agents`

```python
with VaaniClient(api_key="YOUR_API_KEY") as client:
    agents = client.agents.list()
    for agent in agents:
        print(agent.id, agent.agent_name)

    agent = client.agents.get("d62cc9bd-2f51-4581-9f38-6c3b7d58a988")
    print(agent.agent_language)
```

---

## Async Usage

Every method has an exact async counterpart on `AsyncVaaniClient`:

```python
import asyncio
from vaani_sdk import AsyncVaaniClient

async def main():
    async with AsyncVaaniClient(api_key="YOUR_API_KEY") as client:
        # Telephony
        result = await client.trigger_call(
            agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
            contact_number="+919876543210",
            name="Rahul",
        )
        print(result.call_id)

        # WebRTC
        result = await client.trigger_call(
            agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988",
            medium="webrtc",
            primary_language="en",
        )
        print(result.token)

        history = await client.get_call_history(page=1, page_size=5)
        session = await client.sessions.create(
            agent_id="d62cc9bd-2f51-4581-9f38-6c3b7d58a988"
        )
        creds = await client.sessions.connect(session.session_token)

asyncio.run(main())
```

---

## Error Handling

All exceptions inherit from `VaaniError` and carry a `status_code` attribute.

| Exception | HTTP status | Cause |
| --- | --- | --- |
| `AuthenticationError` | 401 | Invalid or missing API key |
| `ForbiddenError` | 403 | Operation not allowed (e.g. DND block, wrong key) |
| `NotFoundError` | 404 | Resource does not exist |
| `SessionExpiredError` | 410 | WebRTC session has ended or expired |
| `InsufficientBalanceError` | 402 | Wallet balance too low |
| `ValidationError` | 422 | Request parameters failed validation |
| `RateLimitError` | 429 | Too many requests |
| `ServerError` | 5xx | Vaani server-side error |
| `TimeoutError` | — | Request timed out |
| `ConnectionError` | — | Could not reach the API |

```python
from vaani_sdk import VaaniClient, ForbiddenError, VaaniError

with VaaniClient(api_key="YOUR_API_KEY") as client:
    try:
        result = client.trigger_call(
            agent_id="my-agent",
            contact_number="+919876543210",
            name="Test",
        )
    except ForbiddenError as exc:
        # e.g. "Call blocked: destination number is in DND registry"
        # Use dnd_check_skipped=True to bypass DND in test environments
        print(f"Forbidden: {exc}")
    except VaaniError as exc:
        print(f"API error {exc.status_code}: {exc}")
```

---

## Development

### Setup

```bash
# Install with dev extras (uses uv)
uv sync --dev

# Or with pip
pip install -e ".[dev]"
```

### Running tests

```bash
# Unit tests (no server required — all mocked)
uv run pytest tests/test_client.py tests/test_resources.py -v

# Integration tests (requires local API stack)
uv run pytest tests/integration/ -v -s
```

### Integration test setup

The integration tests target a locally-running `vaani-external-api` (port 8001) which proxies to `vaani-backend-new` (port 8500).

```bash
# 1. Start vaani-backend-new
cd vaani-backend-new && docker compose -f docker-compose.dev.yaml up -d

# 2. Start vaani-external-api
cd external/vaani-external-api && docker compose up -d

# 3. Run integration tests
cd external/vaani-sdk
VAANI_API_KEY=vaani_... VAANI_AGENT_ID=<uuid> uv run pytest tests/integration/ -v -s
```

Environment variables for integration tests:

| Variable        | Default                                        | Description             |
| --------------- | ---------------------------------------------- | ----------------------- |
| `VAANI_API_KEY` | `vaani_c1fc7e33c05632830ffe16da5e314b0d`       | API key                 |
| `VAANI_BASE_URL`| `http://localhost:8001`                        | External API base URL   |
| `VAANI_AGENT_ID`| `d62cc9bd-2f51-4581-9f38-6c3b7d58a988`        | Agent UUID for tests    |
