Live Voice API
What this is
Three HTTP endpoints under /api/live that back the ComStack live voice agent. The browser authenticates, opens a WebSocket connection to the voice AI service, and forwards tool call events back through the server. The server handles session provisioning, tool dispatch, multilingual label resolution, and live call transfer to a team member.
Base URL: https://api.comstack.ai
Authentication
All three endpoints require the same two headers:
| Header | Value |
|---|---|
Authorization | Bearer <Firebase ID Token> — from getIdToken() on the signed-in Firebase user |
x-project-id | Your project’s Firestore document ID |
This auth model is separate from the MCP API key auth used by the /mcp endpoint.
Error responses:
| Status | Cause |
|---|---|
401 | Missing or invalid Authorization header |
400 | Missing x-project-id header |
POST /api/live/session
Mints an ephemeral voice session token. Validates that the user is a project member, that the voice module is enabled, and that the daily session quota has not been reached.
Request
No body required. Auth headers only.
Response 200
{ "token": "projects/.../ephemeralTokens/...", "greeting": "Hi! How can I help you today?", "voice": "Aoede"}| Field | Type | Description |
|---|---|---|
token | string | Ephemeral voice session token. Valid for 30 minutes; single-use. |
greeting | string | Opening line for the agent to speak at session start. Sourced from the project’s live agent configuration. Empty string if not configured. |
voice | string | Voice name sourced from project configuration. |
Guard conditions — 403
| Condition | Error |
|---|---|
| Voice module disabled | "Voice web agent not enabled for this project" |
| User not provisioned | "This user has not been provisioned. Contact a manager." |
| User not a project member | "User is not a member of this project" |
Session quota — 429
The daily session limit is enforced atomically before the token is minted. When the per-user daily count reaches the plan limit, the call returns 429 and no token is issued:
{ "error": "Daily live-session limit reached (5/day). Try again tomorrow or upgrade your plan." }The counter is per user per UTC day. Concurrent session starts cannot burst past the limit — the reservation and mint happen in a single transaction. See Plans and Quotas for limit values by plan.
POST /api/live/session/resume
Mints a fresh token for reconnecting after a WebSocket drop (GoAway, error 1007, or similar). Functionally identical to POST /api/live/session.
Clear the session’s resume handle before calling this endpoint so the new token starts a clean session rather than attempting to resume the broken one.
POST /api/live/tools/execute
The voice AI dispatches tool calls during a session. The browser receives each toolCall event from the WebSocket and forwards it to this endpoint. All tools respond synchronously.
Request
{ "tool_name": "update_profile", "args": { "first_name": "María", "phone": "+34 611 22 33 44" }}| Field | Required | Description |
|---|---|---|
tool_name | yes | Name of the tool to execute. Standard set: update_profile, ask, answer, transfer_to_manager. Feature-gated set (when onboarding module is enabled on the project): search_company, select_company, run_extraction, set_field, confirm. |
args | no | Tool arguments. Defaults to {} if absent. |
Response 200
{ "result": "Language changed to Spanish. Continue speaking in Spanish from now on.", "widget": { "type": "card", "card_id": "profile_card", "template_id": "profile_v1", "dir": "ltr", "data": { "first_name": "María", "last_name": null, "pronoun": "unspecified", "phone": "+34611223344", "contact_email": null, "language": "es-ES", "language_label": "Spanish" }, "labels": { "profile": "Perfil", "first_name": "Nombre", "pronoun": "Pronombre", "email": "Correo", "phone": "Teléfono", "language": "Idioma" }, "pronoun_labels": { "she_her": "Ella", "he_him": "Él", "they_them": "Elle", "unspecified": "No definido" } }}| Field | Description |
|---|---|
result | Text for the agent to speak aloud. Language-change results include the new language name and an instruction to continue in it. |
widget | Card payload to render on screen. Present when a profile card should appear or update. card_id: "profile_card" is stable — always replaces in place. |
widget.dir | Text direction: "ltr" or "rtl" for Arabic, Hebrew, Farsi, Urdu, and other RTL languages. |
widget.labels | Server-resolved translated labels for card fields. Falls back to English if no cached translation is available. |
widget.pronoun_labels | Server-resolved translated pronoun display values. Falls back to English if no cached translation. |
update_profile — field handling
| Arg | Normalization |
|---|---|
first_name / last_name | Trimmed; empty string → null |
pronoun | Fuzzy-mapped — accepts natural forms like "she", "he/him", "non-binary" |
phone | Parsed to E.164 format; country inferred from user’s language setting |
contact_email | Lowercased and trimmed; must contain @ |
language | Matched against language name map or validated as BCP-47 directly. When language changes, the two widget-critical translation scopes are awaited before the response returns; subsequent switches to the same language are instant cache hits. |
Calling update_profile with no args returns the current profile without writing — used to surface the profile card on demand.
transfer_to_manager
Bridges the caller to a human team member:
- The caller is placed in a private conference room on brief hold.
- The server initiates an outbound call to the project’s configured manager phone number.
- Once the manager answers, they join the same conference.
No arguments required. The caller goes from the website to a live team member in seconds. Outbound calls require phone permissions to be enabled for the destination country.
Onboarding tools (feature-gated)
Available only on projects where the onboarding module is enabled. Each tool mutates the visitor’s active onboarding session.
| Tool | Args | Effect |
|---|---|---|
search_company | country (ISO-3166-1 alpha-2), query | Business registry lookup; returns up to 5 candidates. |
select_company | country, registry_id?, place_id?, business_url? | Stores the selected business identity on the session. Clears any previously extracted profile if the identity changed. |
run_extraction | (none) | Full grounded extraction over the selected company. Writes the resulting profile to the session. |
set_field | path, value | Inline edit on an allowlisted session field path. Allowed paths: voice, greeting_draft, primary_language, brand_tone, service_area, opening_hours, summary, industry, target_audience, intent, languages, products_services, unique_selling_points. |
confirm | (none) | Marks the onboarding brief as confirmed. |
All tools return { result: string } where result is the natural sentence the agent speaks.
Errors
| Status | Cause |
|---|---|
400 | tool_name missing from request body |
500 | Tool dispatch threw an unexpected error |
Live agent placement
Where the voice agent appears on the published site is controlled by the live_agent_placement setting in your project’s site configuration:
PATCH /api/projects/:pid/settings/site{ "live_agent_placement": "widget"}| Value | Placement |
|---|---|
homepage | Agent embedded on the homepage |
destination_page | Agent served at a dedicated slug (live_agent_slug) |
widget | Floating widget on all pages |
modal | Full-screen modal |
none | Agent disabled on the site |
In destination_page mode, a page whose slug collides with live_agent_slug is rejected at create/update time. The slug automatically unlocks when the placement mode changes away from destination_page.
Widget labels
The browser has no hardcoded translation maps. All card labels and pronoun translations are resolved server-side from the project’s translation data and sent in the widget payload. The browser only maintains English fallback constants for legacy responses.
When a user switches language for the first time, the server awaits translation before responding — approximately 1–2 seconds. Subsequent switches to the same language return immediately from cache.
Common errors
401 Unauthorized — the Firebase ID Token is expired or invalid. Re-sign the user in and call getIdToken() to get a fresh token.
403 Voice web agent not enabled — the voice module is disabled on this project. Check the project’s module settings or upgrade the plan.
403 User not provisioned — the signed-in user has not been added to this project. A project manager must provision the user account first.
429 Daily live-session limit reached — the per-user daily quota is exhausted. The limit resets at UTC midnight. The error message includes the limit value. Upgrade the plan for a higher quota.
400 tool_name missing — the /tools/execute request body is missing the tool_name field.
Related
- Plans and Quotas — session quota limits and enforcement
- API Overview — authentication and endpoint index
- Translation System — how multilingual content works