Documentation
Everything you need to integrate HubUI into your products — from connecting a Brain to embedding voice agents in your front‑end.
Overview & Architecture
Understand how HubUI works and how your product fits in
The Big Picture
Your Agent is a fully autonomous conversational AI. It handles all normal dialogue — greetings, Q&A, follow-ups, multi-turn conversations — instantly and without blocking. It does not forward every conversation to your Brain.
The Brain is your optional, on-demand backend. It's only invoked when your System Instructions tell the Agent to use it — for deep reasoning, data lookups, API calls, or any custom logic you build.
✦Completely Non‑Blocking — Like a Real Human Conversation
When the Agent calls your Brain, the conversation does not freeze. The Agent keeps talking — it acknowledges the question instantly ("Sure, let me look that up"), gives natural follow-up cues while processing ("Still checking — one moment"), and the caller can interrupt at any time to change topic, ask something else, or correct their question — just like talking to a real person.
This is fundamentally different from traditional tool calling, where the AI goes silent, the caller hears dead air, and the entire conversation blocks until the function returns. That creates a robotic, turn-by-turn experience that feels nothing like a real phone call.
HubUI's architecture keeps the voice channel alive and natural at all times. Heartbeat messages, hold music, and instant acknowledgments run automatically — your Brain can take 5, 10, even 30+ seconds to process, and the caller never experiences awkward silence or loses the ability to speak.
Browser
Client SDK
Phone
SIP Trunking
Voice Agent
Autonomous Core
✦ Instant conversation
✦ Non-blocking & interruptible
✦ Natural like a human
✦ No Brain needed for
normal dialogue
System Instr.
say so
Your Brain
Optional · On-Demand
LLM inference
Database lookups
API calls
LangChain / LangGraph
Key insight: Your Agent is not a proxy to the Brain — and it is not a traditional tool-calling system. Most conversations flow entirely through the Agent with zero added latency. When the Brain is called, the conversation stays alive: the Agent speaks, the caller can interrupt, and there is never dead air. The Brain is only invoked when you explicitly configure it in your System Instructions — and even then, the caller experience remains completely natural.
The Building Blocks
Every HubUI integration is built from these components. The Agent is the autonomous core — everything else connects to it.
Voice Agent
Autonomous · Real-TimeYour AI persona that handles all conversation in real-time. It speaks, listens, understands, and responds — instantly and without blocking. Configured via System Instructions, voice selection, and behavior settings in the dashboard. The Agent handles greetings, Q&A, follow-ups, clarifications, and multi-turn dialogue on its own. No Brain required for normal conversation.
Brain
Optional · On-DemandYour custom Python backend. The Brain is NOT in the loop for every conversation — it's called on-demand, only when your System Instructions tell the Agent to use it. Use it for heavy lifting: LLM inference (OpenAI, Gemini, Claude), database lookups, REST API calls, LangChain/LangGraph pipelines, or any async logic. Processing can take seconds — that's fine. Heartbeats and hold music keep the caller engaged while the Brain works.
Client SDK
You embed in your appA JavaScript/TypeScript SDK you embed in your web app. It handles WebRTC audio, microphone access, and transcripts. Your users start voice or text sessions with an Agent directly from your product — no external tools or redirects. This is the entry point for browser-based users.
Phone Numbers
Entry point for callersAttach a phone number to your Agent so callers can dial in and talk to your AI. SIP trunk routing delivers inbound calls to the same Agent your web users interact with — same personality, same Brain, different channel.
Dashboard
Your control planeCreate and configure Agents, set up Brains, manage phone numbers, view call logs and analytics, handle billing — all from one place. The dashboard is where you configure everything; it's not in the conversation data path.
How Conversations Actually Work
There are two paths a conversation can take. Understanding this distinction is the most important concept in HubUI.
Path A — Normal Conversation
90%+ of interactions- 1
User speaks or types
A message is captured and routed to your Agent.
- 2
Agent responds instantly
The Agent understands the message and replies in real-time — no delay, no external calls.
- 3
No Brain involved
Zero added latency. The Agent handles greetings, Q&A from its System Instructions, follow-ups, clarifications, small talk, and multi-turn dialogue entirely on its own.
Path B — Brain-Assisted Query
Developer-triggered- 1
User asks something that matches a Brain trigger
The user's question matches a pattern you defined in your System Instructions (e.g., "What's the status of order #12345?").
- 2
Agent recognizes the intent and invokes your Brain
The Agent automatically sends the query to your Brain backend via WebSocket — along with the full conversation history.
- 3
Agent acknowledges instantly
While the Brain is processing, the Agent says something like "Sure, let me check on that" — automatically, with zero config needed from you.
- 4
Your Brain processes the request
This can take seconds — that's fine. Your Brain can call OpenAI/Gemini/Claude, query a database, hit a REST API, run a LangChain/LangGraph pipeline, or execute any async Python logic you write.
- 5
Brain returns → Agent speaks the answer
The Brain sends back the result. The Agent delivers it to the user naturally. Conversation continues (back to Path A).
If processing takes a while, the Brain can send heartbeats ("Still working on that...") and even start hold music to keep the caller engaged.
💡 The Mental Model
- Your Agent is not a proxy. It's a fully autonomous conversational AI with its own personality, knowledge, and real-time capabilities.
- The Brain is your escape hatch to custom logic. It bridges the Agent's conversational intelligence with YOUR data, YOUR APIs, YOUR models.
- System Instructions are the routing rules. They tell the Agent when to call the Brain and what to ask for. Without explicit instructions, the Agent never contacts the Brain — even if one is connected.
- Everything is non-blocking. The Agent continues naturally while the Brain processes in the background. Your users never experience dead air.
Two Ways to Connect Your Users
Browser — HubUI Client
Embed the @hubui/client SDK into your website or web app. Users can talk or chat with your agent directly in the browser — no downloads, no redirects, no phone number required.
Best for: onboarding flows, customer support widgets, interactive demos, and in‑app assistants.
Phone — Inbound Calls
Attach a phone number to your Agent in the dashboard. Callers dial your number and are immediately connected to your Agent.
Best for: business phone lines, appointment booking, and automated support.
PAYG Billing
HubUI uses one prepaid PAYG credit balance. Voice minutes, AI response turns, and phone numbers draw down from the same balance with no monthly plan selection.
Voice
$0.14 per minute
Messages
$0.01 per AI response
Phones
$2 per number monthly
Credit
Top up from Billing
Full rate and balance details are available on the Billing page in your dashboard.
Getting Started
Go from zero to a working voice agent in under five minutes
Phase 1 — Your First Agent (No Code Required)
Create a working Agent in under two minutes using only the dashboard. No Brain, no backend, no code — just System Instructions with your knowledge base.
Step 1 — Create an Agent
The Agent is your autonomous AI persona. It handles all conversation in real-time — greetings, Q&A, follow-ups, multi-turn dialogue — using the knowledge you give it in System Instructions.
- Navigate to the Agents page → Click "Create Agent".
- Set a name, pick a voice, and select your language.
- Write your System Instructions. This is where you define your Agent's personality, knowledge base, and rules. Put everything the Agent needs to know right here — company info, FAQs, product details, policies, how to greet callers, what to say when it doesn't know something.
- Click Save. Your Agent is now live and ready to answer questions based on the knowledge you provided.
That's it. Your Agent can now have full conversations using only the knowledge in its System Instructions — no Brain, no backend, no external resources needed. For many use cases, this is all you need.
Step 2 — Test It
Test your Agent right from the dashboard — no frontend app required.
Dashboard Test Call
Go to the Agents page → click the test button on your Agent card. Choose Voice Call (microphone required) or Text Only mode and start talking. Your Agent will respond using the knowledge from your System Instructions.
Phase 2 — Go Further (Optional)
Your Agent works great with just System Instructions. When you're ready for more — embedding in your app, deep reasoning with LLMs, database lookups, external APIs, or phone numbers — these are three independent paths you can take in any order.
Option A — Embed in Your App
Use the Client SDK to let users talk or chat with your Agent directly from your website or web app.
- Get a Client API Key — go to Settings → API Keys and generate one. It starts with
pk_live_.... - Copy your Agent ID from the Agents page.
- Install and connect:
npm install @hubui/client
import { HubUI } from '@hubui/client';
const session = await HubUI.connect({
agentId: 'your-agent-id',
apiKey: 'pk_live_xxxxx',
mode: 'voice', // or 'text'
userName: 'John Doe',
});
// Listen for real-time transcripts
session.on('transcript', (text, speaker, isFinal) => {
console.log(`${speaker}: ${text}`);
});
// When done
await session.disconnect();See the Client SDK section for text mode, error handling, mobile audio, and the full API reference.
Option B — Add a Brain for Deep Reasoning
A Brain is your custom Python backend that gives your Agent superpowers — LLM inference (OpenAI, Gemini, Claude), database lookups, REST API calls, LangChain/LangGraph pipelines, or any async logic. The Agent calls your Brain on-demand, only when your System Instructions tell it to.
- Go to the Brain page → Click "Create Brain". Enter a name (e.g.,
order_lookup) and your Brain's WebSocket URL. - Copy the Brain API Key (
br_...) — shown only once. - Build your Brain server:
pip install hubui-brain
# brain.py
from hubui_brain import Brain, QueryContext
brain = Brain(api_key="br_xxx", debug=True)
@brain.on_query()
async def handle(ctx: QueryContext):
# Your custom logic here — LLM call, DB lookup, API request, etc.
await ctx.reply(f"You asked: {ctx.query}")
brain.run(host="0.0.0.0", port=8080)- In your Agent's Brain tab, select your Brain from the dropdown.
- Critical: Update your Agent's System Instructions to define exactly when the Agent should call the Brain — what types of questions, conditions, or triggers should route to it. For example: "When the user asks about order status, tracking, or returns, use the
order_lookupbrain to retrieve the information. For all other questions, answer directly." Just selecting the Brain in the dropdown is not enough.
See the Brain SDK section for heartbeats, auto_process(), deployment guides (Cloud Run & Docker), and the full API reference.
Option C — Add a Phone Number
Let callers dial in and talk to your Agent over regular phone lines — same personality, same knowledge, same Brain (if connected).
- Go to the Phone Numbers page → Click "Buy Number".
- Choose a number and assign it to your Agent.
- Callers can now dial that number and talk to your Agent immediately.
See the Phone Numbers section for area code selection, SIP trunking, and number management.
Next Steps
Your Agent is up and running. Here's where to go deeper:
Dashboard Guide
Navigate every page in your HubUI dashboard
Overview Page (Home)
Your landing page after login. It gives you a live pulse of your entire HubUI deployment at a glance.
System Health Metrics (Last 24h)
Active Sessions
—
Brain Engagements
—
Brain Connect
— ms
Brain Processing
— s
Brain Error Rate
— %
Quick Actions
Create Agent
→ Agents
View Logs
→ Calls
Manage Numbers
→ Phone Numbers
API Keys
→ Settings
Recent Activity
Shows the last 5 calls with caller info, source badges (SIP or WebRTC), duration, and relative timestamps. Click "View all" to go to the full Calls page.
PAYG Usage Sidebar
Shows your current credit balance, period spend, voice minutes, message count, and phone-number costs. Low-credit states link back to Billing so credit can be added before service is interrupted.
Settings Page
Manage your profile, credentials, and API keys.
Profile
Display Name
How you appear in the dashboard
Company Name
Optional — for your reference
Timezone
12 timezone options (US-centric)
Your email is read-only — it cannot be changed after account creation.
Change Password
Available for email/password accounts only. If you signed in with Google OAuth, this section is hidden — your password is managed by Google.
Requires: current password, new password (min 6 characters), and confirmation.
API Keys
pk_live_{32 hex chars}.API Key Types Explained
HubUI uses two distinct API keys with different purposes and security levels. Understanding the difference is critical.
Client API Key
Format: pk_live_...
Goes in: Your frontend app (browser-side)
Purpose: Connects end-users to your agents via the Client SDK
Security: Safe for client-side code. Validated against your account and scoped to your agents. Store in env vars like NEXT_PUBLIC_HUBUI_API_KEY.
Storage: SHA-256 hashed on our servers — we never see the full key after creation
Brain API Key
Format: br_...
Goes in: Your backend server (server-side only)
Purpose: Authenticates your Brain to HubUI's real-time WebSocket servers
Security: Never expose to the frontend. Included in every WebSocket message your Brain sends for validation.
Lifecycle: Rotate (generates new key; old key stops immediately) · Revoke (permanent invalidation)
Create a Brain
Navigate to the Brain page in the sidebar to establish a backend connection.
- Click "Create Brain".
- Enter a name — 1–16 characters, lowercase letters, numbers, and underscores only (
^[a-z0-9_]+$). Example:orders_db. - Enter your Brain URL — the WebSocket endpoint where your Brain server listens (e.g.,
wss://my-brain.example.com). - Secondary URL (optional) — a fallback endpoint if the primary URL is unreachable.
- Description (optional) — a note for your own reference.
- Scope — choose "All Agents" or select specific agents that should have access to this Brain.
- Click Create. Copy the Brain API Key (
br_...) — it is shown once. Save it securely.
Brain Key Lifecycle
Reveal
Shown once on creation. Cannot be retrieved later.
Rotate
Generates a new key. Old key stops working immediately.
Revoke
Permanently invalidates the key. Brain can no longer authenticate.
Create an Agent & The System Instructions Rule
CRITICAL Requirement for Brains
Just assigning a Brain in the dropdown is not enough. To make your Agent actually use the Brain, you must explicitly call its exact name inside your Agent's System Instructions.
Example System Instruction:
"You are a helpful assistant. If the user asks about their account balance or order status, you must use the orders_database to retrieve the information."The Agent is the persona the user interacts with. Navigate to the Agents page:
- Click Create Agent and provide a Name.
- Select Voice & Language: Choose how you want the Agent to sound and what language it will converse in.
- Write your System Instructions: This dictates the personality and rules. Mention your Brain's exact name here if you want it invoked.
Default token limit: 1,500 system-instruction tokens. Enterprise accounts can receive custom limits. - Select the Brain: Choose the Brain you created from the dropdown menu.
- Save, and copy the Agent ID. You need this string for your frontend client setup.
Agent Configuration
Fine-tune voices, system instructions, and behaviour for each agent
Configuration Overview
An Agent is your autonomous conversational AI persona. Each Agent has its own voice, personality, rules, and optional Brain connection. Create Agents from the Agents page in the dashboard — click New Agent to open the configuration modal.
The configuration modal has 6 tabs, each controlling a different aspect of your Agent's behaviour:
Basic
Name, engine, voice, language, transcription
Voice
Speech speed (0.5x – 1.5x)
Behavior
Timeouts, silence handling, call protection
Instructions
Personality, rules, Brain routing
Brain
Connect or disconnect a Brain backend
Phone
Manage attached phone numbers (edit only)
Tab 1 — Basic Settings
Agent Name
1 – 100 characters. This is an internal label — callers don't see it.
Engine
Select the AI engine that powers conversations. Currently one option: hub-nabu-0.1-beta (Hub Nabu 0.1 Beta).
Language
Currently supported: en-US (English, United States).
Transcription
Toggle to enable or disable saving of call transcript content. When disabled, conversation text is not stored (privacy mode). When enabled, full conversation text is saved with each call.
Voice Catalog — Premium HD Voices
Select one of 7 premium HD voices. Each voice has a distinct personality and gender. Voice selection is made on the Basic tab.
| Voice | Gender | Personality |
|---|---|---|
| Aria | Female | Warm and bright |
| Atlas | Male | Informative and conversational |
| Blaze | Male | Confident and direct |
| Jade | Female | Clear and firm |
| Dash | Male | Upbeat and engaging |
| Cole | Male | Firm and deliberate |
| Luna | Female | Soft and bright |
Tab 2 — Voice Configuration
Adjust speech speed with a slider. A preview card shows the selected voice's name, gender, language, and personality description.
Minimum
0.5x (slowest)
Default
1.0x (normal)
Maximum
1.5x (fastest)
Step size is 0.05x. For example, valid values include 0.50, 0.55, 0.60, … 1.45, 1.50.
Tab 3 — Call Behavior
Control how your Agent handles silence, goodbyes, and runaway calls.
Silence Timeout
How long the Agent waits for the caller to speak before prompting them. Range: 20 – 999 seconds. Default: 30 s.
End Call Wait
Grace period after the Agent says goodbye before it actually hangs up. Range: 1 – 10 seconds. Default: 3 s.
Call Protection
Safeguards against excessively long or streaming calls that would burn through your minutes.
Max Call Duration
60 – 3,600 s (1 min – 1 hr). Default: 300 s (5 min).
Max Speech Duration
10 – 600 s. Must be less than Max Call Duration. Default: 120 s.
Tab 4 — System Instructions
System Instructions are the single most important configuration for your Agent. They define its personality, knowledge, rules, and — critically — when it should call your Brain.
Live Token Counter
A real-time counter in the top-right corner shows current / limit tokens. Exceeding your account limit blocks saving.
| Account Type | Max Tokens |
|---|---|
| PAYG default | 1,500 |
| Enterprise | Custom |
Critical — Brain Name Rule
If you connect a Brain to your Agent, you must mention the Brain's exact name in your System Instructions. Example: "Use the orders_db brain to look up order status." Just selecting the Brain in the dropdown is not enough — the Agent needs an explicit instruction telling it when and why to invoke the Brain.
Tab 5 — Brain Integration
Connect your Agent to a Brain backend for on-demand custom logic.
Brain Selector
Dropdown listing all Brains you've created. Select one to connect, or choose "No Brain Connected" to disconnect.
Brain Info Card
When a Brain is selected, a card displays its name and description with a disconnect button. If no Brains exist, a link directs you to the Brain page to create one first.
Tab 6 — Phone Numbers
Available only in edit mode (not when creating a new Agent). Lists all phone numbers currently attached to this Agent with a Detach button on each. Detaching moves the number to the unassigned pool — it stays in your account and can be re-attached to any Agent later.
In create mode, this tab shows a "Save Agent First" placeholder. Attach numbers from the page after creating the Agent.
Agent Status Management
Every Agent has one of three statuses. Change status from the Agent card's dropdown menu on the Agents page.
Live and accepting connections. Phone numbers attached to this Agent route inbound calls.
Agent exists but won't accept new connections. Inbound calls to attached numbers will not connect.
Fully shut down. No connections, no inbound calls. The Agent's configuration is preserved.
Status changes also update the phone number routing index — pausing or disabling an Agent stops its phone numbers from accepting calls immediately.
Deleting an Agent
If phone numbers are still attached, deletion is blocked. The dashboard shows an error message listing which numbers are still attached.
Fix: Detach or delete all phone numbers from the Agent first, then try deleting again.
Test Call
Test any active Agent directly from the dashboard. Click the Test button on any Agent card to open the Test Call modal.
Voice Mode
- Microphone permission check before connecting
- Audio input & output device selection
- Test speaker button to verify audio
- Mute / unmute toggle during the call
- Live call duration timer
Text Mode
- Text input field with send button
- Streaming message display
- No microphone required
Transcript View
Both modes show a chat-bubble transcript: user messages on the right (blue), Agent messages on the left (grey). Messages auto-scroll as the conversation progresses.
Brain SDK (Python)
Build your custom reasoning backend with the hubui-brain Python SDK
What Is a Brain?
A Brain is your custom Python backend that the Voice Agent calls on-demand when your System Instructions tell it to. It receives the caller's question over a persistent WebSocket connection, runs your reasoning logic — LLM inference, database lookups, API calls, LangChain/LangGraph pipelines, or any async Python code — and sends back a response the Agent speaks to the caller.
The hubui-brain Python SDK (v0.1.0) abstracts the entire WebSocket protocol so you can focus entirely on your reasoning logic. Protocol Version: 1.0.0.
Installation
Requires Python 3.10+, a Brain API key from the dashboard, and a Brain URL configured in your Agent.
# Install from PyPI (recommended) pip install hubui-brain # Or install from source cd hubui-brain && pip install -e . # Or install directly from GitHub pip install "git+https://github.com/HubUI-AI/hubui-brain.git"
Quick Start — Manual Heartbeats
You control exactly when to send follow-up status updates during longer operations.
from hubui_brain import Brain, QueryContext
brain = Brain(api_key="br_xxx", debug=True)
@brain.on_query()
async def handle_query(ctx: QueryContext):
answer = await my_reasoning_engine.process(ctx.query)
await ctx.reply(answer)
brain.run(host="0.0.0.0", port=8080)Note: You don't need to send an initial acknowledgment — the Voice Agent automatically speaks one (e.g. "Sure, let me check on that") before your Brain even receives the query.
Quick Start — Auto Heartbeats
Configure timing thresholds on Brain and let the SDK send follow-up updates automatically via auto_process().
from hubui_brain import Brain, QueryContext
brain = Brain(
api_key="br_xxx",
heartbeat_after=5.0, # Follow-up status after 5 seconds
hold_music_after=15.0, # Hold music after 15 seconds
debug=True
)
@brain.on_query()
async def handle_query(ctx: QueryContext):
# SDK monitors elapsed time and sends heartbeats automatically
answer = await ctx.auto_process(
my_reasoning_engine.process(ctx.query)
)
await ctx.reply(answer)
brain.run(host="0.0.0.0", port=8080)If heartbeat_after and hold_music_after are both None (the default), auto_process() runs your coroutine directly with zero monitoring overhead.
Instant Acknowledgment & Heartbeats
Instant Acknowledgment (Automatic)
The Voice Agent automatically speaks a brief acknowledgment (e.g. "Sure, let me check on that") the moment the caller asks a question — before your Brain even receives the query. The caller is never left in silence. You don't need to handle this.
Heartbeats (For Extended Processing)
Follow-up status updates when processing takes longer than expected. Since the caller already heard an acknowledgment, heartbeat messages should provide new information — not repeat the initial acknowledgment.
Good Heartbeats
- "Still searching — checking a few more options."
- "Almost there — just confirming the details."
- "Thanks for your patience — pulling up the latest info."
Bad Heartbeats
- "Let me check on that" (repeats acknowledgment)
- "Please wait" (too generic)
auto_process() Timing Diagram
0s → Caller asks a question
Voice Agent instantly acknowledges
Your Brain starts processing
5s → Brain sends heartbeat: "Still working on that..."
15s → Brain starts hold music: "Please hold."
20s → Coroutine returns → ctx.reply(result)Per-Call Overrides
result = await ctx.auto_process(
process_query(ctx.query),
heartbeat_after=7.0, # Override Brain default
hold_music_after=20.0, # Override Brain default
heartbeat_message="Checking a few more sources...",
hold_music_message="Still working — thanks for your patience."
)Full Manual Control
@brain.on_query()
async def handle(ctx: QueryContext):
data = await fetch_from_database(ctx.query)
if needs_extra_time(data):
await ctx.say_processing("Still looking — checking a few more options.")
if needs_even_more_time(data):
await ctx.play_hold_music("This is taking a bit longer. Please hold.")
result = await my_reasoning_layer(ctx.query, data)
await ctx.reply(result)Brain Constructor Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
| api_key | str | None | None | Brain API key. Falls back to HUBUI_BRAIN_API_KEY env var. |
| heartbeat_after | float | None | None | Seconds before auto heartbeat in auto_process(). |
| hold_music_after | float | None | None | Seconds before auto hold music in auto_process(). |
| heartbeat_message | str | None | None | Custom heartbeat message. Default: "One moment please..." |
| hold_music_message | str | None | None | Custom hold music message. Default: "This is taking a bit longer..." |
| debug | bool | False | Log all WebSocket traffic to console. |
| shutdown_timeout | float | 5.0 | Seconds to wait for active calls on shutdown. |
brain.run() vs brain.start()
brain.run(host, port)
Blocking. Recommended for standalone scripts.
brain.run(host="0.0.0.0", port=8080)
await brain.start(host, port)
Async. For integration with existing event loops.
import asyncio
async def main():
await brain.start(host="0.0.0.0", port=8080)
asyncio.run(main())QueryContext — Properties
Passed to your handler on every query. Provides the request data and all response methods.
| Property | Type | Description |
|---|---|---|
| ctx.query | str | The caller's current question or request |
| ctx.session_id | str | Unique identifier for this call session |
| ctx.caller_number | str | None | Caller's phone number (E.164 format) |
| ctx.business_number | str | None | The business phone number that received the call |
| ctx.user_email | str | None | Caller's email (WebRTC/web calls only) |
| ctx.call_source | str | "sip", "webrtc", or "unknown" |
| ctx.connection_type | str | "voice", "text", or "unknown" |
| ctx.history | List[ConversationTurn] | All previous turns in this conversation |
| ctx.total_turns | int | Number of turns in ctx.history |
| ctx.capabilities | Capabilities | None | Tools the Voice Agent supports |
| ctx.available_tools | List[ToolDefinition] | Flat list of tool names and descriptions |
| ctx.is_complete | bool | Whether a final response has been sent |
| ctx.request | QueryRequest | The full raw request object |
QueryContext — Response Methods
Methods are either non-final (can be called multiple times) or final (ends the turn — call exactly once).
Non-Final (call multiple times)
| Method | Description |
|---|---|
| await ctx.say_processing(message) | Speak a follow-up status update to the caller |
| await ctx.play_hold_music(message=None) | Start hold music. Optional message spoken first. |
Final (call exactly once — ends the turn)
| Method | Description |
|---|---|
| await ctx.reply(message, data=None) | Send the final answer. Optional data dict for logging (not spoken). |
| await ctx.ask_clarification(message) | Ask a follow-up question. Triggers a new query with the caller's answer. |
| await ctx.reply_error(message, error_code=None, error_details=None) | Send an error. message is spoken. error_code and error_details are for your logs only. |
| await ctx.terminate_call(message) | End the call gracefully (SIP only). Speaks the message then hangs up. Always check ctx.has_tool("_terminate_call") first. |
auto_process() Method Reference
result = await ctx.auto_process(
coroutine,
heartbeat_after=None, # Override Brain default (seconds)
hold_music_after=None, # Override Brain default (seconds)
heartbeat_message=None, # Override Brain default
hold_music_message=None # Override Brain default
)Wraps your coroutine and automatically sends heartbeat/hold music based on elapsed time. Per-call arguments override Brain-level defaults. If no timing is configured anywhere, the coroutine runs directly with zero overhead.
Capability Checking
The Voice Agent advertises which tools it supports in each request. Always check before attempting to use a tool — _terminate_call is only available on SIP (phone) calls, not WebRTC.
# Check a specific tool
if ctx.has_tool("_terminate_call"):
await ctx.terminate_call("Goodbye!")
else:
await ctx.reply("Goodbye!") # Graceful fallback
# Inspect all available tools
for tool in ctx.available_tools:
print(f"{tool.name}: {tool.description}")ConversationTurn
Each item in ctx.history is a ConversationTurn:
for turn in ctx.history:
print(f"{turn.role}: {turn.content}") # role: "user" or "assistant"
print(f" at: {turn.timestamp}") # ISO 8601 string, or NoneAPI Key Setup
Your Brain API key is generated automatically when you create a Brain in the dashboard. Provide it via constructor or environment variable:
# Option 1: Environment variable (recommended — keeps key out of source code) # export HUBUI_BRAIN_API_KEY=br_xxx brain = Brain() # Option 2: Pass directly brain = Brain(api_key="br_xxx")
Lookup order: api_key constructor param → HUBUI_BRAIN_API_KEY env var. The key is included in every WebSocket response for validation. Treat it like a password — never commit to source control.
Wait! How Do I Invoke the Brain?
Just creating a Brain and selecting it in the Agent's configuration dropdown is not enough. The Agent needs to know when to pause its normal reasoning and hand the query over to your backend.
You must explicitly call your Brain by its exact name inside the Agent's System Instructions.
For example, if you created a brain named orders_db, your Agent's system prompt should include:
Brain Query Timeouts
If your backend takes too long, HubUI aborts the request to prevent leaving the caller in silence. Timeouts are enforced by account limit:
60s
PAYG default
Custom
Enterprise accounts
Deployment
Your Brain is a standard async Python WebSocket server. It runs on any platform that supports long-lived connections.
Docker
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "main.py"]
docker build -t my-brain . docker run -p 8080:8080 -e HUBUI_BRAIN_API_KEY=br_xxx my-brain
Connecting to HubUI
- Deploy your Brain and note the public WebSocket URL (e.g.
wss://my-brain-xxx.run.app) - In the dashboard, create a Brain and enter that URL
- Assign the Brain to your Voice Agent
- Mention the Brain by name in the Agent's System Instructions
Examples
Simple Echo
from hubui_brain import Brain, QueryContext
brain = Brain(debug=True)
@brain.on_query()
async def echo(ctx: QueryContext):
await ctx.reply(f"You said: {ctx.query}")
brain.run(port=8080)OpenAI — Manual Heartbeats
from hubui_brain import Brain, QueryContext
from openai import AsyncOpenAI
brain = Brain(debug=True)
client = AsyncOpenAI()
@brain.on_query()
async def handle(ctx: QueryContext):
await ctx.say_processing("Still working on that — almost there.")
messages = [{"role": t.role, "content": t.content} for t in ctx.history]
messages.append({"role": "user", "content": ctx.query})
response = await client.chat.completions.create(
model="gpt-4o", messages=messages
)
await ctx.reply(response.choices[0].message.content)
brain.run(port=8080)OpenAI — Auto Heartbeats
from hubui_brain import Brain, QueryContext
from openai import AsyncOpenAI
brain = Brain(heartbeat_after=5.0, hold_music_after=15.0, debug=True)
client = AsyncOpenAI()
@brain.on_query()
async def handle(ctx: QueryContext):
messages = [{"role": t.role, "content": t.content} for t in ctx.history]
messages.append({"role": "user", "content": ctx.query})
async def call_openai():
resp = await client.chat.completions.create(
model="gpt-4o", messages=messages
)
return resp.choices[0].message.content
result = await ctx.auto_process(call_openai())
if should_end_call(result):
if ctx.has_tool("_terminate_call"):
await ctx.terminate_call(result)
return
await ctx.reply(result)
brain.run(port=8080)LangGraph Agent
from hubui_brain import Brain, QueryContext
from langgraph.graph import StateGraph
brain = Brain(debug=True)
graph = StateGraph(...)
agent = graph.compile()
@brain.on_query()
async def handle(ctx: QueryContext):
await ctx.say_processing("Still pulling up the details...")
result = await agent.ainvoke({
"query": ctx.query,
"history": [(t.role, t.content) for t in ctx.history],
"caller": ctx.caller_number,
"call_source": ctx.call_source,
})
await ctx.reply(result["response"])
brain.run(port=8080)Client SDK (JS/TS)
Embed voice and text agents in your web applications with @hubui/client
What Is HubUI Client?
@hubui/client is a lightweight JavaScript/TypeScript SDK that lets you connect end-users to HubUI agents directly from a browser. It manages WebRTC audio streaming, session lifecycle, real-time transcription, and text messaging — all in a single HubUI.connect() call.
Use it to build custom call interfaces, embed agents in your product, or create fully branded voice & chat experiences. Full TypeScript definitions are included — no @types/ package needed. SDK Version: 1.0.0.
Installation
# From npm (recommended) npm install @hubui/client # or yarn add @hubui/client # From GitHub repository npm install github:HubUI-AI/hubui-client # Explicit Git URL npm install git+https://github.com/HubUI-AI/hubui-client.git
Prerequisites
HubUI Account
Active account at app.hubui.ai
Agent Created
At least one agent configured in your dashboard
Client API Key
pk_live_... key from Settings → API Keys
Agent ID
Your agent's ID from the Agents page
Quick Start — Voice Mode
import { HubUI } from '@hubui/client';
// Connect to your agent in voice mode
const session = await HubUI.connect({
agentId: 'your-agent-id', // From HubUI Dashboard
apiKey: 'pk_live_xxxxx', // From Dashboard > Settings > API Keys
mode: 'voice',
userName: 'John Doe', // Optional
});
// Listen for real-time transcripts
session.on('transcript', (text, speaker, isFinal) => {
console.log(`${speaker}: ${text}`);
});
// Handle connection state changes
session.on('connectionStateChanged', (state) => {
console.log('Connection:', state);
});
// Mute/unmute controls
session.mute();
session.unmute();
// Disconnect when done
await session.disconnect();Quick Start — Text Mode
import { HubUI } from '@hubui/client';
// Connect to your agent in text mode
const session = await HubUI.connect({
agentId: 'your-agent-id',
apiKey: 'pk_live_xxxxx',
mode: 'text',
userName: 'John Doe',
});
// Listen for agent responses
session.on('message', (text) => {
console.log('Agent:', text);
});
// Send messages
await session.send('Hello! How can you help me?');
// Disconnect when done
await session.disconnect();HubUI.connect(config) — Full Reference
Connects to a HubUI agent and returns a Promise<HubUISession>. Throws a HubUIError if connection fails.
| Property | Type | Required | Description |
|---|---|---|---|
| agentId | string | Yes | Your agent ID from the dashboard |
| apiKey | string | Yes* | Your API key (pk_live_...) from Settings → API Keys |
| token | string | No | Pre-fetched realtime token (advanced — skips token fetch) |
| serverUrl | string | No | Realtime server URL (required when token is provided) |
| mode | 'voice' | 'text' | Yes | Connection mode — voice for audio calls, text for chat |
| userName | string | No | End user's display name |
| userEmail | string | No | End user's email (for analytics) |
| audioInput | string | No | Audio input device ID (from getAudioDevices) |
| audioOutput | string | No | Audio output device ID (from getAudioDevices) |
| apiBaseUrl | string | No | Custom HubUI API base URL |
| debug | boolean | No | Enable SDK debug logging in the browser console (default: false) |
* apiKey is required unless you provide token + serverUrl. The key must start with pk_live_.
HubUI.getAudioDevices()
Static method that requests microphone permission and lists all available audio devices. Use the returned deviceId values in audioInput / audioOutput config, or in setAudioInput() / setAudioOutput().
const { inputs, outputs } = await HubUI.getAudioDevices();
// inputs: [{ deviceId, label, kind: 'audioinput' }, ...]
// outputs: [{ deviceId, label, kind: 'audiooutput' }, ...]
console.log('Microphones:', inputs);
console.log('Speakers:', outputs);
// Pass to connect config
const session = await HubUI.connect({
agentId: 'your-agent-id',
apiKey: 'pk_live_xxxxx',
mode: 'voice',
audioInput: inputs[0].deviceId,
audioOutput: outputs[0].deviceId,
});HubUISession — Properties
| Property | Type | Description |
|---|---|---|
| session.state | HubUIConnectionState | Current connection state |
| session.mode | 'voice' | 'text' | Current session mode |
| session.transcript | HubUITranscriptEntry[] | Full conversation transcript array (immutable copy) |
Transcript Entry Shape
{
id: string; // Unique entry ID
speaker: 'user' | 'agent'; // Who spoke
text: string; // The transcribed or sent text
timestamp: Date; // When this entry was created
isFinal: boolean; // true = final, false = interim
}HubUISession — Methods
| Method | Description |
|---|---|
| on(event, callback) | Subscribe to events. Returns void. |
| off(event, callback) | Unsubscribe from a previously registered event handler. |
| mute() | Mute the microphone (voice mode only). |
| unmute() | Unmute the microphone (voice mode only). |
| isMuted() | Returns true if the microphone is currently muted. |
| await enableAudio() | Enable audio playback. Required on mobile browsers after audioPlaybackBlocked event. |
| await send(message) | Send a text message to the agent (text mode). Throws if not connected. |
| await setAudioInput(deviceId) | Hot-swap the microphone during an active session. |
| await setAudioOutput(deviceId) | Change speaker/output device during an active session. |
| await disconnect() | End the session gracefully. Sends a session-end signal and closes the connection. |
HubUISession — Events
| Event | Callback Signature | Description |
|---|---|---|
| connected | () => void | Successfully connected to agent |
| disconnected | () => void | Disconnected from agent |
| error | (error: HubUIError) => void | An error occurred |
| transcript | (text: string, speaker: 'user' | 'agent', isFinal: boolean) => void | Voice transcript received (interim or final) |
| message | (text: string) => void | Text message received from agent |
| connectionStateChanged | (state: HubUIConnectionState) => void | Connection state changed |
| audioPlaybackBlocked | () => void | Browser blocked audio — call enableAudio() |
Connection States
disconnected → connecting → connected ↔ reconnectingMobile Browser Support
iOS Safari and Android Chrome require a user gesture (tap/click) to play audio. The SDK emits audioPlaybackBlocked when the browser's autoplay policy blocks the agent's voice. Show a UI button and call enableAudio():
session.on('audioPlaybackBlocked', () => {
// Show a button prompting the user to tap
showButton('Tap to enable audio', async () => {
await session.enableAudio();
hideButton();
});
});The SDK also attempts to unlock audio automatically during connect() by playing a silent buffer, but this may not work on all browsers — always handle the event as a fallback.
Error Handling
All errors are instances of HubUIError with a code property from the ErrorCodes enum.
import { HubUI, HubUIError, ErrorCodes } from '@hubui/client';
try {
const session = await HubUI.connect(config);
} catch (error) {
if (error instanceof HubUIError) {
switch (error.code) {
case ErrorCodes.INVALID_API_KEY:
console.error('Invalid API key');
break;
case ErrorCodes.INVALID_AGENT_ID:
console.error('Agent not found');
break;
case ErrorCodes.MICROPHONE_ACCESS_DENIED:
console.error('Microphone permission denied');
break;
default:
console.error(`Error [${error.code}]: ${error.message}`);
}
}
}All Error Codes
| Code | Category | Description |
|---|---|---|
| INVALID_API_KEY | Auth | API key format is invalid or key not recognized |
| INVALID_AGENT_ID | Auth | Agent ID not found or doesn't belong to this account |
| UNAUTHORIZED | Auth | Authentication failed |
| MISSING_API_KEY | Config | No apiKey provided in config |
| MISSING_AGENT_ID | Config | No agentId provided in config |
| INVALID_CONFIG | Config | Configuration object is malformed |
| INVALID_MODE | Config | Mode must be "voice" or "text" |
| CONNECTION_FAILED | Connection | Could not establish connection |
| CONNECTION_TIMEOUT | Connection | Connection attempt timed out (30s) |
| CONNECTION_LOST | Connection | Connection dropped unexpectedly |
| NETWORK_ERROR | Connection | Network-level failure |
| TOKEN_FETCH_FAILED | Connection | Could not fetch authentication token |
| MICROPHONE_ACCESS_DENIED | Device | User denied microphone permission |
| AUDIO_DEVICE_ERROR | Device | Could not access audio device |
| SESSION_NOT_CONNECTED | Session | Attempted action on disconnected session |
| SESSION_ALREADY_CONNECTED | Session | Attempted to connect already-connected session |
| UNKNOWN | Other | Unexpected error |
TypeScript Support
Full TypeScript definitions are bundled with the package — no separate @types/ install needed.
import type {
HubUIConfig,
HubUIConnectionState,
HubUITranscriptEntry,
HubUIEvents,
AudioDevice,
} from '@hubui/client';
import { HubUI, HubUISession, HubUIError, ErrorCodes, SDK_VERSION } from '@hubui/client';
import type { ErrorCode } from '@hubui/client';Browser Support
| Browser | Minimum Version |
|---|---|
| Chrome | 70+ |
| Firefox | 65+ |
| Safari | 14+ |
| Edge | 79+ |
WebRTC support is required. Older browsers without WebRTC will fail at connection time with CONNECTION_FAILED.
Phone Numbers
Provision, configure, and manage inbound phone lines
Overview
Phone numbers let callers dial a real US phone number and speak directly to your AI agent. Numbers are provisioned automatically behind the scenes, with SIP trunk routing handled entirely by HubUI.
Inbound only: HubUI currently supports inbound calls only. Your agent answers calls placed to your number — outbound calling is not available at this time.
Assigned
Attached to an agent — actively routing inbound calls
Unassigned
In your account but not attached to any agent
Every phone number exists in one of two pools. Assigned numbers are linked to a specific agent and receive inbound calls. Unassigned numbers are held in your account (billing continues) and can be re-attached to any agent at any time.
Account Requirements
Phone numbers require PSTN capability on the account and enough PAYG credit for monthly number costs and voice usage.
| Requirement | Default | Notes |
|---|---|---|
| PSTN Enabled | Enabled for PAYG accounts | Controlled by backend account limits |
| Monthly Number Rate | $2.00 | Per active US number |
| Per-Phone Concurrency | 1 | Enterprise overrides can raise this |
| Credit Balance | Required | Add credit from Billing before provisioning |
Provisioning a New Number
Add a phone number to your agent in five steps. The entire process is handled through the dashboard — provisioning and SIP trunk configuration happen automatically behind the scenes.
Add Number
Click button
Area Code
Enter 3 digits
Select
Pick a number
Name It
Friendly name
Live
Ready for calls
Add Phone Number
dashboardNavigate to the Phone Numbers page and click "Add Phone Number." Select the agent you want to assign the number to.
Search by Area Code
USEnter a 3-digit area code (e.g. 415, 704, 212). HubUI searches available local US numbers matching that area code.
Select a Number
chooseBrowse the search results showing available numbers with their locality and region. Pick the number you want to provision.
Add a Friendly Name
optionalGive the number an easy-to-remember label (e.g. "Support Line" or "Sales NY"). This name appears throughout the dashboard for quick identification.
Number Provisioned
liveThe number is provisioned, configured with a SIP trunk for HubUI routing, registered in the phone number index, and attached to your agent. It's immediately ready to receive inbound calls.
Phone Billing and Suspension
Phone numbers are billed at $2.00 per number each month from your PAYG credit balance. Monthly phone billing is handled together for all numbers on your account.
$2
Per Number
Charged monthly from credit
All
Billed Together
3 numbers require $6.00
30 days
Suspension Window
Reserved before release
If your balance is below the full monthly phone amount, HubUI suspends all phone numbers instead of charging partial credit. Suspended numbers stay reserved for 30 days, but inbound calls do not connect while suspended.
Add enough credit to cover the full suspended phone balance.
HubUI charges the full phone amount and reactivates all suspended numbers together.
If the suspension window expires, the numbers are released to the carrier and may not be recoverable.
Managing Your Numbers
Four actions are available for managing phone numbers. Understanding the difference between Detach and Delete is critical — one is reversible, the other is not.
Edit
Change friendly name, priority, or primary status
Detach
Move to Unassigned pool — reversible
Attach
Assign from Unassigned pool to an agent
Delete
Release to carrier — permanent & irreversible
Detaching a Number
Detaching moves a phone number from its current agent to the Unassigned pool. The number stays in your account, stays fully provisioned, and account metadata such as friendly name and notes is preserved.
Number remains provisioned — you keep the number
Friendly name and notes are preserved
Can be re-attached to any agent at any time
PAYG phone billing continues while the number stays provisioned
Inbound calls will not connect while the number is unassigned
Attaching a Number
Attach picks a number from the Unassigned pool and assigns it to the target agent. All previously saved settings are carried over.
Navigate to the Unassigned tab on the Phone Numbers page
Click Attach on the number you want to assign
Select the target agent from the dropdown
Optionally set as primary and adjust priority
The number is moved from unassigned to the agent's phone numbers collection and the phone index is updated
Deleting a Number
⚠ Permanent & Irreversible: Deleting a phone number releases it back to the carrier. The number is gone from your account and may be reassigned to someone else. This action cannot be undone.
When you delete a phone number, HubUI performs the following cleanup:
The number is released to the carrier (permanent release)
Future monthly phone charges stop once the number is released
If you want to keep the number but remove it from the current agent, use Detach instead.
Detach vs. Delete
Understanding the difference between these two actions is critical. Use Detach to temporarily unassign a number; use Delete only when you no longer need the number at all.
| Criteria | Detach | Delete |
|---|---|---|
| Number stays in your account? | Yes — moves to Unassigned pool | No — released to carrier |
| Can be re-attached? | Yes — to any agent | No — number is gone |
| Reversible? | Yes | No |
| Billing continues? | Yes — number is still provisioned | No — future monthly phone charges stop |
| Settings preserved? | Yes — friendly name, notes, and priority | No — everything removed |
| Phone index entry | Updated (status → unassigned) | Deleted entirely |
Calls & Logs
Browse call history, transcripts, and session recordings
Overview
The Calls page is a centralised log of every session your agents handle — phone calls via SIP trunking and browser-based sessions via WebRTC (voice & text). Every call is stored with its full metadata, duration breakdown, and real-time transcript so you can audit quality, debug issues, and review conversation flow.
Complete History
Every session recorded with caller ID, agent, source, type, duration, and termination reason
Full Transcripts
Turn-by-turn conversation log with speaker labels, timestamps, and chat-bubble UI
Rich Metadata
Business number, turn counts, interruption counts, and ended-by attribution per call
Call Logs Table
The main table displays all sessions in reverse-chronological order (most recent first). Each row surfaces the key data points you need at a glance.
| Column | Description | Sortable |
|---|---|---|
| Date / Time | Session start timestamp, formatted as locale date + time | Yes |
| Caller | Phone number for SIP calls, or "Web Call" for WebRTC sessions | Yes |
| Agent | Agent name with an engine badge (e.g. hub-nabu-0.1-beta) | Yes |
| Source | Phone / SIP or Web / WebRTC | Yes |
| Type | Voice or Text | No |
| Duration | Session length formatted as mm:ss | Yes |
| Ended By | User, Agent, or System — who terminated the session | No |
| Actions | View button → opens the Call Detail modal | — |
Default sort is most recent first (start_time descending). Click any sortable column header to toggle ascending / descending order.
Filters Panel
Narrow the call log to exactly the sessions you need. The filter panel sits above the table and supports multiple simultaneous criteria.
Phone Number Search
Free-text input — type a phone number and press Enter to filter. Matches caller or business number.
Agent Dropdown
Select a specific agent to show only its sessions, or leave as "All Agents" for the complete log.
Source Filter
Choose between Phone (SIP) or Web (WebRTC) to isolate a channel.
Date Range
Pick From / To dates to scope the log to a specific time window. Use Apply to execute, Clear to reset.
Call Detail View
Click View on any call row to open a full-screen modal with complete session metadata and the live transcript.
Metadata Grid
The top section displays a structured grid of session properties:
Caller
Phone number or "Web Call"
Agent
Name + engine badge
Duration
Total call length (mm:ss)
Source
SIP or WebRTC badge
Type
Voice or Text badge
Ended By
User / Agent / System
Business Number
Attached phone number
Total Turns
X user / Y agent breakdown
Interruptions
Count of mid-speech interrupts
Transcript Viewer
Below the metadata grid, the full conversation transcript is rendered as a chat-bubble interface for easy reading.
User Messages → Right-Aligned
Displayed in a blue/primary bubble aligned to the right, with a User icon and HH:mm:ss timestamp beneath each message.
Agent Messages → Left-Aligned
Displayed in a muted/gray bubble aligned to the left, with a Bot icon and HH:mm:ss timestamp beneath each message.
Session Sources & Types
Every session is categorised by its source channel and communication type. Understanding these distinctions helps when filtering and analysing your call data.
| Source | Type | Channel | Description |
|---|---|---|---|
| SIP | Voice | Phone | Traditional phone call routed through SIP trunking to a provisioned number |
| WebRTC | Voice | Browser | Real-time voice session initiated via the @hubui/client SDK in a web browser |
| WebRTC | Text | Browser | Text-based chat session via the @hubui/client SDK — agent responds with synthesised speech |
Ended By Attribution
Every session records who or what terminated the conversation. This attribution is crucial for understanding user satisfaction and detecting system issues.
User Ended
The caller or web user hung up or closed the session voluntarily — the ideal outcome for resolved enquiries.
Agent Ended
The AI agent terminated the conversation — typically after completing a task or when configured to end after a certain condition.
System Disconnect
The session ended due to a system event — network timeout, SIP trunk error, or infrastructure disconnection. Worth investigating if frequent.
Analytics
Monitor agent sessions, Brain performance, and usage trends
Overview
The Analytics dashboard provides two distinct perspectives on your platform's performance, organised into dedicated tabs:
Sessions Tab
Session volume, caller metrics, duration statistics, and outcome breakdowns across all call sources
Brain AI Tab
Brain engagement metrics, success rates, processing latency, connect time, and bandwidth analysis
Important: The Brain AI tab only appears when you have a Brain connected to at least one agent. If no Brain is configured, only the Sessions tab is visible.
Date Range & Auto-Resolution
Both tabs share a unified date-range filter that determines the time window for all charts and metrics. The system automatically selects the optimal data resolution based on your selected range.
Quick Presets
Auto-Resolution Logic
The chart granularity automatically adjusts to provide the most useful visualisation for your selected window:
| Time Range | Resolution | Interval |
|---|---|---|
| ≤ 24 hours | 5-minute buckets | Up to 288 data points |
| 1 – 7 days | Hourly buckets | Up to 168 data points |
| > 7 days | Daily buckets | One point per day |
The current timezone is displayed alongside the date picker. All timestamps are shown in your local browser timezone.
Sessions — Stat Cards
The Sessions tab opens with two rows of stat cards providing a high-level summary of activity within the selected date range.
Row 1 — Key Metrics
Total Sessions
All calls in range
Unique Callers
Distinct phone numbers
Unique Users
Distinct email addresses
Avg Duration
Mean session length
Row 2 — Source Breakdown
Voice WebRTC
Browser voice calls
Text WebRTC
Browser text sessions
Phone SIP
Phone trunk calls
Total Duration
Combined session time
Sessions — Volume Chart
A stacked bar chart visualising session volume over time, broken down by source channel. The chart resolution automatically adapts based on the selected date range.
SIP Calls
Yellow/amber segments — phone trunk sessions
WebRTC Voice
Green segments — browser voice sessions
WebRTC Text
Purple segments — browser text sessions
Hover over any bar to see the exact count per source for that time bucket. The X-axis labels auto-format based on resolution (timestamps for 5-min, hours for hourly, dates for daily).
Sessions — Duration Statistics
Aggregate duration metrics for all sessions within the selected date range, giving you insight into typical conversation lengths.
Average
Mean session duration across all calls
Minimum
Shortest session in range
Maximum
Longest session in range
Total
Sum of all session durations
Durations are formatted as human-readable strings (e.g. "2m 34s" or "1h 12m"). The API also provides raw seconds for programmatic access.
Sessions — Outcome Breakdown
Understand how sessions end. Each outcome category is displayed as a progress bar with count and percentage, making it easy to spot trends.
User Ended
Caller hung up voluntarily
Agent Ended
AI agent terminated session
System Disconnects
Infrastructure or timeout events
Errors
Sessions ending with an error
Each bar shows the percentage relative to total sessions. A high "Errors" percentage may indicate infrastructure issues. A high "System Disconnects" rate could signal timeout configuration problems.
Brain AI — Performance Stats
The Brain AI tab provides deep insight into how your Brain backend performs. Stat cards are arranged in two rows covering engagement, reliability, and throughput.
Row 1 — Engagement & Reliability
Total Engagements
Every Brain request across all agents
Successful
Requests that returned a valid response
Failed
Requests that timed out or errored
Success Rate
Percentage of successful requests
Row 2 — Latency & Throughput
Avg Processing
Mean time to process a request (with p95)
Avg Connect
Mean TCP connect time in ms (with p95)
Data Sent
Total outbound bytes (with avg/call)
Data Received
Total inbound bytes (with avg/call)
Brain AI — Request Volume
A stacked bar chart showing Brain request volume over time, split by outcome. Uses the same auto-resolution as the Sessions tab.
Successful Requests
Green bars — Brain returned a valid response within timeout
Failed Requests
Red bars — requests that timed out, errored, or received invalid responses
Brain AI — Requests & Error Rate
A dual-axis chart combining total request volume (area) with error rate percentage (line) over time. This visualisation helps correlate traffic spikes with reliability drops.
Left Y-Axis — Request Count
Total Brain requests rendered as a gradient area fill. Hover for exact counts per interval.
Right Y-Axis — Error Rate %
Percentage of failed requests rendered as a red line (0–100% scale). Spikes indicate Brain instability.
Brain AI — Latency & Connect Time
Two side-by-side line charts tracking the performance of your Brain API over time. Both charts auto-resolve based on the selected date range.
Processing Latency (seconds)
Average time your Brain takes to process each request and return a response. Measured from request sent to response received, excluding connection setup.
Connect Time (milliseconds)
Time to establish the TCP/TLS connection to your Brain API URL. High connect times indicate network issues or DNS problems between HubUI's infrastructure and your Brain endpoint.
Both charts show data points at daily resolution (dots visible) for ranges over 7 days. For shorter ranges, continuous line rendering is used. Percentile stats (p95) are shown in the stat cards above, not in the time-series charts.
Brain AI — Bandwidth
An area chart showing total data transferred between HubUI and your Brain API over time. Bandwidth is the sum of bytes sent (request payloads) and bytes received (response payloads).
Auto-formatted units: Values are displayed as B, KB, MB, or GB depending on magnitude
Gradient fill: A cyan gradient area visualises cumulative traffic per time bucket
Stat card context: The Data Sent and Data Received stat cards above show totals with per-call averages
Optimisation tip: If bandwidth is high relative to engagement count, consider compressing your Brain API response payloads
Billing
Understand PAYG credit, rates, and usage costs
Overview
HubUI uses a pay-as-you-go credit balance. Voice minutes, AI response messages, and phone numbers draw down credit using the current rate card. The dashboard flow is credit-based rather than tier-based.
Credit Balance
Add credit by card and monitor your available balance from Billing
Live Cost Tracking
Current-period voice, message, and phone costs are shown in real time
Phone Numbers
Phone numbers are billed monthly from the same PAYG balance
Rate Card
The public PAYG rate card is returned by GET /api/v1/plans. Enterprise accounts can have custom per-user overrides for selected limits and rates, while phone pricing remains global.
| Item | PAYG | Notes |
|---|---|---|
| Voice | $0.14/min | Web voice and PSTN voice use the same rate |
| Messages | $0.01/response | Billed per AI response turn |
| Phone Numbers | $2.00/month | Per active US number |
| Agents | 99 | Default account limit |
| System Instruction Tokens | 1,500 | Can be customized for enterprise users |
| Concurrent Web Voice | 10 | Per account |
| Concurrent Per Phone | 1 | Carrier-enforced informational limit |
| Brain Query Timeout | 60s | Maximum external brain response window |
Credit Billing
Usage draws down the account's prepaid credit balance. The Billing page shows live credit, current-period spend, and a rate card so users can top up before the balance reaches zero. At zero balance, new calls and messages are blocked until credit is added.
Low Credit
A warning appears when the account balance is at or below the configured low-credit threshold.
Top-Ups
Users add credit through Stripe PaymentIntents. Successful payments update balance through the backend billing flow.
| Cost Type | Metric | Displayed On Billing |
|---|---|---|
| Voice | Minutes | Voice cost and total period spend |
| Messages | AI response turns | Message cost and total period spend |
| Phones | Monthly active numbers | Phone cost and total period spend |
Phone Number Billing
Phone numbers draw from the same PAYG balance as voice and messages. The current public rate is $2.00 per active US number per month, charged from credit rather than recurring plan billing.
$2
Monthly Number Rate
Per active US number
1
Concurrent Per Phone
Default informational limit
Credit
Required
Provisioning requires an available PAYG balance
Phone billing is all-or-nothing per account. If your account has 3 phone numbers, the monthly phone amount is $6.00. If the full amount is not available, all phone numbers are suspended together for 30 days. Add enough credit during that window to reactivate them. After the window expires, suspended numbers are released to the carrier.
Phone-number capability is controlled by account limits returned from the backend.
Released numbers stop appearing in future active-number cost calculations.
Enterprise accounts can receive custom account-level web voice limits and other configured overrides.
Stripe Customer Portal
The Payment Methods button opens Stripe's hosted Customer Portal where users can manage cards and payment records. Credit top-ups are one-time PaymentIntent charges.
Payment Methods
Update your credit card or add a new payment method at any time
Payment Records
Review Stripe-hosted payment records for credit purchases
Credit Top-Ups
Self-serve credit additions are confirmed with PaymentIntents
Secure Payments
Payment processing is handled by Stripe, so HubUI never sees card details
Troubleshooting
Debug common issues and find answers quickly
"My Agent isn't using my Brain"
Cause
Your Brain's exact name is not mentioned in the agent's System Instructions. Just selecting the Brain in the dropdown is not enough — the Voice Agent needs an explicit instruction telling it when and how to invoke the Brain.
Fix
Add a clear instruction referencing the Brain by its exact name:
You must use the `orders_db` brain to look up order status whenever a caller asks about their order.
The name in your instructions must match the Brain name exactly as shown in the Brain panel (case-sensitive).
"I can't delete my Agent"
Cause
Phone numbers are still attached to the agent. The dashboard will show an error message listing which numbers need to be removed first.
Fix
Go to Phone Numbers or the agent's Phone tab and either detach (moves to unassigned pool) or delete (releases to carrier) every attached number. Once no numbers remain, deletion will succeed.
"Audio isn't playing on mobile"
Cause
iOS Safari and Android Chrome have autoplay policies that block audio playback until a user gesture occurs. The SDK emits an audioPlaybackBlocked event when this happens.
Fix
Listen for the event and show a "Tap to enable audio" button:
session.on('audioPlaybackBlocked', () => {
showButton('Tap to enable audio', async () => {
await session.enableAudio();
});
});"My Brain keeps timing out"
Cause
Your Brain's processing time exceeds the account timeout limit. The PAYG default is 60 seconds; enterprise accounts can be configured with custom limits.
Fix
- Optimise your processing pipeline — cache results, reduce LLM prompt sizes, use faster models
- Use
auto_process()withheartbeat_afterandhold_music_afterto keep the connection alive while processing - Contact support if your account needs a custom timeout for production workloads
# Keep connection alive during long operations
brain = Brain(
api_key="br_xxx",
heartbeat_after=5.0, # "Still working..." at 5s
hold_music_after=15.0, # Hold music at 15s
)
@brain.on_query()
async def handle(ctx: QueryContext):
result = await ctx.auto_process(slow_llm_call(ctx.query))
await ctx.reply(result)Client SDK Error Codes — Quick Reference
The most common error codes you'll encounter when integrating the Client SDK, along with likely causes and fixes.
| Error | Likely Cause | Fix |
|---|---|---|
| INVALID_API_KEY | Wrong key format or revoked key | Verify key starts with pk_live_. Generate a new key if revoked |
| INVALID_AGENT_ID | Typo in agent ID or agent deleted | Copy the agent ID fresh from the dashboard Agents page |
| MICROPHONE_ACCESS_DENIED | User denied browser mic permission | Show UI explaining why the mic is needed, then re-prompt |
| CONNECTION_TIMEOUT | Network issue or server unreachable | Check internet connectivity. Retry after a short delay |
| TOKEN_FETCH_FAILED | API key doesn't match agent's account | Verify the key and agent belong to the same account |
| SESSION_NOT_CONNECTED | Called send() or mute() before connected | Wait for the connected event before calling session methods |
| CONNECTION_LOST | Connection dropped unexpectedly | Listen for connectionStateChanged → reconnecting for auto-recovery |
| AUDIO_DEVICE_ERROR | Audio device inaccessible or disconnected | Use HubUI.getAudioDevices() to verify available devices |
All error codes are importable: import { ErrorCodes } from '@hubui/client'. See the section for the complete list of 17 error codes.