Skip to content

Embed Widget ​

Add an AI-powered assistant to any website with a single <script> tag. The Universal API embed widget supports text chat, voice conversations, or both — with a toggle for end users to switch between modes.

Quick Start ​

1. Create an Embed Token ​

bash
curl -X POST https://api.universalapi.co/embed/create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "YOUR_TEXT_AGENT_ID",
    "voiceAgentId": "YOUR_VOICE_AGENT_ID",
    "allowedDomains": ["yourdomain.com", "localhost"],
    "greeting": "Hi! How can I help you today?"
  }'

2. Add the Script Tag ​

Paste the returned snippet into your HTML:

html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="YOUR_TEXT_AGENT_ID"
  data-voice-agent="YOUR_VOICE_AGENT_ID"
  data-token="emb_pk_live_..."
  async></script>

That's it! A floating chat button appears on your page.

Modes ​

The widget supports three modes, controlled by the data-mode attribute or set at token creation:

ModeDescriptionDefault when
textText chat only (streaming HTTP)Only agentId provided
voiceVoice only (WebSocket audio via Nova Sonic)Only voiceAgentId provided
bothBoth with toggle tabsBoth agents provided

Text Only ​

html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="TEXT_AGENT_ID"
  data-token="emb_pk_live_..."
  async></script>

Voice Only ​

html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-voice-agent="VOICE_AGENT_ID"
  data-token="emb_pk_live_..."
  async></script>

Both (with Toggle) ​

html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="TEXT_AGENT_ID"
  data-voice-agent="VOICE_AGENT_ID"
  data-token="emb_pk_live_..."
  data-mode="both"
  async></script>

When mode is both, users see tabs to switch between 💬 Chat and 🎙️ Voice.

Why Separate Agents? ​

Text and voice agents use fundamentally different technologies:

  • Text agents use streaming HTTP with models like Claude, Nova, or GPT — optimized for detailed, formatted responses with markdown, code blocks, and tool use.
  • Voice agents use WebSocket audio with Amazon Nova Sonic — optimized for real-time, natural conversation with low latency audio.

By specifying separate agent IDs, you can optimize each experience independently. For example, use a tool-heavy research agent for text and a fast conversational agent for voice.

How It Works Under the Hood ​

Understanding the underlying architecture helps when debugging or building custom native integrations.

Text Chat Flow ​

Browser → POST /embed/chat (embed token)
       ← { bearerToken, agentId }

Browser → POST stream.api.universalapi.co/agent/{agentId}/chat (bearer token)
       ← Streaming response (text chunks + control markers)
  1. Authentication — The widget exchanges your emb_pk_live_* token for temporary streaming credentials via POST /embed/chat
  2. Streaming — Messages are sent to the agent's streaming endpoint. The response is an HTTP stream containing markdown text interspersed with control markers (__META__, __TOOL_START__, __TOOL_END__, __DONE__)
  3. Conversations — A conversationId is returned in the stream metadata. The widget saves it and sends it back on subsequent messages for multi-turn memory.

Voice Chat Flow ​

Browser → WebSocket wss://voice.api.universalapi.co/ws/{agentId}?token=emb_pk_live_...
       ↔ Bidirectional audio (PCM Int16, base64-encoded JSON)
  1. Connection — The widget opens a WebSocket directly to the voice runtime with the embed token as a query parameter
  2. Audio capture — Microphone audio is captured at 16kHz, converted to Int16 PCM, base64-encoded, and sent as JSON messages
  3. Audio playback — The server sends agent speech at 24kHz (Int16 PCM, base64) which is decoded and played through the Web Audio API
  4. Transcripts — Real-time transcription messages arrive alongside audio for displaying what's being said

Build Your Own UI

Want to skip the widget and integrate these protocols directly into your own app? See the Native Integration Guide for complete protocol documentation, code examples, and a React component.

Creating the Right Agent for Your Widget ​

