Developers

Integration guide

Losono agents can be integrated in three ways: a drop-in embed widget for chat and voice, a custom API for server-side chat and bespoke UIs, or lead tracking to capture external form submissions and click events on your existing site. All paths use the same published agent and can be combined — they differ in setup, auth, and what you build on your side.

Quick reference

Embed loader
https://losono.xorora.com/embed.js
Hosted widget
https://losono.xorora.com/embed/{slug}
Chat API
POST https://losono.xorora.com/api/agents/{agentId}/chat
Voice API
GET/POST https://losono.xorora.com/api/agents/{agentId}/voice?mode=deploy
API key prefix
losono_sk_
Track loader
https://losono.xorora.com/track.js
Track API
POST https://losono.xorora.com/api/agents/{agentId}/track
Freeform form API
POST https://losono.xorora.com/api/agents/{agentId}/forms/submit
Registered form API
POST https://losono.xorora.com/api/agents/{agentId}/forms/{formId}/submit
Public slug lookup
GET https://losono.xorora.com/api/public/agents/{slug}
Track config API
GET https://losono.xorora.com/api/agents/{agentId}/track/config
Visitor ID key
losono_visitor_id (shared with embed widget)
Agent identifiers
Embed and track.js use slug · API uses agentId (UUID from dashboard URL)

Choose an integration

Pick the path that matches your product. You can combine all three on the same agent.

Embed widgetCustom APILead tracking
Best forMarketing sites, docs, Shopify, WebflowServer-side chat, mobile apps, fully custom UIExisting site forms and intent clicks (brochure downloads, product views)
Setup timeOne script tagAPI key + HTTP/WebSocket clientOne script tag (+ optional server webhook)
API keyNot requiredRequired (or allowed-origin browser calls)Not required in browser; required for server-side form POST
ChatBuilt-in UIYou implement UI + streaming parserN/A
VoiceToggle on deploy page (Pro plan)WebSocket to Gemini Live via session token (Pro plan)N/A
CRM syncPre-chat form (auto)N/AExternal forms (auto); sessions (manual from dashboard)
Identifierdata-agent="slug"agentId in URL pathdata-agent="slug" in browser; agentId for server webhooks

Embed widget (recommended)

Add a floating chat launcher to any website with a single script tag. Losono hosts the widget UI in a secure iframe — no API key is exposed on your domain.

When to use the embed

  • You want the fastest path to production on a website.
  • You are fine with Losono's default chat (and optional voice) UI.
  • You do not want to manage API keys in your frontend.
  • You need chat + optional voice without building WebSocket audio capture.

Prerequisites

  • A Losono account with at least one agent configured (prompt + optional context files).
  • The agent must be published.
  • For voice in the embed: Pro subscription, voice enabled on the agent, and Chat + voice mode on the deploy page.

Step-by-step

  1. 1. Create and configure your agent

    Sign in to the dashboard, create an agent, write a system prompt, and upload any context documents you want the agent to reference.

  2. 2. Test in the playground

    Open the agent playground and verify chat (and voice, if needed) before going live. Playground mode uses your logged-in session — it is separate from the public embed.

  3. 3. Publish the agent

    Go to Deploy and click Publish. The embed URL and script snippet are inactive until the agent is published.

  4. 4. Configure embed settings (optional)

    On the deploy page, customize:

    • Greeting — first message shown in the widget
    • Primary color — header and user bubble color
    • Launcher position — bottom-right (default) or bottom-left
    • Modes — chat only, or chat + voice
    • Allowed origins — restrict which domains may call the chat API from the iframe (empty = allow all)

    Click Save embed settings after changes.

  5. 5. Copy the script snippet

    From the deploy page, copy the script snippet. It looks like this:

    <script src="https://losono.xorora.com/embed.js" data-agent="your-agent-slug"></script>
  6. 6. Add the script to your site

    Paste the snippet before the closing </body>tag on every page where you want the widget. The loader creates a fixed-position iframe pointing at Losono's hosted widget.

    Minimal HTML example

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>My site</title>
      </head>
      <body>
        <h1>Welcome</h1>
    
        <!-- Losono embed — one line -->
        <script src="https://losono.xorora.com/embed.js" data-agent="your-agent-slug"></script>
      </body>
    </html>
  7. 7. Verify

    Load your site. You should see a circular launcher in the corner. Click it to open chat. Messages stream in real time and conversations persist per browser via localStorage.

