Appearance
Embed Widget API Reference
REST API Endpoints
Create Embed Token
Create a new embed token for your widget.
POST /embed/createAuthentication: Required (Bearer token)
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | At least one | Text chat agent ID |
voiceAgentId | string | At least one | Voice/bidi agent ID |
allowedDomains | string[] | No | Domain allowlist (default: ["localhost"]) |
mode | string | No | "text", "voice", or "both" (auto-detected from agents) |
greeting | string | No | Greeting message |
color | string | No | Brand color hex (default: "#6366f1") |
position | string | No | "bottom-right" or "bottom-left" |
agentName | string | No | Display name in widget header |
title | string | No | Header title (overrides agentName display) |
subtitle | string | No | Secondary text under the title (e.g., "Powered by AI") |
logoUrl | string | No | URL of circular avatar image in header |
initialPrompt | string | No | Auto-sent to agent on first load — agent's reply becomes the opening message |
placeholder | string | No | Input box placeholder text (default: "Ask me anything...") |
size | string | No | Widget size: "compact" (340×480), "standard" (400×580), "large" (440×640) |
autoOpen | boolean | No | Auto-open widget panel on page load (default: false) |
autoOpenDelay | number | No | Delay in milliseconds before auto-open (default: 0, max: 30000) |
rateLimitPerDay | number | No | Max requests per day (default: 1000) |
rateLimitPerIp | number | No | Max requests per IP (default: 10) |
minutesLimit | number | No | Voice minutes limit (null = unlimited) |
creditLimit | number | No | Credit usage limit (null = unlimited) |
TIP
You must provide at least one of agentId or voiceAgentId. Providing both enables the dual-mode widget with text/voice toggle tabs.
Response:
json
{
"data": {
"embedTokenId": "emb-a1b2c3d4-...",
"embedToken": "emb_pk_live_...",
"agentId": "text-agent-uuid",
"voiceAgentId": "voice-agent-uuid",
"allowedDomains": ["yourdomain.com", "localhost"],
"config": {
"greeting": "Hi! How can I help?",
"color": "#6366f1",
"position": "bottom-right",
"mode": "both",
"agentName": "AI Assistant"
},
"snippet": "<script src=\"https://cdn.universalapi.co/embed/widget.js\"\n data-text-agent=\"text-agent-uuid\"\n data-voice-agent=\"voice-agent-uuid\"\n data-token=\"emb_pk_live_...\"\n async></script>",
"status": "active"
}
}Get Widget Config
Fetch widget configuration for a given embed token. This is a public endpoint — no user auth required. Validates the token and Origin header.
GET /embed/config?token=emb_pk_live_...Response:
json
{
"data": {
"agentId": "text-agent-uuid",
"voiceAgentId": "voice-agent-uuid",
"agentName": "AI Assistant",
"greeting": "Hi! How can I help?",
"color": "#6366f1",
"position": "bottom-right",
"mode": "both"
}
}List Embed Tokens
List all embed tokens for the authenticated user.
GET /embed/listAuthentication: Required
Response:
json
{
"data": {
"tokens": [
{
"embedTokenId": "emb-a1b2c3d4-...",
"tokenPrefix": "emb_pk_live_",
"agentId": "text-agent-uuid",
"voiceAgentId": "voice-agent-uuid",
"allowedDomains": ["yourdomain.com"],
"config": { ... },
"minutesUsed": 12.5,
"minutesLimit": null,
"status": "active",
"createdAt": 1714000000
}
],
"count": 1
}
}Update Embed Token
Update settings for an existing embed token.
PUT /embed/{embedTokenId}Authentication: Required
Request Body (all fields optional):
| Field | Type | Description |
|---|---|---|
allowedDomains | string[] | New domain allowlist |
rateLimitPerDay | number | New daily rate limit |
rateLimitPerIp | number | New per-IP rate limit |
config | object | Updated widget config (greeting, color, mode, etc.) |
status | string | "active" or "revoked" |
Delete (Revoke) Embed Token
Soft-delete an embed token. The widget stops working immediately.
DELETE /embed/{embedTokenId}Authentication: Required
Embed Chat (Streaming Credentials)
Get streaming credentials for text chat. The widget calls this internally.
POST /embed/chatRequest Body:
json
{
"token": "emb_pk_live_..."
}Response:
json
{
"data": {
"agentId": "text-agent-uuid",
"bearerToken": "uapi_ut_...",
"streamUrl": "https://stream.api.universalapi.co/agent/{agentId}/chat"
}
}The widget uses these credentials to stream directly from the agent API.
Script Tag Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
data-text-agent | string | At least one | Text chat agent ID |
data-voice-agent | string | At least one | Voice/bidi agent ID |
data-token | string | Yes | Embed token (emb_pk_live_...) |
data-mode | string | No | Override mode: "text", "voice", "both" |
data-position | string | No | "bottom-right" (default) or "bottom-left" |
data-color | string | No | Brand color hex |
data-greeting | string | No | Greeting text (shown as static first bubble in text mode) |
data-title | string | No | Header title (overrides agent name) |
data-subtitle | string | No | Secondary text under the title |
data-logo | string | No | URL of circular avatar image in header |
data-initial-prompt | string | No | Auto-sent to agent on load — response is opening message |
data-placeholder | string | No | Input box placeholder text |
data-size | string | No | "compact", "standard" (default), "large" |
data-default-tab | string | No | Which tab to show first in "both" mode: "text" (default) or "voice" |
data-auto-open | string | No | Auto-open the widget panel on page load. Any value (or empty) enables; "false" or "0" disables |
data-auto-open-delay | string | No | Delay in milliseconds before auto-opening (default: "0", max: "30000") |
Auto-Open Reliability
The data-auto-open attribute uses a two-phase timing strategy (requestAnimationFrame + setTimeout) that ensures the widget opens after the page has fully rendered. This avoids race conditions with cookie consent banners, chat widgets, and other elements that compete for attention on page load. Use data-auto-open-delay to add extra time if needed (e.g., "1500" to wait 1.5 seconds for consent banners to finish animating).
JavaScript API (window.UniversalAPIWidget)
Methods
| Method | Arguments | Description |
|---|---|---|
open() | — | Open the widget panel |
close() | — | Close the widget panel |
switchTo(tab) | "text" or "voice" | Switch active tab (only in "both" mode) |
on(event, callback) | event name, function | Register event listener |
getMode() | — | Returns "text", "voice", or "both" |
getActiveTab() | — | Returns "text" or "voice" |
isReady() | — | Returns true after widget has initialized |
Properties
| Property | Type | Description |
|---|---|---|
version | string | Widget version (e.g., "3.0.0") |
Events
| Event | Data | Description |
|---|---|---|
ready | — | Widget has loaded and is ready |
open | — | Panel was opened |
close | — | Panel was closed |
voiceStart | — | Voice WebSocket connected, mic active |
voiceEnd | — | Voice session ended |
transcript | { text, role, is_final } | Voice transcript received |