Skip to main content
layercode-js-sdk.

LayercodeClient

The LayercodeClient is the core client for all JavaScript frontend SDKs, providing audio recording, playback, and real-time communication with the Layercode agent.
import LayercodeClient from "https://cdn.jsdelivr.net/npm/@layercode/js-sdk@latest/dist/layercode-js-sdk.esm.js";

window.layercode = new LayercodeClient({
  agentId: "your-agent-id",
  conversationId: "your-conversation-id", // optional
  authorizeSessionEndpoint: "/api/authorize",
  metadata: { userId: "123" }, // optional

  // Connection lifecycle
  onConnect: ({ conversationId, config }) => {
    console.log("connected", conversationId);
    console.log("agent config", config);
  },
  onDisconnect: () => console.log("disconnected"),
  onError: (err) => console.error("error", err),
  onStatusChange: (status) => console.log("status", status),

  // Messaging & events
  onMessage: (msg) => console.log("message", msg),           // non-audio messages
  onDataMessage: (msg) => console.log("data message", msg),  // response.data messages

  // Audio meters & speaking state
  onUserAmplitudeChange: (amp) => console.log("user amplitude", amp),
  onAgentAmplitudeChange: (amp) => console.log("agent amplitude", amp),
  audioInputChanged: (enabled) => console.log("mic enabled", enabled),
  onUserIsSpeakingChange: (speaking) => console.log("user speaking?", speaking),
  onAgentSpeakingChange: (speaking) => console.log("agent speaking?", speaking),

  // Device & mute state
  onDeviceSwitched: (id) => console.log("active input device", id),
  onDevicesChanged: (list) => console.log("devices changed", list),
  onMuteStateChange: (muted) => console.log("muted?", muted),

  enableAmplitudeMonitoring: false, // skip metering while audio is disabled
});

window.layercode.connect();

Usage Example

Call connect() to start the session once the user is ready, and invoke disconnect() when you want to tear the connection down.

Constructor Options

options
object
required
Options for the LayercodeClient.
options.agentId
string
required
The ID of your Layercode agent.
options.conversationId
string
The conversation ID to resume a previous conversation (optional).
options.authorizeSessionEndpoint
string
required
The endpoint to authorize the session. It must return a JSON object: .
options.metadata
object
Optional metadata to send with the session authorization request.
options.audioInput
boolean
Whether microphone capture should start immediately. Defaults to true. Set to false to initialize the client in text-only mode and defer the permission prompt.
options.audioOutput
boolean
Whether agent audio should play through the browser immediately. Defaults to true. Set to false to keep the connection active while holding back speaker playback until you opt in.
options.enableAmplitudeMonitoring
boolean
Whether microphone and speaker amplitude monitoring should run. Defaults to true. Disable this when audioInput starts as false to avoid unnecessary audio processing.
options.vadResumeDelay
number
Milliseconds before resuming agent audio after a temporary pause due to a false interruption. Defaults to 500.
options.audioInputChanged
function
Callback triggered when the audio input state changes. Receives a boolean.
options.audioOutputChanged
function
Callback triggered when the audio output state changes. Receives a boolean indicating whether the agent audio is audible.
options.onConnect
function
Callback when the client connects. Receives . Use config to inspect the effective agent configuration returned from authorizeSessionEndpoint.
options.onDisconnect
function
Callback when the client disconnects.
options.onError
function
Callback when an error occurs. Receives an Error object.
options.onMessage
function
Callback for all non-audio messages from the server (excludes response.audio).
options.onDataMessage
function
Callback for custom data messages from the server (typically response.data events).
options.onUserAmplitudeChange
function
Callback for changes in the user’s microphone amplitude (number, 0–1).
options.onAgentAmplitudeChange
function
Callback for changes in the agent’s audio amplitude (number, 0–1).
options.onStatusChange
function
Callback when the client’s status changes. Receives a string: “disconnected” | “connecting” | “connected” | “error”.
options.onUserIsSpeakingChange
function
Callback when the SDK detects user speech start/stop. Receives a boolean.
options.onAgentSpeakingChange
function
Callback when the agent starts or stops speaking. Receives a boolean.
options.onMuteStateChange
function
Callback when the client is muted or unmuted. Receives a boolean representing the new muted state.
options.onDeviceSwitched
function
Callback when the active input device changes in the browser. Receives the active deviceId.
options.onDevicesChanged
function
Callback when available input devices change (hot-plug). Receives .