Script attributes

AttributeRequiredDescription
data-agentYesPublished agent slug (from deploy page)
data-positionNobottom-right (default) or bottom-left
srcYesMust point at https://losono.xorora.com/embed.js. The loader derives the iframe origin from this URL.

Custom launcher position

<script
  src="https://losono.xorora.com/embed.js"
  data-agent="your-agent-slug"
  data-position="bottom-left">
</script>

Enabling voice in the embed

  1. 1. Upgrade to Pro and enable voice on the agent

    Voice requires an active Pro subscription and voice enabled for that agent in the dashboard.

  2. 2. Set modes to Chat + voice

    On the deploy page, change Modes to Chat + voice and save.

  3. 3. Redeploy the script (no changes needed)

    The hosted iframe picks up settings server-side. Users will see Chat and Voice tabs inside the widget. Microphone permission is requested when starting a voice session.

The embed loader sets allow="microphone" on the iframe so voice works inside the hosted widget.

Alternative: direct iframe URL

Instead of the script loader, you can embed the full widget page directly. Useful for internal tools or when you control iframe sizing yourself.

<iframe
  src="https://losono.xorora.com/embed/your-agent-slug"
  title="Losono agent"
  allow="microphone"
  style="border:0;width:400px;height:640px;">
</iframe>

Hosted URL: https://losono.xorora.com/embed/your-agent-slug

How the embed works (for implementers and LLMs)

  1. embed.js reads data-agent and creates an iframe at https://losono.xorora.com/embed/{slug}.
  2. The iframe loads Losono's EmbedWidget component, which calls POST /api/agents/{agentId}/chat with mode: "chat" and a generated visitorId.
  3. Auth for chat requests from the iframe uses the Referer header (/embed/{slug}) — no API key on your domain.
  4. The parent page and iframe communicate via postMessage (losono:embed:resize, losono:embed:close) to resize the launcher and handle overlay clicks.
  5. Conversations and messages are stored in the visitor's browser localStorage under keys prefixed with losono_.
  6. The embed widget and track.js share the same losono_visitor_id localStorage key. A visitor who submits an external form and later opens the chat widget can be correlated by visitor ID in the dashboard.

Custom API integration

Build your own chat UI or voice client. Use a server-side API key for backend integrations; use allowed origins only when you must call chat from a browser without exposing a secret.

When to use the custom API

  • Chat runs on your server (e.g. support ticket enrichment, Slack bot).
  • You need a fully branded UI that does not use the Losono widget.
  • You are building a mobile or desktop app.
  • You want programmatic control over message history and streaming.

Prerequisites

  • Agent is published.
  • You know the agent's agentId (UUID in the dashboard URL: /agents/{agentId}/...).
  • An API key created on the deploy page (for server-side or voice).
  • For voice: Pro plan, voice enabled on the agent, and a client that can capture/play PCM audio.

Step-by-step

  1. 1. Publish the agent

    Same as the embed path — the agent must be published before deploy APIs accept traffic.

  2. 2. Copy the agent ID

    Open your agent in the dashboard. The URL contains the UUID:

    /agents/550e8400-e29b-41d4-a716-446655440000/deploy

    Use this agentId in all API paths. Do not confuse it with the embed slug.

  3. 3. Create an API key

    On the deploy page, enter a key name (e.g. "Production backend") and click Generate key. Copy the secret immediately — it is shown only once.

    losono_sk_...

    Store keys in environment variables or a secrets manager. Never commit them to git or ship them in client-side bundles.

  4. 4. Send a chat message

    POST to the chat endpoint with mode: "chat" (required for deployed access). Include the full message history on each request.

    curl — streaming chat

    curl -N -X POST "https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/chat" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer losono_sk_..." \
      -d '{
        "mode": "chat",
        "visitorId": "user-123",
        "messages": [
          {
            "id": "msg-1",
            "role": "user",
            "parts": [{ "type": "text", "text": "What are your hours?" }]
          }
        ]
      }'

    The response is an AI SDK UI message stream. Read X-Conversation-Id from response headers and pass it on the next request to continue the same conversation.

  5. 5. (Optional) Add voice

    Check availability, start a session, connect to the returned WebSocket URL, and stream PCM audio. See the voice section below for the full protocol.

