# TypeScript

Connect over a plain WebSocket to send microphone audio through the .wave Live API and receive the model’s audio plus transcripts.

[Create an account](https://api.dotwave.ai/auth/signup) · [ws package reference](https://github.com/websockets/ws)

> **Coming from the OpenAI Realtime API?** This guide uses the [Live API](https://dotwave.ai/docs/live/). Live sessions follow the Live convention, which is not Realtime-shaped: there is no `input_audio_buffer`, speech does not arrive inside a `response`, and the transcripts are their own event streams. This guide uses the plain `ws` package, not an SDK. For transcription in the Realtime format, see the [Realtime API](https://dotwave.ai/docs/realtime/).

## Install and connect

```bash
npm install ws
export DOTWAVE_API_KEY="wk_…"
```

```typescript
import WebSocket from 'ws';

const ws = new WebSocket('wss://api.dotwave.ai/v1/live/sessions', {
  headers: { Authorization: `Bearer ${process.env.DOTWAVE_API_KEY}` },
});

ws.on('open', () => {
  // session.start must be the first event, or the socket refuses it.
  ws.send(JSON.stringify({
    type: 'session.start',
    session: {
      model: 'nemotron-voicechat',
      audio: {
        input: {
          format: { type: 'audio/pcm', rate: 24000 },
        },
        output: {
          format: { type: 'audio/pcm', rate: 24000 },
          encoding: 'base64',
        },
      },
    },
  }));
});

ws.on(
  'message',
  raw => {
    const event = JSON.parse(raw.toString());
    switch (event.type) {
      case 'session.input_transcript.delta':
        process.stdout.write(event.delta);
        break;
      case 'session.output_transcript.delta':
        process.stdout.write(event.delta);
        break;
      case 'session.output_audio.delta':
        playPcm24k(Buffer.from(event.delta, 'base64'));
        break;
    }
  },
);
```

> **Keep API keys on your server.** For a browser client, create a short-lived client secret on the server.

## Microphone source

Configure your capture library for mono PCM16 and pass each chunk straight to this function. The capture library controls the device; this function controls the socket.

```typescript
function sendPcmChunk(chunk: Buffer) {
  ws.send(JSON.stringify({
    type: 'session.input_audio.append',
    audio: chunk.toString('base64'),
  }));
}

// Existing microphone callback:
microphone.on('data', sendPcmChunk);
```

## Send a recorded turn

Raw PCM files have no timing of their own, so pace each chunk against an absolute clock. Live appends audio straight onto the session, so there is no buffer to commit when the turn ends.

```typescript
import { open } from 'node:fs/promises';
import { setTimeout as delay } from 'node:timers/promises';

async function sendPcmFile(path: string) {
  const file = await open(path, 'r');
  const chunk = Buffer.alloc(3840);
  const started = performance.now();
  let sent = 0;

  try {
    while (true) {
      const { bytesRead } = await file.read(chunk, 0, chunk.length, null);
      if (!bytesRead) break;
      sendPcmChunk(chunk.subarray(0, bytesRead));
      sent += bytesRead;
      const audioMs = sent / (24000 * 2) * 1000;
      await delay(Math.max(0, started + audioMs - performance.now()));
    }
  } finally {
    await file.close();
  }
}
```

## Reconnects

Handle capacity errors before audio starts. If the socket closes unexpectedly, open a new session and continue with new audio; do not replay audio from the previous session.

## Limits

Session length, voices and supported fields depend on the model: see its page in [Models](https://dotwave.ai/docs/models/). A field the model does not support is refused, never silently ignored.