Methods

connect
function
required
connect(): Promise<void> Connects to the Layercode agent, authorizes the session, and starts audio capture and playback.
disconnect
function
required
disconnect(): Promise<void> Disconnects from the Layercode agent, stops audio capture and playback, and closes the WebSocket.
setAudioInput
function
setAudioInput(state: boolean): Promise<void> Enables or disables microphone capture without tearing down the WebSocket. Pass true when the user opts into voice mode; pass false to drop back to text-only mode.
setAudioOutput
function
setAudioOutput(state: boolean): Promise<void> Enables or disables local agent playback without interrupting the active session.
Tip: Call setAudioOutput(false) to silence agent audio while another clip plays in your UI, then restore playback with setAudioOutput(true) when you’re ready. Pair this with the audioOutputChanged callback to keep toggles in sync.
setInputDevice
function
setInputDevice(deviceId: string): Promise<void> Switches the microphone input. Pass ‘default’ (or an empty string) to use the system default device.
listDevices
function
Returns available input devices, marking the default one.
getStream
function
getStream(): MediaStream | null Returns the active microphone MediaStream, or null if not initialized.
mute
function
mute(): void Stop sending mic audio to the server without tearing down the stream/connection.
unmute
function
unmute(): void Resume sending mic audio to the server.

Turn-taking (Push-to-Talk)

Layercode supports both automatic and push-to-talk turn-taking. For push-to-talk, use these methods to signal when the user starts and stops speaking:
triggerUserTurnStarted
function
triggerUserTurnStarted(): Promise<void> Signals that the user has started speaking (for push-to-talk mode). Interrupts any agent audio playback.
triggerUserTurnFinished
function
triggerUserTurnFinished(): Promise<void> Signals that the user has finished speaking (for push-to-talk mode).

Text messages

sendClientResponseText
function
sendClientResponseText(text: string): Promise<void> Ends the active user turn, interrupts agent playback, and forwards text to the agent. The server will emit user.transcript before the agent responds, keeping UI components in sync.
sendClientResponseData
function
sendClientResponseData(payload: Record<string, any>): void Sends a JSON-serializable payload to your agent backend without affecting the current turn. The data surfaces as a data webhook event. See docs page: Send JSON data from the client.

Events & Callbacks

Properties

status
string
Connection status: “disconnected” | “connecting” | “connected” | “error”.
audioInputEnabled
boolean
Indicates whether microphone capture is currently enabled.
audioOutputEnabled
boolean
Indicates whether agent audio playback is currently enabled for this client.
userSpeaking
boolean
Whether the voice activity detector currently hears the user speaking.
agentSpeaking
boolean
Whether the agent is presently speaking (based on audio replay).
userAudioAmplitude
number
User mic amplitude (0–1). Non-zero only when amplitude monitoring is enabled.
agentAudioAmplitude
number
Agent playback amplitude (0–1). Non-zero only when amplitude monitoring is enabled.
isMuted
boolean
Whether the microphone track is muted.
conversationId
string
The conversation ID the client is currently attached to, if any.

Agent Config (from authorizeSessionEndpoint)

On onConnect, you receive { conversationId, config }. Relevant fields:
type AgentConfig = {
  transcription: {
    trigger: 'push_to_talk' | 'automatic';
    can_interrupt: boolean;
  };
  vad?: {
    enabled?: boolean;
    gate_audio?: boolean;            // default: true
    buffer_frames?: number;          // default: 10
    model?: string;                  // default: 'v5'
    positive_speech_threshold?: number; // default: 0.15
    negative_speech_threshold?: number; // default: 0.05
    redemption_frames?: number;      // default: 4
    min_speech_frames?: number;      // default: 2
    pre_speech_pad_frames?: number;  // default: 0
    frame_samples?: number;          // default: 512
  };
};
When transcription.trigger === 'automatic' and vad.enabled !== false, the SDK initializes MicVAD and gates mic audio accordingly. gate_audio controls whether audio is sent only while speaking; buffer_frames controls the “pre-speech” buffer flushed when speech starts.