Chat API reference

POST https://losono.xorora.com/api/agents/{agentId}/chat

Request body

{
  "mode": "chat",
  "visitorId": "optional-stable-visitor-id",
  "conversationId": "optional-existing-conversation-uuid",
  "messages": [
    {
      "id": "unique-message-id",
      "role": "user",
      "parts": [{ "type": "text", "text": "Hello" }]
    }
  ]
}
FieldRequiredNotes
modeYes*Must be "chat" for production. Default "playground" is dashboard-only (session auth).
messagesYesNon-empty array. Last user message is used for RAG retrieval.
conversationIdNoOmit on first message; reuse value from X-Conversation-Id header afterward.
visitorIdNoStable ID for analytics and conversation grouping in logs.

Response

  • Body: AI SDK UI message stream (SSE-compatible)
  • Header: X-Conversation-Id: {uuid}

Node.js — server-side chat (no streaming UI)

const AGENT_ID = "550e8400-e29b-41d4-a716-446655440000";
const API_KEY = process.env.LOSONO_API_KEY;

async function askLosono(userText, conversationId) {
  const response = await fetch(
    `https://losono.xorora.com/api/agents/${AGENT_ID}/chat`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({
        mode: "chat",
        conversationId,
        visitorId: "server-job-1",
        messages: [
          {
            id: crypto.randomUUID(),
            role: "user",
            parts: [{ type: "text", text: userText }],
          },
        ],
      }),
    },
  );

  if (!response.ok) {
    throw new Error(`Chat failed: ${response.status}`);
  }

  const nextConversationId =
    response.headers.get("X-Conversation-Id") ?? conversationId;

  // Parse AI SDK stream — use @ai-sdk/ui-utils or your preferred parser
  const text = await response.text();
  return { text, conversationId: nextConversationId };
}

React — streaming with AI SDK

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";

const AGENT_ID = "550e8400-e29b-41d4-a716-446655440000";
const API_KEY = process.env.NEXT_PUBLIC_LOSONO_KEY; // only if proxied — prefer a server route

export function CustomChat() {
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({
      api: `/api/agents/${AGENT_ID}/chat`,
      headers: { Authorization: `Bearer ${API_KEY}` },
      body: { mode: "chat", visitorId: "web-user-1" },
    }),
  });

  // Render messages and an input that calls sendMessage({ text: "..." })
}

Recommended pattern: expose a thin API route on your server that attaches the Bearer token, so the key never ships to browsers.

Voice API reference

Voice requires Pro, voice enabled on the agent, and ?mode=deploy on all voice routes. Reference implementation: src/components/voice/agent-voice.tsx.

  1. 1. Check availability

    GET https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/voice?mode=deploy&apiKey=losono_sk_...&visitorId=optional
    
    // 200 OK
    { "voiceAvailable": true, "mode": "deploy" }
    
    // 403 — voice not available on this plan/agent
    { "voiceAvailable": false, "reason": "...", "code": "voice_unavailable" }
  2. 2. Start a session

    POST https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/voice?mode=deploy&apiKey=losono_sk_...
    Content-Type: application/json
    
    {}
    
    // 200 OK
    {
      "conversationId": "uuid",
      "preview": { "userPrompt": "...", "context": [...] },
      "wsUrl": "wss://generativelanguage.googleapis.com/ws/...?access_token=..."
    }

    You can pass the API key as Authorization: Bearer losono_sk_... instead of the apiKey query parameter.

  3. 3. Connect to WebSocket and send setup

    Open wsUrl in a WebSocket client. On open, send an empty setup frame (system prompt is locked in the ephemeral token):

    { "setup": {} }

    Wait for setupComplete in the first server message before streaming audio.

  4. 4. Stream microphone audio

    Send 16 kHz PCM chunks as base64 inside Gemini realtime input frames:

    {
      "realtimeInput": {
        "audio": {
          "mimeType": "audio/pcm;rate=16000",
          "data": "<base64-encoded-int16-pcm>"
        }
      }
    }

    Losono ships AudioWorklet processors at /audio-worklets/capture-processor.js and /audio-worklets/playback-processor.js for browser clients. Playback audio arrives as base64 PCM at 24 kHz in server messages.

  5. 5. Handle server events

    Parse Gemini Live WebSocket messages for:

    • inputTranscription / outputTranscription — show live captions
    • modelTurn.parts[].inlineData — assistant audio (base64 PCM)
    • interrupted / turnComplete — turn boundaries
  6. 6. Persist transcripts (optional)

    PATCH https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/voice?mode=deploy&apiKey=losono_sk_...
    Content-Type: application/json
    
    {
      "action": "transcript",
      "conversationId": "uuid-from-session",
      "role": "user",
      "text": "Transcribed user speech"
    }
  7. 7. End session and record usage

    PATCH https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/voice?mode=deploy&apiKey=losono_sk_...
    Content-Type: application/json
    
    {
      "action": "complete",
      "conversationId": "uuid-from-session",
      "sessionStartedAt": 1710000000000
    }

    Call this when the user hangs up so voice minutes are billed correctly.

