{
  "asyncapi": "3.0.0",
  "info": {
    "title": "Munsit WebSocket APIs",
    "version": "1.0.0",
    "description": "WebSocket APIs for real-time text-to-speech generation and live speech-to-text transcription. Each channel declares its own address."
  },
  "servers": {
    "production": {
      "host": "api.munsit.com",
      "protocol": "wss",
      "pathname": "/api/v1/websocket/text-to-speech",
      "description": "Production WebSocket server (see each channel for its path)"
    }
  },
  "channels": {
    "text-to-speech": {
      "address": "wss://api.munsit.com/api/v1/websocket/text-to-speech",
      "messages": {
        "initConnection": {
          "name": "Initialize Connection",
          "title": "Initialize Connection",
          "summary": "Initialize the WebSocket connection",
          "payload": {
            "type": "object",
            "required": [
              "type",
              "voice_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "initConnection"
              },
              "model_id": {
                "type": "string",
                "default": "faseeh-v1-preview"
              },
              "voice_id": {
                "type": "string"
              },
              "voice_settings": {
                "type": "object",
                "properties": {
                  "stability": {
                    "type": "number",
                    "default": 0.5
                  },
                  "similarity_boost": {
                    "type": "number",
                    "default": 0.75
                  },
                  "speed": {
                    "type": "number",
                    "default": 1.0,
                    "minimum": 0.7,
                    "maximum": 1.2
                  }
                }
              },
              "output_format": {
                "type": "string",
                "enum": [
                  "pcm_8000",
                  "pcm_16000",
                  "pcm_22050",
                  "pcm_24000"
                ],
                "default": "pcm_24000",
                "description": "Audio output format"
              },
              "x_api_key": {
                "type": "string"
              }
            }
          }
        },
        "text": {
          "name": "Send Text",
          "title": "Send Text",
          "summary": "Send text for audio generation",
          "payload": {
            "type": "object",
            "required": [
              "type",
              "text"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "text"
              },
              "text": {
                "type": "string"
              },
              "flush": {
                "type": "boolean",
                "default": false
              },
              "try_trigger_generation": {
                "type": "boolean",
                "default": false
              }
            }
          }
        },
        "clear": {
          "name": "Clear Buffer",
          "title": "Clear Buffer",
          "summary": "Clear the text buffer",
          "payload": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "clear"
              }
            }
          }
        },
        "closeConnection": {
          "name": "Close Connection",
          "title": "Close Connection",
          "summary": "Close the WebSocket connection",
          "payload": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "closeConnection"
              }
            }
          }
        },
        "connectionInitialized": {
          "name": "Connection Initialized",
          "title": "Connection Initialized",
          "summary": "Response confirming connection initialization",
          "payload": {
            "type": "object",
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "connectionInitialized"
              }
            }
          }
        },
        "audio": {
          "name": "Audio Response",
          "title": "Audio Response",
          "summary": "Audio chunk response",
          "payload": {
            "type": "object",
            "required": [
              "audio"
            ],
            "properties": {
              "audio": {
                "type": "string",
                "description": "Base64-encoded PCM audio data"
              },
              "sampleRate": {
                "type": "number",
                "default": 24000
              }
            }
          }
        },
        "error": {
          "name": "Error",
          "title": "Error",
          "summary": "Error response",
          "payload": {
            "type": "object",
            "required": [
              "type",
              "errorCode",
              "errorMessage"
            ],
            "properties": {
              "type": {
                "type": "string",
                "const": "error"
              },
              "errorCode": {
                "type": "number",
                "description": "Numeric error code"
              },
              "errorMessage": {
                "type": "string",
                "description": "Human-readable error message"
              }
            }
          }
        }
      },
      "bindings": {
        "ws": {
          "query": {
            "type": "object",
            "properties": {
              "x-api-key": {
                "type": "string",
                "description": "Your Munsit API key"
              }
            },
            "required": [
              "x-api-key"
            ]
          }
        }
      }
    },
    "speech-to-text": {
      "address": "wss://api.munsit.com/api/v1/websocket/speech-to-text",
      "messages": {
        "audio_chunk": {
          "name": "Audio Chunk",
          "title": "Audio Chunk",
          "summary": "Send a chunk of audio bytes for transcription",
          "payload": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "event",
                  "data"
                ],
                "properties": {
                  "event": {
                    "type": "string",
                    "const": "audio_chunk"
                  },
                  "data": {
                    "type": "object",
                    "required": [
                      "audioBuffer"
                    ],
                    "properties": {
                      "audioBuffer": {
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 255
                        },
                        "description": "Audio bytes as Uint8 values. First chunk should be WAV."
                      }
                    }
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "audioBuffer"
                ],
                "properties": {
                  "audioBuffer": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 255
                    },
                    "description": "Compatibility format accepted by server."
                  }
                }
              }
            ]
          }
        },
        "transcription": {
          "name": "Transcription",
          "title": "Transcription",
          "summary": "Transcription text event from server",
          "payload": {
            "type": "object",
            "required": [
              "event",
              "data"
            ],
            "properties": {
              "event": {
                "type": "string",
                "const": "transcription"
              },
              "data": {
                "type": "string",
                "description": "Cumulative transcription text"
              },
              "isFinal": {
                "type": "boolean",
                "description": "True on the final hypothesis emitted in response to end_of_stream. Note /api/v1/listen uses is_final (snake_case) instead."
              }
            }
          }
        },
        "transcription_error": {
          "name": "Transcription Error",
          "title": "Transcription Error",
          "summary": "Error event from server",
          "payload": {
            "type": "object",
            "required": [
              "event",
              "data"
            ],
            "properties": {
              "event": {
                "type": "string",
                "const": "transcription_error"
              },
              "data": {
                "type": "string",
                "description": "Error message"
              }
            }
          }
        },
        "end_of_stream": {
          "name": "End Of Stream",
          "title": "End Of Stream",
          "summary": "Signal end of audio and request a guaranteed final transcript",
          "description": "Transcribes whatever audio is still buffered, at any duration, then emits a final transcription followed by finalized. Closing the socket does NOT flush buffered audio, so send this before closing — otherwise utterances shorter than min_buffer_seconds return nothing.",
          "payload": {
            "type": "object",
            "required": [
              "event"
            ],
            "properties": {
              "event": {
                "type": "string",
                "const": "end_of_stream"
              }
            }
          }
        },
        "finalized": {
          "name": "Finalized",
          "title": "Finalized",
          "summary": "Session complete; carries the full transcript",
          "description": "Emitted once in response to end_of_stream. Safe to close the socket after receiving it.",
          "payload": {
            "type": "object",
            "required": [
              "event",
              "data"
            ],
            "properties": {
              "event": {
                "type": "string",
                "const": "finalized"
              },
              "data": {
                "type": "string",
                "description": "Complete transcript for the session"
              }
            }
          }
        }
      },
      "bindings": {
        "ws": {
          "headers": {
            "type": "object",
            "properties": {
              "x-api-key": {
                "type": "string",
                "description": "API key header"
              },
              "Authorization": {
                "type": "string",
                "description": "Bearer token header"
              }
            }
          },
          "query": {
            "type": "object",
            "properties": {
              "x-api-key": {
                "type": "string",
                "description": "Your Munsit API key"
              },
              "token": {
                "type": "string",
                "description": "Optional auth token for browser WebSocket fallback"
              },
              "min_buffer_seconds": {
                "type": "number",
                "default": 0.5,
                "minimum": 0.1,
                "maximum": 5.0,
                "description": "Seconds of audio that must accumulate before an interim transcription is emitted. Does not gate the final result."
              }
            }
          }
        }
      },
      "title": "Speech-to-Text (Legacy — deprecated)",
      "description": "DEPRECATED: superseded by the /api/v1/listen channel. Remains available for existing integrations; receives no new recognition features (no word timestamps, turns, hotwords, confidence, sentiment, or gender). Finalization (end_of_stream/finalized) and min_buffer_seconds were added as correctness fixes."
    },
    "listen": {
      "address": "wss://api.munsit.com/api/v1/listen",
      "title": "Live Transcription (/api/v1/listen)",
      "description": "Live Arabic speech-to-text: stream binary audio frames, receive typed JSON events (interim/final results with word timestamps and confidence, turn boundaries, per-turn sentiment and speaker gender). Authenticate with the api_key query parameter or the x-api-key header; auth failure closes with code 1008.",
      "messages": {
        "audio": {
          "name": "Audio",
          "title": "Audio (binary frame)",
          "summary": "Raw audio in the encoding/sample_rate declared at connect time; interleaved when channels=2",
          "contentType": "application/octet-stream",
          "payload": {
            "type": "string",
            "format": "binary",
            "description": "Raw audio bytes (e.g. little-endian 16-bit PCM for encoding=linear16). At most 60 seconds ahead of real time, else close 4008."
          }
        },
        "keepAlive": {
          "name": "KeepAlive",
          "title": "KeepAlive (control)",
          "summary": "Reset the 12-second idle timeout during send pauses",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "KeepAlive"
              }
            },
            "required": [
              "type"
            ]
          }
        },
        "configure": {
          "name": "Configure",
          "title": "Configure (control)",
          "summary": "Retune endpointing mid-session",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Configure"
              },
              "endpointing": {
                "type": "integer",
                "minimum": 100,
                "maximum": 5000,
                "description": "Silence window in ms that ends a turn"
              }
            },
            "required": [
              "type",
              "endpointing"
            ]
          }
        },
        "closeStream": {
          "name": "CloseStream",
          "title": "CloseStream (control)",
          "summary": "Finalize any in-progress turn, report billing, close with code 1000",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "CloseStream"
              }
            },
            "required": [
              "type"
            ]
          }
        },
        "metadataOpen": {
          "name": "Metadata (open)",
          "title": "Metadata — session opened",
          "summary": "First event after a successful connection",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Metadata"
              },
              "session_id": {
                "type": "string"
              },
              "correlation_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "model": {
                "type": "string"
              },
              "protocol_version": {
                "type": "integer"
              },
              "channels": {
                "type": "integer",
                "enum": [
                  1,
                  2
                ]
              },
              "sample_rate": {
                "type": "integer",
                "enum": [
                  8000,
                  16000
                ]
              },
              "dropped_hotwords": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Hotword entries that could not be applied: over the 200-entry/40-character limits, or not representable by the model vocabulary"
              },
              "sentiment": {
                "type": "string",
                "enum": [
                  "available",
                  "unavailable"
                ]
              },
              "gender": {
                "type": "string",
                "enum": [
                  "available",
                  "unavailable"
                ]
              }
            }
          }
        },
        "speechStarted": {
          "name": "SpeechStarted",
          "title": "SpeechStarted",
          "summary": "Voice activity confirmed on a channel",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "SpeechStarted"
              },
              "channel": {
                "type": "integer"
              },
              "ts": {
                "type": "number",
                "description": "Audio timestamp in seconds"
              }
            }
          }
        },
        "results": {
          "name": "Results",
          "title": "Results",
          "summary": "Interim or final transcription of the current turn",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Results"
              },
              "channel": {
                "type": "integer"
              },
              "turn_id": {
                "type": "integer"
              },
              "transcript": {
                "type": "string"
              },
              "words": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "word": {
                      "type": "string"
                    },
                    "start": {
                      "type": "number",
                      "description": "Word start time in seconds (session-absolute)"
                    },
                    "end": {
                      "type": "number",
                      "description": "Word end time in seconds (session-absolute)"
                    },
                    "confidence": {
                      "type": "number",
                      "minimum": 0,
                      "maximum": 1
                    }
                  }
                }
              },
              "is_final": {
                "type": "boolean",
                "description": "true: this text will not change"
              },
              "speech_final": {
                "type": "boolean",
                "description": "true: the turn genuinely ended. is_final=true with speech_final=false is a forced split after 60s of unbroken speech; transcription continues under the next turn_id"
              },
              "language": {
                "type": "string",
                "const": "ar"
              },
              "confidence": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Utterance-level confidence on finals; null on interims"
              }
            }
          }
        },
        "utteranceEnd": {
          "name": "UtteranceEnd",
          "title": "UtteranceEnd",
          "summary": "The turn genuinely ended (never sent after forced splits). Sent immediately after the final Results; Gender and Sentiment follow it.",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "UtteranceEnd"
              },
              "channel": {
                "type": "integer"
              },
              "turn_id": {
                "type": "integer"
              },
              "last_word_end": {
                "type": "number"
              }
            }
          }
        },
        "sentiment": {
          "name": "Sentiment",
          "title": "Sentiment",
          "summary": "Per-turn sentiment, sent after each final result (including forced splits), following UtteranceEnd and Gender",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Sentiment"
              },
              "channel": {
                "type": "integer"
              },
              "turn_id": {
                "type": "integer"
              },
              "label": {
                "type": "string",
                "enum": [
                  "positive",
                  "neutral",
                  "negative"
                ]
              },
              "score": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              }
            }
          }
        },
        "gender": {
          "name": "Gender",
          "title": "Gender",
          "summary": "Per-turn speaker gender classified from the turn audio, sent after each final result (including forced splits), following UtteranceEnd",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Gender"
              },
              "channel": {
                "type": "integer"
              },
              "turn_id": {
                "type": "integer"
              },
              "label": {
                "type": "string",
                "enum": [
                  "male",
                  "female"
                ]
              },
              "score": {
                "type": "number",
                "minimum": 0,
                "maximum": 1
              }
            }
          }
        },
        "metadataClose": {
          "name": "Metadata (close)",
          "title": "Metadata — session closed",
          "summary": "Billing summary sent after CloseStream, before close 1000",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Metadata"
              },
              "session_id": {
                "type": "string"
              },
              "audio_seconds_billed": {
                "type": "number",
                "description": "Seconds of audio received x channels"
              },
              "turn_count": {
                "type": "integer"
              }
            }
          }
        },
        "error": {
          "name": "Error",
          "title": "Error",
          "summary": "Structured error. recoverable=false always precedes a close with the matching code; recoverable=true means the session continues",
          "payload": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "Error"
              },
              "code": {
                "type": "integer",
                "description": "1008 policy rejection (authentication failed, concurrent-session limit reached, or insufficient/exhausted wallet balance — see message), 1011 internal/keepalive-timeout, 4002 invalid params, 4008 flow abuse"
              },
              "message": {
                "type": "string"
              },
              "recoverable": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "bindings": {
        "ws": {
          "headers": {
            "type": "object",
            "properties": {
              "x-api-key": {
                "type": "string",
                "description": "API key (server-side clients)"
              }
            }
          },
          "query": {
            "type": "object",
            "properties": {
              "api_key": {
                "type": "string",
                "description": "API key (browser clients)"
              },
              "encoding": {
                "type": "string",
                "enum": [
                  "linear16",
                  "mulaw",
                  "alaw"
                ],
                "default": "linear16"
              },
              "sample_rate": {
                "type": "integer",
                "enum": [
                  8000,
                  16000
                ],
                "default": 8000
              },
              "channels": {
                "type": "integer",
                "enum": [
                  1,
                  2
                ],
                "default": 1
              },
              "language": {
                "type": "string",
                "enum": [
                  "ar"
                ],
                "default": "ar"
              },
              "interim_results": {
                "type": "boolean",
                "default": true
              },
              "endpointing": {
                "type": "integer",
                "minimum": 100,
                "maximum": 5000,
                "default": 800
              },
              "smart_turn": {
                "type": "boolean",
                "default": true
              },
              "hotwords": {
                "type": "string",
                "description": "Comma-separated custom vocabulary, URL-encoded; up to 200 entries of up to 40 characters"
              },
              "correlation_id": {
                "type": "string",
                "maxLength": 128
              },
              "metadata": {
                "type": "string",
                "description": "Base64-encoded JSON object, up to 2 KB"
              },
              "x-api-key": {
                "type": "string",
                "description": "API key (alternative query form; api_key takes precedence)"
              }
            }
          }
        }
      }
    }
  },
  "operations": {
    "sendInitConnection": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/text-to-speech"
      },
      "messages": [
        {
          "$ref": "#/channels/text-to-speech/messages/initConnection"
        }
      ]
    },
    "sendText": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/text-to-speech"
      },
      "messages": [
        {
          "$ref": "#/channels/text-to-speech/messages/text"
        }
      ]
    },
    "receiveAudio": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/text-to-speech"
      },
      "messages": [
        {
          "$ref": "#/channels/text-to-speech/messages/audio"
        },
        {
          "$ref": "#/channels/text-to-speech/messages/connectionInitialized"
        },
        {
          "$ref": "#/channels/text-to-speech/messages/error"
        }
      ]
    },
    "sendSttAudioChunk": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/speech-to-text"
      },
      "messages": [
        {
          "$ref": "#/channels/speech-to-text/messages/audio_chunk"
        },
        {
          "$ref": "#/channels/speech-to-text/messages/end_of_stream"
        }
      ]
    },
    "receiveSttEvents": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/speech-to-text"
      },
      "messages": [
        {
          "$ref": "#/channels/speech-to-text/messages/transcription"
        },
        {
          "$ref": "#/channels/speech-to-text/messages/finalized"
        },
        {
          "$ref": "#/channels/speech-to-text/messages/transcription_error"
        }
      ]
    },
    "sendListenAudioAndControls": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/listen"
      },
      "title": "Send audio and controls",
      "messages": [
        {
          "$ref": "#/channels/listen/messages/audio"
        },
        {
          "$ref": "#/channels/listen/messages/keepAlive"
        },
        {
          "$ref": "#/channels/listen/messages/configure"
        },
        {
          "$ref": "#/channels/listen/messages/closeStream"
        }
      ]
    },
    "receiveListenEvents": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/listen"
      },
      "title": "Receive transcription events",
      "messages": [
        {
          "$ref": "#/channels/listen/messages/metadataOpen"
        },
        {
          "$ref": "#/channels/listen/messages/speechStarted"
        },
        {
          "$ref": "#/channels/listen/messages/results"
        },
        {
          "$ref": "#/channels/listen/messages/utteranceEnd"
        },
        {
          "$ref": "#/channels/listen/messages/sentiment"
        },
        {
          "$ref": "#/channels/listen/messages/gender"
        },
        {
          "$ref": "#/channels/listen/messages/metadataClose"
        },
        {
          "$ref": "#/channels/listen/messages/error"
        }
      ]
    }
  }
}
