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

Only when
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.

1

Voice Agent

Autonomous · Real-Time

Your 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.

2

Brain

Optional · On-Demand

Your 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.

3

Client SDK

You embed in your app

A 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.

4

Phone Numbers

Entry point for callers

Attach 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.

5

Dashboard

Your control plane

Create 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. 1

    User speaks or types

    A message is captured and routed to your Agent.

  2. 2

    Agent responds instantly

    The Agent understands the message and replies in real-time — no delay, no external calls.

  3. 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. 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. 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. 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. 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. 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.

  1. Navigate to the Agents page → Click "Create Agent".
  2. Set a name, pick a voice, and select your language.
  3. 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.
  4. 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.

  1. Get a Client API Key — go to Settings → API Keys and generate one. It starts with pk_live_....
  2. Copy your Agent ID from the Agents page.
  3. 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.

  1. Go to the Brain page → Click "Create Brain". Enter a name (e.g., order_lookup) and your Brain's WebSocket URL.
  2. Copy the Brain API Key (br_...) — shown only once.
  3. 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)
  1. In your Agent's Brain tab, select your Brain from the dropdown.
  2. 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_lookup brain 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).

  1. Go to the Phone Numbers page → Click "Buy Number".
  2. Choose a number and assign it to your Agent.
  3. 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

Generate:Enter an optional name → creates a key in the format pk_live_{32 hex chars}.
Reveal:The full key is shown once on creation — copy and store it immediately. It is SHA-256 hashed on our servers; we cannot retrieve it later.
Revoke:Permanent and immediate. Any SDK or app using this key will stop working the instant you revoke it.

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.

  1. Click "Create Brain".
  2. Enter a name — 1–16 characters, lowercase letters, numbers, and underscores only (^[a-z0-9_]+$). Example: orders_db.
  3. Enter your Brain URL — the WebSocket endpoint where your Brain server listens (e.g., wss://my-brain.example.com).
  4. Secondary URL (optional) — a fallback endpoint if the primary URL is unreachable.
  5. Description (optional) — a note for your own reference.
  6. Scope — choose "All Agents" or select specific agents that should have access to this Brain.
  7. 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:

  1. Click Create Agent and provide a Name.
  2. Select Voice & Language: Choose how you want the Agent to sound and what language it will converse in.
  3. 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.
  4. Select the Brain: Choose the Brain you created from the dropdown menu.
  5. 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.

VoiceGenderPersonality
AriaFemaleWarm and bright
AtlasMaleInformative and conversational
BlazeMaleConfident and direct
JadeFemaleClear and firm
DashMaleUpbeat and engaging
ColeMaleFirm and deliberate
LunaFemaleSoft 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 TypeMax Tokens
PAYG default1,500
EnterpriseCustom

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.

Active

Live and accepting connections. Phone numbers attached to this Agent route inbound calls.

Paused

Agent exists but won't accept new connections. Inbound calls to attached numbers will not connect.

Disabled

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

ParameterTypeDefaultDescription
api_keystr | NoneNoneBrain API key. Falls back to HUBUI_BRAIN_API_KEY env var.
heartbeat_afterfloat | NoneNoneSeconds before auto heartbeat in auto_process().
hold_music_afterfloat | NoneNoneSeconds before auto hold music in auto_process().
heartbeat_messagestr | NoneNoneCustom heartbeat message. Default: "One moment please..."
hold_music_messagestr | NoneNoneCustom hold music message. Default: "This is taking a bit longer..."
debugboolFalseLog all WebSocket traffic to console.
shutdown_timeoutfloat5.0Seconds 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.

PropertyTypeDescription
ctx.querystrThe caller's current question or request
ctx.session_idstrUnique identifier for this call session
ctx.caller_numberstr | NoneCaller's phone number (E.164 format)
ctx.business_numberstr | NoneThe business phone number that received the call
ctx.user_emailstr | NoneCaller's email (WebRTC/web calls only)
ctx.call_sourcestr"sip", "webrtc", or "unknown"
ctx.connection_typestr"voice", "text", or "unknown"
ctx.historyList[ConversationTurn]All previous turns in this conversation
ctx.total_turnsintNumber of turns in ctx.history
ctx.capabilitiesCapabilities | NoneTools the Voice Agent supports
ctx.available_toolsList[ToolDefinition]Flat list of tool names and descriptions
ctx.is_completeboolWhether a final response has been sent
ctx.requestQueryRequestThe 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)