Lead tracking

Capture form submissions and click events from your existing website without replacing your UI. Data appears in the dashboard and can sync to Sales CRM.

What gets captured

  • External form submissions — registered forms (validated against a schema you define) or freeform key-value payloads from any HTML form or server handler.
  • Click and behavior events — individual interactions (e.g. brochure downloads, product views) grouped into visitor sessions with a 30-minute inactivity window.

Where data appears

  • Forms page — external form definitions, submissions table, and CRM field mapping for form fields.
  • Tracking page — script snippet, sessions list, event log, and manual session export to CRM.

CRM export behavior

  • Pre-chat and external form submissions sync to Sales CRM automatically on submit when CRM is connected and field mapping is complete.
  • Tracking sessions export to CRM manually from the Tracking page (bulk export and retry, same pattern as form exports).

Prerequisites

  • Agent is published.
  • For browser calls from your domain: configure Allowed origins on the deploy page (same as embed iframe chat).
  • For server-side form POST: create an API key on the deploy page.

track.js setup

Add lead tracking to any page with a single script tag. No API key is exposed in HTML — browser requests authenticate via allowed origins.

  1. 1. Publish the agent

    Same as the embed path — tracking endpoints reject traffic until the agent is published.

  2. 2. Register external forms (optional)

    On the agent Forms page, create registered forms with field schemas. Skip this step if you only need freeform submissions.

  3. 3. Configure allowed origins

    On the deploy page, add your site origins (e.g. https://www.example.com). Browser calls fromtrack.js require a matching Origin header.

  4. 4. Copy the track.js snippet

    From the agent Tracking page, copy the script tag:

    <script src="https://losono.xorora.com/track.js" data-agent="your-agent-slug"></script>
  5. 5. Add the script before </body>

    Paste on every page where you want click tracking or declarative form capture. The loader resolves your agent slug via GET /api/public/agents/{slug}, then loads tracking limits and registered forms from GET /api/agents/{agentId}/track/config — no UUID in the snippet.

  6. 6. Verify in the dashboard

    Trigger a test click or form submit, then check the Tracking page for new sessions and events.

Declarative HTML

Click tracking

<!-- Declarative click tracking -->
<a href="/brochure.pdf" data-losono-track="document_open"
   data-losono-props='{"documentId":"brochure-2024"}'>
  Download brochure
</a>

Registered form capture

<!-- Declarative form capture (registered form slug) -->
<form data-losono-form="contact">
  <input name="email" type="email" required />
  <input name="name" type="text" />
  <button type="submit">Send</button>
</form>

Form capture modes

By default, data-losono-form intercepts the submit event (preventDefault) and sends data to Losono only — your native form POST or email handler will not run. Listen for losono:submitted or losono:submit-error on the form element for client-side feedback.

To capture in Losono and keep native browser submission (e.g. Formspree, Netlify Forms), add data-losono-form-mode="dual":

Dual capture (Losono + native submit)

<form data-losono-form="contact" data-losono-form-mode="dual" action="/thanks" method="post">
  <input name="email" type="email" required />
  <button type="submit">Send</button>
</form>

Programmatic API

After track.js loads, use the global window.Losono object:

// Programmatic API (after track.js loads)
Losono.track("product_view", { productId: "sku-123", name: "Widget Pro" });
Losono.identify({ email: "user@example.com", name: "Jane" });
Losono.submitForm({ email: "user@example.com", message: "Hello" });

Losono.identify() stores traits in localStorage (key losono_traits) alongside losono_visitor_id. Subsequent events and form submissions include those traits automatically.

Server-side form webhook

For backend form handlers, POST directly to the freeform form API with a Bearer token — copy the webhook URL and API key from the Forms page:

POST https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/forms/submit
Authorization: Bearer losono_sk_...

Click events API

Ingest click and behavior events. Events are grouped into sessions server-side — clients do not manage session IDs.

POST https://losono.xorora.com/api/agents/{agentId}/track

Tracking config

Fetch limits, registered form schemas, and known event names (for dashboard validation) via:

GET https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/track/config

track.js loads this after resolving the agent slug. Dashboard owners can also call this endpoint while signed in.

Single event

{
  "visitorId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "event": "document_open",
  "properties": {
    "documentId": "brochure-2024",
    "title": "Product brochure"
  },
  "pageUrl": "https://client.com/products",
  "timestamp": "2026-06-30T12:00:00Z"
}

Batch events

{
  "visitorId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "referrer": "https://google.com",
  "events": [
    {
      "event": "page_view",
      "pageUrl": "https://client.com/pricing"
    },
    {
      "event": "cta_click",
      "properties": { "label": "Start trial" }
    }
  ]
}

Batch size is capped at 20 events per request. Each event's properties object must be at most 8 KB when serialized.

Request fields

FieldRequiredNotes
visitorIdYesStable UUID; track.js persists this in losono_visitor_id
eventYes*Event name for single-event body; omit when using events array
eventsNoBatch alternative to top-level event
propertiesNoArbitrary JSON object (max 8 KB per event)
pageUrlNoPage where the event occurred
timestampNoISO 8601 client timestamp
referrerNoStored on new sessions only

Response

{
  "sessionId": "uuid",
  "eventIds": ["uuid", "uuid"],
  "eventCount": 12
}

Session behavior

Losono finds an open session for (agentId, visitorId) where lastActivityAt is within the last 30 minutes. If none exists, a new session is created with landing page and referrer. Each ingested event increments eventCount and updates the session summary. Sessions are stored only — no CRM call on ingest.

Auth and rate limits

  • Browser: Origin must match an allowed origin on the agent. CORS headers are returned on success.
  • Server: Authorization: Bearer losono_sk_... for batch ingest from backends.
  • Rate limit: 100 events per visitor per minute. Exceeding returns 429 rate_limited.

External forms API

Capture leads from HTML forms on your site or from server-side handlers. Registered forms validate against dashboard schemas; freeform accepts any key-value payload.

Registered forms

Create the form in the dashboard first, then submit to:

POST https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/forms/contact/submit

Use the form slug or UUID as formId. With track.js, set data-losono-form="contact" on the <form> element — the script intercepts submit and POSTs to this endpoint. Field validation matches the registered schema.

Request body

{
  "visitorId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "responses": {
    "email": "jane@example.com",
    "name": "Jane Doe"
  },
  "pageUrl": "https://example.com/contact",
  "metadata": { "utm_source": "google" }
}

