> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tryhamsa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-Time WebSocket API

> Connect to Hamsa's real-time WebSocket API for streaming Text-to-Speech and Speech-to-Text

## Overview

The Hamsa Real-Time WebSocket API enables bidirectional streaming communication for Text-to-Speech (TTS) and Speech-to-Text (STT) operations. A single persistent connection can handle multiple requests without reconnecting.

## Connection

### Endpoint

```text theme={null}
wss://api.tryhamsa.com/v1/realtime/ws
```

### Authentication

Authenticate using your API key via query parameter or header:

<CodeGroup>
  ```bash Query Parameter theme={null}
  wss://api.tryhamsa.com/v1/realtime/ws?api_key=YOUR_API_KEY
  ```

  ```bash Header theme={null}
  X-Api-Key: YOUR_API_KEY
  ```
</CodeGroup>

### Connection Response

Upon successful connection, the server sends:

```json theme={null}
{
  "type": "info",
  "payload": {
    "message": "Connected to realtime WebSocket server"
  }
}
```

<Warning>
  Connections are automatically closed after 60 minutes of inactivity. The server sends ping frames every 30 seconds to keep connections alive.
</Warning>

## Message Format

All messages follow this structure:

```typescript theme={null}
interface WebSocketMessage {
  type: "tts" | "stt" | "response" | "error" | "info" | "ack" | "end";
  payload?: object;
}
```

| Type       | Direction       | Description            |
| ---------- | --------------- | ---------------------- |
| `tts`      | Client → Server | Text-to-Speech request |
| `stt`      | Client → Server | Speech-to-Text request |
| `ack`      | Server → Client | Request acknowledgment |
| `response` | Server → Client | Response data          |
| `end`      | Server → Client | Stream completion      |
| `error`    | Server → Client | Error message          |
| `info`     | Server → Client | Informational message  |

***

## Text-to-Speech (TTS)

Convert text to speech with streaming audio output.

### Request

<ParamField body="type" type="string" required>
  Must be `"tts"`
</ParamField>

