Skip to main content

bloonio_voice_relay_client

Client SDK for bloonio_voice_relay — the Bloonio voice-assistant PaaS. Backend integration for tenants: mint per-call tokens for your app's WebRTC/WS client, manage voice assistants (openai / gemini / sandbox), and fetch sessions and transcripts. Framework-agnostic (httpx + pydantic), bearer-key auth only.

Install

pip install bloonio-voice-relay-client

Two-minute integration

# .env
BLOONIO_VOICE_BASE_URL=$BASE_URL
BLOONIO_VOICE_API_KEY=bvr_...

Unlike the sibling wa / mail / auth / chat relay clients (each keyed on a tenant_id + tenant_secret pair), the voice tenant plane accepts exactly one credential: Authorization: Bearer bvr_.... No HMAC signing, no secondary auth mode — BLOONIO_VOICE_API_KEY is the only secret this SDK needs.

This SDK is the backend side — read this before writing any integration code. VoiceRelayClient / AsyncVoiceRelayClient hold your tenant's bvr_ key and must never run in a browser or ship inside a mobile app. The pattern your app's WebRTC/WS client actually needs: your server calls mint_call_token(assistant_id=...) and hands the resulting call_token to your app; the app then talks to the relay's public plane directly (/api/v1/public/connect/session, the call-token-gated stream socket, /api/v1/public/fetch/transcript) using only that token. The bvr_ key itself never leaves your backend.

The token is session-scoped — it authorises exactly one session (connect it, stream it, read its transcript) and nothing else — and reusable within its TTL (CALL_TOKEN_TTL_SECONDS, 300s by default server-side): it is not single-use. The TTL bounds how long the token can be used to start something (connect the call, open the stream socket); reading the transcript of a call that's already connected is honoured past that TTL for as long as the session is still live.

Quickstart

# main.py — construct the client once at startup and register it as the singleton
from fastapi import FastAPI
from bloonio_voice_relay_client import VoiceRelayClient, VoiceRelaySettings, set_voice_client

app = FastAPI()
set_voice_client(VoiceRelayClient(VoiceRelaySettings()))   # reads the BLOONIO_VOICE_* env vars above
# anywhere else — a route, a worker task, a management command
from bloonio_voice_relay_client import get_voice_client

voice = get_voice_client()
voice.whoami()   # confirms the key resolves: {"tenant_id": ..., "tenant_name": ...}

# provider="sandbox" is the one provider that costs nothing and calls nothing to try —
# no OpenAI/Gemini credentials needed, zero outbound network calls server-side. `openai`
# and `gemini` are the real providers; swap `provider=` once you're wiring an actual call.
assistant = voice.create_assistant(
    name="Front desk",
    provider="sandbox",
    instructions="You are a friendly assistant for Acme Freight.",
)
print(assistant["assistant_id"], assistant["status"])

# Mint a call token for THIS assistant and hand `call_token` to your app — see
# "This SDK is the backend side" above. Never hand the app `voice` itself.
token = voice.mint_call_token(assistant_id=assistant["assistant_id"])
print(token["call_token"], token["expires_at"])

session = voice.fetch_session(session_id=token["session_id"])
print(session["status"])   # "created" -> "active" -> "ended" | "orphaned"

for turn in voice.fetch_transcript(session_id=token["session_id"]):
    print(turn["role"], turn["text"])