Response

{
  "submissionId": "uuid",
  "formId": "uuid",
  "submitted": true
}

Freeform submissions

Accept any responses object without a pre-registered schema:

POST https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/forms/submit

curl — server-side freeform submit

curl -X POST "https://losono.xorora.com/api/agents/550e8400-e29b-41d4-a716-446655440000/forms/submit" \
  -H "Authorization: Bearer losono_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "visitorId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "responses": { "email": "jane@example.com", "company": "Acme" },
    "pageUrl": "https://example.com/contact",
    "formName": "Contact page"
  }'

Response

{
  "submissionId": "uuid",
  "submitted": true
}

CRM field mapping

  • Registered forms: map each form field to CRM columns on the Forms page ( namespaced keys like external:{formId}:{fieldId}).
  • Freeform: common keys such as email, name, and phone auto-map via normalized label matching; configure explicit mappings for other keys on the Forms page.
  • Submissions sync to Sales CRM automatically on success when CRM is connected and mapping is complete (same as pre-chat forms).

Sales CRM sync

Developer-oriented summary of how leads reach your CRM. OAuth and field mapping UI live in the dashboard.

SourceSync triggerDashboard
Pre-chat formAuto on submitForms
External formAuto on submitForms
Tracking sessionManual exportTracking

Idempotency