<ParamField body="payload" type="object" required>
  <Expandable title="payload properties">
    <ParamField body="text" type="string" required>
      The text to synthesize. Maximum 2000 characters.
    </ParamField>

    <ParamField body="speaker" type="string" required>
      Voice name (e.g. `Amjad`) or the UUID of a custom cloned voice. Pick a speaker that matches the chosen dialect.
    </ParamField>

    <ParamField body="dialect" type="string">
      Dialect to synthesize. One of: `pls`, `egy`, `syr`, `irq`, `jor`, `leb`, `ksa`, `uae`, `bah`, `qat`, `kuw`, `oma`, `msa`, `ar-sa`, `en`. See [Dialects and Voice Examples](#dialects-and-voice-examples) below.
    </ParamField>

    <ParamField body="languageId" default="ar" type="string">
      Language code. Defaults to `"ar"` (Arabic).
    </ParamField>

    <ParamField body="mulaw" default={false} type="boolean">
      Whether to use mu-law audio encoding.
    </ParamField>

    <ParamField body="expressiveness" default={1} type="number">
      Controls how expressive the generated speech sounds, from `0` (flat and monotone) to `2` (highly expressive).
    </ParamField>
  </Expandable>
</ParamField>

### Example Request

```json theme={null}
{
  "type": "tts",
  "payload": {
    "text": "مرحبا بك في خدمة همسة",
    "speaker": "Amjad",
    "dialect": "pls",
    "languageId": "ar",
    "mulaw": false,
    "expressiveness": 1
  }
}
```

### Response Flow

<Steps>
  <Step title="Acknowledgment">
    Server confirms the request was received:

    ```json theme={null}
    {
      "type": "ack",
      "payload": {
        "message": "Real time text to speach connection establesh"
      }
    }
    ```
  </Step>

  <Step title="Audio Stream">
    Server streams raw audio data as binary chunks. Buffer these chunks to reconstruct the complete audio file.
  </Step>

  <Step title="Stream End">
    Server signals completion:

    ```json theme={null}
    {
      "type": "end",
      "payload": {
        "message": "End of TTS stream"
      }
    }
    ```
  </Step>
</Steps>

### Code Example

<CodeGroup>
  ```javascript JavaScript theme={null}
  const ws = new WebSocket('wss://api.tryhamsa.com/v1/realtime/ws?api_key=YOUR_API_KEY');

  const audioChunks = [];

  ws.onopen = () => {
    ws.send(JSON.stringify({
      type: 'tts',
      payload: {
        text: 'مرحبا بك',
        speaker: 'Amjad',
        dialect: 'pls',
        languageId: 'ar',
        mulaw: false,
        expressiveness: 1
      }
    }));
  };

  ws.onmessage = (event) => {
    if (event.data instanceof Blob) {
      // Binary audio chunk
      audioChunks.push(event.data);
    } else {
      const message = JSON.parse(event.data);

      if (message.type === 'end') {
        // Combine all chunks into final audio
        const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
        const audioUrl = URL.createObjectURL(audioBlob);
        const audio = new Audio(audioUrl);
        audio.play();
      }
    }
  };
  ```

  ```python Python theme={null}
  import asyncio
  import websockets
  import json

  async def tts_stream():
      uri = "wss://api.tryhamsa.com/v1/realtime/ws?api_key=YOUR_API_KEY"

      async with websockets.connect(uri) as ws:
          # Send TTS request
          await ws.send(json.dumps({
              "type": "tts",
              "payload": {
                  "text": "مرحبا بك",
                  "speaker": "Amjad",
                  "dialect": "pls",
                  "languageId": "ar",
                  "mulaw": False,
                  "expressiveness": 1
              }
          }))

          audio_chunks = []

          async for message in ws:
              if isinstance(message, bytes):
                  audio_chunks.append(message)
              else:
                  data = json.loads(message)
                  if data["type"] == "end":
                      # Save audio file
                      with open("output.wav", "wb") as f:
                          f.write(b"".join(audio_chunks))
                      break

  asyncio.run(tts_stream())
  ```
</CodeGroup>

### Dialects and Voice Examples

Pick a `speaker` that matches your chosen `dialect`. Voice examples per dialect:

| Dialect     | Code  | Voice examples |
| ----------- | ----- | -------------- |
| Palestinian | `pls` | Amjad, Khayra  |
| Egyptian    | `egy` | Zahra, Subhi   |
| Syrian      | `syr` | Dalal, yara    |
| Iraqi       | `irq` | Lyali, Fatma   |
| Jordanian   | `jor` | samah, Shaker  |
| Lebanese    | `leb` | Carla, Majd    |
| Saudi       | `ksa` | Maram, Hakeem  |
| Emirati     | `uae` | Sameh, Amera   |
| Bahraini    | `bah` | Eyad, Halima   |
| Qatari      | `qat` | Hessa, Nidal   |
| Kuwaiti     | `kuw` | Mai, Haidar    |
| Omani       | `oma` | Aisha, Jaber   |
| MSA / Fusha | `msa` | Salem, Tamim   |
| English     | `en`  | Emily, James   |

***

## Speech-to-Text (STT)

Transcribe audio to text.

### Request

<ParamField body="type" type="string" required>
  Must be `"stt"`
</ParamField>

<ParamField body="payload" type="object" required>
  <Expandable title="payload properties">
    <ParamField body="audioBase64" type="string" required>
      Base64-encoded audio data.
    </ParamField>

    <ParamField body="language" default="ar" type="string">
      Language code for transcription. Defaults to `"ar"` (Arabic).
    </ParamField>

    <ParamField body="isEosEnabled" default type="boolean">
      Enable end-of-speech detection.
    </ParamField>

    <ParamField body="eosThreshold" default={0.3} type="number">
      Threshold for end-of-speech detection (0.0 to 1.0).
    </ParamField>

    <ParamField body="model" default="s2" type="string">
      The STT model to use for transcription. One of: `s2`, `s3`.
    </ParamField>
  </Expandable>
</ParamField>

### Example Request

```json theme={null}
{
  "type": "stt",
  "payload": {
    "audioBase64": "//NExAAAAAANIAcAPABEAEQAQABEAEQARABEA...",
    "language": "ar",
    "isEosEnabled": true,
    "eosThreshold": 0.3,
    "model": "s2"
  }
}
```

### Response

The server sends the transcribed text directly as a plain string (not JSON):

```text theme={null}
مرحبا بك في خدمة همسة
```

### Code Example

<CodeGroup>
  ```javascript JavaScript theme={null}
  const ws = new WebSocket('wss://api.tryhamsa.com/v1/realtime/ws?api_key=YOUR_API_KEY');

  ws.onopen = async () => {
    // Get audio from microphone
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const mediaRecorder = new MediaRecorder(stream);
    const chunks = [];

    mediaRecorder.ondataavailable = (e) => chunks.push(e.data);

    mediaRecorder.onstop = async () => {
      const blob = new Blob(chunks);
      const buffer = await blob.arrayBuffer();
      const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));

      ws.send(JSON.stringify({
        type: 'stt',
        payload: {
          audioBase64: base64,
          language: 'ar',
          isEosEnabled: true,
          eosThreshold: 0.3
        }
      }));
    };

    mediaRecorder.start();
    setTimeout(() => mediaRecorder.stop(), 3000); // Record 3 seconds
  };

  ws.onmessage = (event) => {
    if (typeof event.data === 'string') {
      try {
        const json = JSON.parse(event.data);
        if (json.type === 'error') {
          console.error('Error:', json.payload.message);
        }
      } catch {
        // Plain text transcription result
        console.log('Transcription:', event.data);
      }
    }
  };
  ```

  ```python Python theme={null}
  import asyncio
  import websockets
  import json
  import base64

  async def stt_transcribe():
      uri = "wss://api.tryhamsa.com/v1/realtime/ws?api_key=YOUR_API_KEY"

      async with websockets.connect(uri) as ws:
          # Read audio file
          with open("audio.wav", "rb") as f:
              audio_data = f.read()

          audio_base64 = base64.b64encode(audio_data).decode()

          # Send STT request
          await ws.send(json.dumps({
              "type": "stt",
              "payload": {
                  "audioBase64": audio_base64,
                  "language": "ar",
                  "isEosEnabled": True,
                  "eosThreshold": 0.3
              }
          }))

          # Receive transcription
          response = await ws.recv()
          print(f"Transcription: {response}")

  asyncio.run(stt_transcribe())
  ```
</CodeGroup>

***

## Error Handling

### Error Response Format

```json theme={null}
{
  "type": "error",
  "payload": {
    "message": "Error description"
  }
}
```

### WebSocket Close Codes

| Code   | Description                                             |
| ------ | ------------------------------------------------------- |
| `4001` | Authentication failed - invalid or missing API key      |
| `4003` | Insufficient funds - project wallet balance is depleted |
| `4500` | Internal authentication error                           |
| `1000` | Connection closed due to inactivity (60 min timeout)    |
| `1001` | Server shutting down                                    |

### Common Errors

| Error                                             | Cause                                       |
| ------------------------------------------------- | ------------------------------------------- |
| `Missing API key in headers or query parameters`  | No API key provided                         |
| `API key is invalid or expired`                   | Invalid API key                             |
| `User account is inactive or not found`           | Account issue                               |
| `Project is inactive or not found`                | Project issue                               |
| `Insufficient funds in wallet`                    | Wallet balance is zero or negative          |
| `Invalid message format: missing type or payload` | Malformed message                           |
| `Unsupported message type: [type]`                | Unknown message type                        |
| `Invalid payload for message type: tts`           | TTS validation failed                       |
| `Invalid payload for message type: stt`           | STT validation failed                       |
| `Voice not owned by user`                         | Attempting to use unauthorized cloned voice |

***

## Rate Limiting

* **Limit**: 100 requests per 60 seconds per API key
* Exceeding the limit returns: `Rate limit exceeded for this API key`

***