The embed widget works with any UAPI agent, but you'll get the best results by designing agents specifically for the embed context.

Text Agent Best Practices ​

Your text agent uses create_agent() just like any other agent. Tips for embed use:

python
from strands import Agent
from strands.models.bedrock import BedrockModel

def create_agent():
    model = BedrockModel(
        model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
        region_name="us-east-1"
    )
    agent = Agent(
        model=model,
        system_prompt="""You are a helpful customer support agent for Acme Corp.

Rules:
- Keep responses concise (2-3 paragraphs max) since users are chatting in a small widget
- Use markdown formatting sparingly — bullet points are fine, but avoid large code blocks
- If you don't know something, say so and offer to connect them with a human
- Be friendly and professional""",
        session_manager=session_manager
    )
    return agent, []

Key design principles:

  • Keep responses short — Widget panels are small; users don't want to scroll through essays
  • Avoid heavy markdown — Code blocks and tables render poorly in compact UIs
  • Be greeting-aware — If you use data-initial-prompt, your system prompt should support context-aware greetings
  • Add tools judiciously — MCP tools work great, but each tool call adds latency the user sees as "typing"

Voice Agent Best Practices ​

Voice agents use create_bidi_agent() (not create_agent()) and run on Amazon Nova Sonic:

python
from strands import Agent
from strands.models.bedrock import BedrockModel

def create_bidi_agent():
    model = BedrockModel(
        model_id="amazon.nova-sonic-v1:0",
        region_name="us-east-1"
    )
    agent = Agent(
        model=model,
        system_prompt="""You are a friendly voice assistant for Acme Corp.

Rules:
- Keep responses to 1-2 sentences — long responses feel unnatural in voice
- Use conversational language, not formal writing
- Avoid lists, bullet points, or anything that sounds weird spoken aloud
- If you need to convey complex info, suggest the user switch to text chat
- Confirm understanding before taking actions"""
    )
    return agent

Key design principles:

  • Ultra-short responses — In voice, anything over 2 sentences feels like a monologue
  • Conversational tone — Write your system prompt as if coaching a human phone agent
  • No visual formatting — Markdown, links, and code are meaningless in audio
  • Use initialPrompt — Set it at the agent or embed level so the agent speaks first without waiting for silence detection
  • Consider latency — Avoid tools that take >2 seconds; voice users expect instant responses

Combining Text + Voice (Both Mode) ​

When using mode: "both", users can switch between tabs. Design each agent for its medium:

bash
curl -X POST https://api.universalapi.co/embed/create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "TEXT_AGENT_ID",
    "voiceAgentId": "VOICE_AGENT_ID",
    "allowedDomains": ["yourdomain.com"],
    "greeting": "Hi! Type a question or click the mic to talk.",
    "initialPrompt": "A visitor just opened the support widget. Greet them briefly."
  }'

initialPrompt applies to text only

The initialPrompt field sends a hidden message to the text agent on load. For voice agents, use the initialPrompt field on the agent record itself (via POST /agent/create or PUT /agent/update) to make the voice agent speak first.

Configuration Options ​

AttributeDescriptionDefault
data-text-agentText chat agent ID—
data-voice-agentVoice agent ID—
data-tokenEmbed token (emb_pk_live_...)Required
data-mode"text", "voice", or "both"Auto-detected
data-position"bottom-right" or "bottom-left""bottom-right"
data-colorBrand color (hex)"#6366f1"
data-greetingGreeting message"Hi! How can I help you today?"
data-titleHeader title (overrides agent name)Agent name
data-subtitleSecondary text below the title—
data-logoCircular avatar image URL in header🤖 emoji
data-initial-promptAuto-sent on load; agent's reply becomes the opening message—
data-placeholderInput field placeholder text"Ask me anything..."
data-size"compact", "standard", or "large""standard"

Customization ​

Branding ​

Add your logo, title, and subtitle to make the widget match your brand:

html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="YOUR_AGENT_ID"
  data-token="emb_pk_live_..."
  data-title="Acme Support"
  data-subtitle="We typically reply instantly"
  data-logo="https://yourdomain.com/logo.png"
  data-color="#0066cc"
  async></script>

Initial Prompt (Contextual Greeting) ​

Instead of a static greeting, use data-initial-prompt to send a hidden message to the agent on load. The agent's response becomes the opening message — perfect for context-aware greetings:

html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="YOUR_AGENT_ID"
  data-token="emb_pk_live_..."
  data-initial-prompt="The user just landed on the pricing page. Greet them and offer to help compare plans."
  async></script>

The prompt is invisible to the user — they only see the agent's reply as the first message.

Size Presets ​

Choose from three sizes to fit your layout:

SizeDimensionsBest for
compact340 × 480Mobile-first sites, sidebars
standard400 × 580Most websites (default)
large440 × 640Desktop-focused, complex agents
html
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="YOUR_AGENT_ID"
  data-token="emb_pk_live_..."
  data-size="large"
  async></script>

Auto-Open on Page Load ​

Make the widget open automatically when the page loads — great for landing pages, onboarding flows, or support pages:

html
<!-- Open immediately on load -->
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="YOUR_AGENT_ID"
  data-token="emb_pk_live_..."
  data-auto-open
  async></script>

<!-- Open after 2 seconds (useful to let cookie banners settle) -->
<script src="https://cdn.universalapi.co/embed/widget.js"
  data-text-agent="YOUR_AGENT_ID"
  data-token="emb_pk_live_..."
  data-auto-open
  data-auto-open-delay="2000"
  async></script>

You can also configure auto-open via the API when creating the embed token:

bash
curl -X POST https://api.universalapi.co/embed/create \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "YOUR_AGENT_ID",
    "autoOpen": true,
    "autoOpenDelay": 1500
  }'

TIP

The auto-open uses a two-phase timing strategy (requestAnimationFrame + setTimeout) to reliably open after the browser has painted the widget. This prevents race conditions with cookie consent banners, third-party chat widgets, and other elements that compete for z-index on page load.

JavaScript API ​

Control the widget programmatically via window.UniversalAPIWidget:

javascript
// Open/close the widget
UniversalAPIWidget.open();
UniversalAPIWidget.close();

// Switch between text and voice (only in "both" mode)
UniversalAPIWidget.switchTo('voice');
UniversalAPIWidget.switchTo('text');

// Listen for events
UniversalAPIWidget.on('ready', () => console.log('Widget loaded'));
UniversalAPIWidget.on('open', () => console.log('Panel opened'));
UniversalAPIWidget.on('close', () => console.log('Panel closed'));
UniversalAPIWidget.on('voiceStart', () => console.log('Voice session started'));
UniversalAPIWidget.on('voiceEnd', () => console.log('Voice session ended'));
UniversalAPIWidget.on('transcript', (msg) => console.log('Transcript:', msg));

// Check state
console.log(UniversalAPIWidget.getMode());      // "text" | "voice" | "both"
console.log(UniversalAPIWidget.getActiveTab());  // "text" | "voice"
console.log(UniversalAPIWidget.isReady());       // true/false
console.log(UniversalAPIWidget.version);         // "3.0.0"

Security ​

  • Domain allowlist — Tokens only work on approved domains
  • Rate limiting — Per-day and per-IP limits prevent abuse
  • Token revocation — Instantly disable a widget by revoking its token
  • Shadow DOM isolation — Widget styles don't leak into your page
  • No credentials exposed — The emb_pk_live_ token is a restricted publishable key; your API keys are never sent to the browser

Billing ​

Widget usage is billed to the token owner's account at standard rates:

  • Text chat: Normal agent invocation credits (varies by model + tools used)
  • Voice: ~50 credits/minute while connected (Nova Sonic real-time audio)

See Pricing for details.

Universal API - The agentic entry point to the universe of APIs