MethodDescription
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)

MethodDescription
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 None

API 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:

"You are a helpful customer support agent. If the user asks about the status of their order or wants to check inventory, you must query the orders_db brain to find the answer before responding."

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

  1. Deploy your Brain and note the public WebSocket URL (e.g. wss://my-brain-xxx.run.app)
  2. In the dashboard, create a Brain and enter that URL
  3. Assign the Brain to your Voice Agent
  4. 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.

PropertyTypeRequiredDescription
agentIdstringYesYour agent ID from the dashboard
apiKeystringYes*Your API key (pk_live_...) from Settings → API Keys
tokenstringNoPre-fetched realtime token (advanced — skips token fetch)
serverUrlstringNoRealtime server URL (required when token is provided)
mode'voice' | 'text'YesConnection mode — voice for audio calls, text for chat
userNamestringNoEnd user's display name
userEmailstringNoEnd user's email (for analytics)
audioInputstringNoAudio input device ID (from getAudioDevices)
audioOutputstringNoAudio output device ID (from getAudioDevices)
apiBaseUrlstringNoCustom HubUI API base URL
debugbooleanNoEnable 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

PropertyTypeDescription
session.stateHubUIConnectionStateCurrent connection state
session.mode'voice' | 'text'Current session mode
session.transcriptHubUITranscriptEntry[]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

MethodDescription
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

EventCallback SignatureDescription
connected() => voidSuccessfully connected to agent
disconnected() => voidDisconnected from agent
error(error: HubUIError) => voidAn error occurred
transcript(text: string, speaker: 'user' | 'agent', isFinal: boolean) => voidVoice transcript received (interim or final)
message(text: string) => voidText message received from agent
connectionStateChanged(state: HubUIConnectionState) => voidConnection state changed
audioPlaybackBlocked() => voidBrowser blocked audio — call enableAudio()

Connection States

disconnected → connecting → connected ↔ reconnecting

Mobile 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

CodeCategoryDescription
INVALID_API_KEYAuthAPI key format is invalid or key not recognized
INVALID_AGENT_IDAuthAgent ID not found or doesn't belong to this account
UNAUTHORIZEDAuthAuthentication failed
MISSING_API_KEYConfigNo apiKey provided in config
MISSING_AGENT_IDConfigNo agentId provided in config
INVALID_CONFIGConfigConfiguration object is malformed
INVALID_MODEConfigMode must be "voice" or "text"
CONNECTION_FAILEDConnectionCould not establish connection
CONNECTION_TIMEOUTConnectionConnection attempt timed out (30s)
CONNECTION_LOSTConnectionConnection dropped unexpectedly
NETWORK_ERRORConnectionNetwork-level failure
TOKEN_FETCH_FAILEDConnectionCould not fetch authentication token
MICROPHONE_ACCESS_DENIEDDeviceUser denied microphone permission
AUDIO_DEVICE_ERRORDeviceCould not access audio device
SESSION_NOT_CONNECTEDSessionAttempted action on disconnected session
SESSION_ALREADY_CONNECTEDSessionAttempted to connect already-connected session
UNKNOWNOtherUnexpected 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

BrowserMinimum Version
Chrome70+
Firefox65+
Safari14+
Edge79+

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.

RequirementDefaultNotes
PSTN EnabledEnabled for PAYG accountsControlled by backend account limits
Monthly Number Rate$2.00Per active US number
Per-Phone Concurrency1Enterprise overrides can raise this
Credit BalanceRequiredAdd 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

step 1

Area Code

Enter 3 digits

step 2

Select

Pick a number

step 3

Name It

Friendly name

step 4

Live

Ready for calls

01

Add Phone Number

dashboard

Navigate to the Phone Numbers page and click "Add Phone Number." Select the agent you want to assign the number to.

02

Search by Area Code

US

Enter a 3-digit area code (e.g. 415, 704, 212). HubUI searches available local US numbers matching that area code.

03

Select a Number

choose

Browse the search results showing available numbers with their locality and region. Pick the number you want to provision.

04

Add a Friendly Name

optional

Give the number an easy-to-remember label (e.g. "Support Line" or "Sales NY"). This name appears throughout the dashboard for quick identification.

05

Number Provisioned

live

The 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.

1.

Add enough credit to cover the full suspended phone balance.

2.

HubUI charges the full phone amount and reactivates all suspended numbers together.

3.

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.

1.

Navigate to the Unassigned tab on the Phone Numbers page

2.

Click Attach on the number you want to assign

3.

Select the target agent from the dropdown

4.

Optionally set as primary and adjust priority

5.

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.

CriteriaDetachDelete
Number stays in your account?Yes — moves to Unassigned poolNo — released to carrier
Can be re-attached?Yes — to any agentNo — number is gone
Reversible?YesNo
Billing continues?Yes — number is still provisionedNo — future monthly phone charges stop
Settings preserved?Yes — friendly name, notes, and priorityNo — everything removed
Phone index entryUpdated (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.

ColumnDescriptionSortable
Date / TimeSession start timestamp, formatted as locale date + timeYes
CallerPhone number for SIP calls, or "Web Call" for WebRTC sessionsYes
AgentAgent name with an engine badge (e.g. hub-nabu-0.1-beta)Yes
SourcePhone / SIP or Web / WebRTCYes
TypeVoice or TextNo
DurationSession length formatted as mm:ssYes
Ended ByUser, Agent, or System — who terminated the sessionNo
ActionsView 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.

SourceTypeChannelDescription
SIPVoicePhoneTraditional phone call routed through SIP trunking to a provisioned number
WebRTCVoiceBrowserReal-time voice session initiated via the @hubui/client SDK in a web browser
WebRTCTextBrowserText-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

Last 12 HoursLast 24 HoursLast 7 DaysLast 30 DaysCustom Range

Auto-Resolution Logic

The chart granularity automatically adjusts to provide the most useful visualisation for your selected window:

Time RangeResolutionInterval
≤ 24 hours5-minute bucketsUp to 288 data points
1 – 7 daysHourly bucketsUp to 168 data points
> 7 daysDaily bucketsOne 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.

ItemPAYGNotes
Voice$0.14/minWeb voice and PSTN voice use the same rate
Messages$0.01/responseBilled per AI response turn
Phone Numbers$2.00/monthPer active US number
Agents99Default account limit
System Instruction Tokens1,500Can be customized for enterprise users
Concurrent Web Voice10Per account
Concurrent Per Phone1Carrier-enforced informational limit
Brain Query Timeout60sMaximum 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 TypeMetricDisplayed On Billing
VoiceMinutesVoice cost and total period spend
MessagesAI response turnsMessage cost and total period spend
PhonesMonthly active numbersPhone 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() with heartbeat_after and hold_music_after to 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.

ErrorLikely CauseFix
INVALID_API_KEYWrong key format or revoked keyVerify key starts with pk_live_. Generate a new key if revoked
INVALID_AGENT_IDTypo in agent ID or agent deletedCopy the agent ID fresh from the dashboard Agents page
MICROPHONE_ACCESS_DENIEDUser denied browser mic permissionShow UI explaining why the mic is needed, then re-prompt
CONNECTION_TIMEOUTNetwork issue or server unreachableCheck internet connectivity. Retry after a short delay
TOKEN_FETCH_FAILEDAPI key doesn't match agent's accountVerify the key and agent belong to the same account
SESSION_NOT_CONNECTEDCalled send() or mute() before connectedWait for the connected event before calling session methods
CONNECTION_LOSTConnection dropped unexpectedlyListen for connectionStateChanged → reconnecting for auto-recovery
AUDIO_DEVICE_ERRORAudio device inaccessible or disconnectedUse 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.