Notes & Best Practices

  • The SDK manages microphone access, audio streaming, and playback automatically.
  • Use audioInput: false plus setAudioInput(true) to defer the browser permission prompt until the user explicitly switches to voice. Disable amplitude monitoring at the same time to avoid unnecessary processing.
  • The metadata option allows you to set custom data which is then passed to your backend webhook (useful for user/session tracking).
  • The conversationId can be used to resume a previous conversation, or omitted to start a new one.

Small device example

await layercode.setInputDevice('default');
const devices = await layercode.listDevices();
const usb = devices.find(d => /USB/i.test(d.label));
if (usb) await layercode.setInputDevice(usb.deviceId);

Authorizing Sessions

To connect a client (browser) to your Layercode voice agent, you must first authorize the session. The SDK will automatically send a POST request to the path (or url if your backend is on a different domain) passed in the authorizeSessionEndpoint option. In this endpoint, you will need to call the Layercode REST API to generate a client_session_key and conversation_id (if it’s a new conversation).
If your backend is on a different domain, set authorizeSessionEndpoint to the full URL (e.g., https://your-backend.com/api/authorize).
Why is this required? Your Layercode API key should never be exposed to the frontend. Instead, your backend acts as a secure proxy: it receives the frontend’s request, then calls the Layercode authorization API using your secret API key, and finally returns the client_session_key to the frontend. This also allows you to authenticate your user, and set any additional metadata that you want passed to your backend webhook. How it works:
  1. Frontend: The SDK automatically sends a POST request to your authorizeSessionEndpoint with a request body.
  2. Your Backend: Your backend receives this request, then makes a POST request to the Layercode REST API /v1/agents/web/authorize_session endpoint, including your LAYERCODE_API_KEY as a Bearer token in the headers.
  3. Layercode: Layercode responds with a client_session_key (and a conversation_id), which your backend returns to the frontend.
  4. Frontend: The SDK uses the client_session_key to establish a secure WebSocket connection to Layercode.
Example backend authorization endpoint code:
export const dynamic = "force-dynamic";
import { NextResponse } from "next/server";

export const POST = async (request: Request) => {
  // Here you could do any user authorization checks you need for your app
  const endpoint = "https://api.layercode.com/v1/agents/web/authorize_session";
  const apiKey = process.env.LAYERCODE_API_KEY;
  if (!apiKey) {
    throw new Error("LAYERCODE_API_KEY is not set.");
  }
  const requestBody = await request.json();
  if (!requestBody || !requestBody.agent_id) {
    throw new Error("Missing agent_id in request body.");
  }
  try {
    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify(requestBody),
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(text || response.statusText);
    }
    return NextResponse.json(await response.json());
  } catch (error: any) {
    console.log("Layercode authorize session response error:", error.message);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
};

Custom Authorization

Use the optional authorizeSessionRequest function when you need to control how authorization credentials are exchanged with your backend (for example, to add custom headers or reuse an existing HTTP client).
Custom Authorization example
import LayercodeClient from "@layercode/js-sdk";

const client = new LayercodeClient({
  agentId: "agent_123",
  authorizeSessionEndpoint: "/api/authorize",
  authorizeSessionRequest: async ({ url, body }) => {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Custom-Header": "my-header",
      },
      body: JSON.stringify(body),
    });

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

    return response;
  },
});
If you omit authorizeSessionRequest, the client falls back to a standard fetch call that POSTs the JSON body to authorizeSessionEndpoint.

Request payload

  • agent_id – ID of the agent to connect.
  • metadata – metadata supplied when instantiating the client.
  • sdk_version – version string of the JavaScript SDK.
  • conversation_id – present only when reconnecting to an existing conversation.

Troubleshooting

AudioWorklet InvalidStateError on first connect

Browsers freeze a freshly created AudioContext until the user interacts with the page. If your app calls connect() during component initialization, audioWorklet.addModule() pauses, the worklet never registers, and the player throws InvalidStateError: AudioWorklet does not have a valid AudioWorkletGlobalScope as soon as audio starts streaming. To avoid the race:
  • Gate the first connect() behind a user gesture (click, tap, key press)—for example, a “Start voice agent” button.
  • Await connect() inside that handler; keep any teardown logic (like disconnect()) in lifecycle cleanup.
  • Expect this to show up most during rapid local reloads or with React Strict Mode double-mounting. In real usage, the user normally clicks before the agent activates, so reconnects after the first gesture are safe.
Once the audio worklet loads successfully the first time, subsequent reconnects can happen automatically within the same session. Just ensure the initial call happens after a gesture.