Export records use Losono UUIDs as idempotency keys (leadSource + leadSourceId). Retrying a failed export does not create duplicate CRM records when the original sync succeeded.

Session export fields

Session exports expose virtual fields you can map to CRM columns:

  • session:visitor_id, session:landing_page, session:referrer
  • session:event_count, session:last_event, session:events_summary

Authentication

How Losono validates requests depends on the integration path. Track and form endpoints share the same deploy access rules as chat.

Embed widget

No API key. Chat and voice requests from the hosted iframe authenticate via the Referer header (https://losono.xorora.com/embed/{slug}).

Custom API — Bearer token (recommended)

Authorization: Bearer losono_sk_...

Create keys on the agent deploy page. Keys use the losono_sk_ prefix and are stored hashed server-side. Revoke compromised keys immediately from the dashboard.

Custom API — allowed origins (browser only)

If you configure Allowed origins on the deploy page and call chat from a browser whose Origin matches, requests may succeed without a Bearer token. This is intended for same-site frontends you control — prefer server-side keys for anything sensitive.

Allowed entries can be full origins (https://app.example.com), hostnames, or wildcards like *.example.com. Empty list = allow all origins.

Voice API key placement

Voice routes accept the key as either:

  • Authorization: Bearer losono_sk_... header, or
  • ?apiKey=losono_sk_... query parameter (useful for WebSocket-adjacent fetch calls)

Lead tracking auth

  • Browser (track.js): No API key in HTML. Requests authenticate via allowed origins on the agent — the same mechanism as embed iframe chat. track.js resolves the agent slug through GET https://losono.xorora.com/api/public/agents/your-agent-slug.
  • Server-side form POST: Authorization: Bearer losono_sk_... on POST /api/agents/{agentId}/forms/submit (and registered form endpoints).
  • visitorId: Client-generated UUID persisted in localStorage under losono_visitor_id. Shared with the embed widget for cross-surface visitor correlation.

Message format

Chat uses Vercel AI SDK UI messages. Each message has a role and a parts array.

{
  "id": "unique-string",
  "role": "user" | "assistant" | "system",
  "parts": [
    { "type": "text", "text": "Message content" }
  ]
}

Send the full conversation history on every chat request. The API extracts text from the last user message for RAG retrieval and persists both user and assistant turns when streaming completes.

Streaming responses follow the AI SDK UI message stream protocol. Use @ai-sdk/react with DefaultChatTransport or parse SSE events manually.

Error codes

Common JSON error bodies returned by deploy, track, and form APIs.

HTTPCodeMeaning
401unauthorizedMissing or invalid auth (API key, origin, or referer)
401invalid_api_keyBearer token is not a valid active key for this agent
403agent_not_publishedAgent exists but is not published
400messages_requiredChat body missing messages array
400user_message_requiredNo non-empty user text in messages
400invalid_modemode must be chat or playground
400invalid_jsonMalformed JSON request body (track/form endpoints)
400visitor_id_requiredMissing visitorId on track or form submit
400validation_failedInvalid event payload, batch size, properties size, or registered form field validation
404form_not_foundUnknown form slug or UUID on registered form submit
429rate_limitedMore than 100 track events per visitor per minute
403voice_unavailablePro/voice not enabled or not allowed for this agent
503gemini_not_configuredServer missing GOOGLE_GENERATIVE_AI_API_KEY
500conversation_failedCould not create or load conversation
500retrieval_failedRAG retrieval error