Errors. Every method raises VoiceRelayError on any non-2xx response: .status_code, .message (the relay's message, or the raw response text when there's no structured message to show), and .body (the parsed JSON envelope, or None when the response body wasn't JSON at all). .code also exists — extracted from the envelope's data.code when present, the same mechanism bloonio_wa_relay_client uses — but is always None against the real relay today: no voice tenant-plane route currently populates it, so an identifier like QUOTA_EXCEEDED rides inside .message instead ("QUOTA_EXCEEDED: monthly minutes exhausted"), not as a separate field.

from bloonio_voice_relay_client import VoiceRelayError

try:
    voice.mint_call_token(assistant_id=assistant["assistant_id"])
except VoiceRelayError as e:
    print(e.status_code, e.message)

Reuse one VoiceRelayClient per process — it wraps a single httpx.Client — and either call voice.close() when done or use it as a context manager: with VoiceRelayClient(...) as voice:. The async twin, AsyncVoiceRelayClient, has full method parity (await voice.whoami(), async with AsyncVoiceRelayClient(...) as voice: / await voice.aclose()).

Assistants

for a in voice.list_assistants():   # newest first, no pagination, capped at 200
    print(a["assistant_id"], a["name"], a["status"])

assistant = voice.fetch_assistant(assistant_id="asst_1")

update_assistant only edits name / instructions / voice / first_message / language / temperature / toolsprovider, pipeline, mode and model are fixed at creation and cannot be changed afterward; the update endpoint has no fields for them at all (create a new assistant instead of trying to migrate one in place). Fields you don't pass are left untouched, not cleared — there's no way to null a field through this route either:

updated = voice.update_assistant(assistant_id="asst_1", name="New name", temperature=0.5)

delete_assistant always returns None — the relay's delete response carries no data, only a confirmation message, so there's nothing to hand back:

voice.delete_assistant(assistant_id="asst_1")   # -> None

Sessions & calls

create_session and mint_call_token accept the identical assistant_id request — the difference is the response. create_session is for when your OWN backend will finish the call itself (see connect_session below); mint_call_token (Quickstart, above) is the one to reach for when you're instead handing the call off to your app's own WebRTC/WS client over the public plane.

session = voice.create_session(assistant_id="asst_1")

for s in voice.list_sessions():   # newest first, no pagination/filter, capped at 200
    print(s["session_id"], s["status"])

connect_session applies to provider="openai" sessions only (400 otherwise) and, unlike every other method here, performs a real, billable server-proxied SDP exchange with OpenAI the moment it succeeds — there is no sandbox/dry-run form of this one call. A gemini session instead streams over a websocket (/relay/stream/session), out of this SDK's scope. Only call it once you're intentionally starting a live OpenAI call, with an SDP offer your own WebRTC stack produced:

result = voice.connect_session(session_id=session["session_id"], sdp_offer="v=0\r\n...")
print(result["attached"], result["sdp_answer"])

Public surface

Every client method returns the relay's response data verbatim — a plain dict or list, never an instance of the schemas below. The schemas exist so you can validate/type a response yourself, e.g. Assistant.model_validate(res).

Symbol Kind Notes
VoiceRelayClient client (sync) wraps httpx.Client; reuse one per process
AsyncVoiceRelayClient client (async) wraps httpx.AsyncClient; async with / await .aclose()
VoiceRelaySettings settings reads BLOONIO_VOICE_* env vars (pydantic-settings); base_url / api_key required, request_timeout_seconds defaults to 10.0
VoiceRelayError error raised on any non-2xx; .status_code / .message / .body / .code (see "Errors" above)
AssistantProvider enum openai | gemini | sandbox
SessionStatus enum createdactiveended | orphaned
TurnRole enum user | assistant
AssistantTool schema one entry of Assistant.tools — the create-side hmac_secret is write-only and never echoed back, so this schema doesn't model it either
Assistant schema create_assistant() / fetch_assistant() / list_assistants() (one row) / update_assistant(); status is a plain str, always "active" in practice (deleted rows are never returned again)
CallToken schema mint_call_token()'s response shape
Session schema fetch_session() / list_sessions() (one row) — deliberately narrower than the raw dict; internal bookkeeping fields present after a call connects are ignored, not rejected, if you validate into this schema
ConnectSessionResult schema connect_session()'s response shape
TranscriptTurn schema one row of fetch_transcript()
set_voice_client / get_voice_client singleton app-startup wiring — see "Quickstart"

Voice ships no contract doc — the three enums above are read directly off bloonio_voice_relay's own source, not a published spec, and none of them are formal enum.Enum classes server-side. A future value the relay starts returning that isn't listed here raises pydantic.ValidationError out of .model_validate() rather than parsing leniently — client methods return the raw dict/list regardless of this, so it only bites if you opt into the schemas above.

License

Proprietary — Bloonio internal.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bloonio_voice_relay_client-0.1.0.tar.gz (30.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bloonio_voice_relay_client-0.1.0-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file bloonio_voice_relay_client-0.1.0.tar.gz.

File metadata

File hashes

Hashes for bloonio_voice_relay_client-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9a2191873ff253c1f7bdff8ed7cf2c5796174987a797f041fc478f87067456bc
MD5 4e9372d84d42aa58ef23f6928a07684c
BLAKE2b-256 eb8a9c1af89aeb9aae3f9ca1ac2c3e47f76b5dc97599ded54ae5fea2bec1942a

See more details on using hashes here.

File details

Details for the file bloonio_voice_relay_client-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for bloonio_voice_relay_client-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 101bcae34c8610058679102c80a2d264c4dc649de481da6b830749f06edbd6ac
MD5 19ff6f1f7e84065bd06495df04bdce76
BLAKE2b-256 3bb0c691aed6eda16180979b95d5d06977a95224dbdf705a5d976369294ac471

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page