Gradium S2S API
The S2S API from Gradium — 1 operation(s) for s2s.
The S2S API from Gradium — 1 operation(s) for s2s.
openapi: 3.1.0
info:
title: Gradium metering S2S API
description: "This documentation covers the Gradium API.\n\nThis API exposes our Text-To-Speech and Speech-To-Text models, which offers low-latency, high-quality & natural sounding output and best in class accuracy. \n\nFor issues, questions, or feature requests, please contact us at support@gradium.ai"
version: 0.1.0
servers:
- url: https://api.gradium.ai/api
description: Gradium API
tags:
- name: S2S
paths:
/speech/s2s:
get:
tags:
- S2S
summary: S2S WebSocket Stream
description: "Connect to this endpoint via WebSocket for real-time speech-to-speech: incoming audio is transcribed, optionally translated, and re-synthesized into speech.\n\n**Connection URL:**\n\n```\nwss://api.gradium.ai/api/speech/s2s\n```\n\n**Authentication:**\nInclude your API key in the WebSocket connection header:\n- Header: `x-api-key: your_api_key`\n\n---\n\n## Quick Reference\n\n| Direction | Message Type | Example |\n|-----------|-------------|---------|\n| \U0001F535⬆️ Client→Server | Setup (first) | `{\"type\": \"setup\", \"model_name\": \"default\", \"input_format\": \"pcm\", \"output_format\": \"pcm\", \"voice_id\": \"YTpq7expH9539ERJ\"}` |\n| \U0001F7E2⬇️ Server→Client | Ready | `{\"type\": \"ready\", \"request_id\": \"uuid\", \"sample_rate\": 48000}` |\n| \U0001F535⬆️ Client→Server | Audio | `{\"type\": \"audio\", \"audio\": \"base64...\"}` |\n| \U0001F7E2⬇️ Server→Client | Text (stream) | `{\"type\": \"text\", \"text\": \"Hello world\", \"start_s\": 0.5, \"stop_s\": 1.2}` |\n| \U0001F7E2⬇️ Server→Client | Audio (stream) | `{\"type\": \"audio\", \"audio\": \"base64...\"}` |\n| \U0001F535⬆️ Client→Server | EndOfStream | `{\"type\": \"end_of_stream\"}` |\n| \U0001F7E2⬇️ Server→Client | EndOfStream | `{\"type\": \"end_of_stream\"}` |\n| \U0001F534⬇️ Server→Client | Error | `{\"type\": \"error\", \"message\": \"Error description\", \"code\": 1008}` |\n\n---\n\n## Message Types\n\n### 1. Setup Message (First Message)\n\n**Direction:** Client → Server\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"setup\",\n \"model_name\": \"default\",\n \"input_format\": \"pcm\",\n \"output_format\": \"pcm\",\n \"voice_id\": \"YTpq7expH9539ERJ\"\n}\n```\n\n**Fields:**\n- `type` (string, required): Must be \"setup\"\n- `model_name` (string, optional): The speech-to-speech model to use (default: \"default\")\n- `stt_model_name` (string, optional): The speech-to-text model used to transcribe the input\n- `tts_model_name` (string, optional): The text-to-speech model used to synthesize the output\n- `input_format` (string, optional): Input audio format (default: \"wav\"). One of \"pcm\", \"pcm_8000\", \"pcm_16000\", \"pcm_22050\", \"pcm_24000\", \"pcm_44100\", \"pcm_48000\", \"wav\", \"opus\", \"ulaw_8000\", \"mulaw_8000\", \"alaw_8000\".\n- `output_format` (string, optional): Output audio format (default: \"wav\"). One of \"wav\", \"pcm\", \"opus\", \"ulaw_8000\", \"mulaw_8000\", \"alaw_8000\", \"pcm_8000\", \"pcm_16000\", \"pcm_22050\", \"pcm_24000\", \"pcm_44100\", \"pcm_48000\".\n- `voice_id` (string, optional): Voice ID from the library used for the synthesized output\n- `json_config` (object or string, optional): Advanced options. Set `target_language` to translate the speech (e.g. `{\"target_language\": \"en\"}`); omit it to keep the original language.\n\n**Important:** This must be the very first message sent after connection. The server will close the connection if any other message is sent first.\n\n---\n\n### 2. Ready Message\n\n**Direction:** Server → Client\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"ready\",\n \"request_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"sample_rate\": 48000,\n \"frame_size\": 3840\n}\n```\n\n**Fields:**\n- `type` (string): Will be \"ready\"\n- `request_id` (string): Unique identifier for the session\n- `sample_rate` (integer): Output sample rate in Hz\n- `frame_size` (integer): Output frame size in samples\n\nThis message is sent by the server after receiving the setup message, indicating that the connection is ready to receive audio.\n\n---\n\n### 3. Audio Message\n\n**Direction:** Client → Server\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"audio\",\n \"audio\": \"base64_encoded_audio_data...\"\n}\n```\n\n**Fields:**\n- `type` (string, required): Must be \"audio\"\n- `audio` (string, required): Base64-encoded input audio chunk\n\n**Audio Format Requirements (for PCM input):**\n- **Sample Rate**: 24000 Hz (24kHz)\n- **Format**: PCM (Pulse Code Modulation)\n- **Bit Depth**: 16-bit signed integer (little-endian)\n- **Channels**: Single channel (mono)\n- **Chunk Size**: Recommended 1920 samples per frame (80ms at 24kHz)\n\nSend audio messages to be converted. The server will stream back text and synthesized audio as it processes the input.\n\n---\n\n### 4. Text Response\n\n**Direction:** Server → Client\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"text\",\n \"text\": \"Hello world\",\n \"start_s\": 0.5,\n \"stop_s\": 1.2,\n \"stream_id\": 0\n}\n```\n\n**Fields:**\n- `type` (string): Will be \"text\"\n- `text` (string): The transcribed (and translated, if `target_language` is set) text segment\n- `start_s` (float): Start time of the segment in seconds\n- `stop_s` (float): Stop time of the segment in seconds\n- `stream_id` (integer or null): Stream identifier\n\n---\n\n### 5. Audio Response\n\n**Direction:** Server → Client\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"audio\",\n \"audio\": \"base64_encoded_audio_data...\",\n \"start_s\": 0.0,\n \"stop_s\": 0.08,\n \"stream_id\": 0\n}\n```\n\n**Fields:**\n- `type` (string): Will be \"audio\"\n- `audio` (string): Base64-encoded output audio chunk in the requested format\n- `start_s` (float): Start time of the chunk in seconds\n- `stop_s` (float): Stop time of the chunk in seconds\n- `stream_id` (integer or null): Stream identifier\n\nWhen using `\"pcm\"` output format, the audio is 16-bit signed integer mono. The output sample rate is reported in the `ready` message.\n\n---\n\n### 6. End Of Stream\n\n**Direction:** Client → Server and Server → Client\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"end_of_stream\"\n}\n```\n\nThe client sends this when it has finished sending audio. The server then returns any remaining text and audio, an `end_of_stream` message, and closes the connection.\n\n---\n\n## Error Handling\n\nWhen errors occur, the server sends an error message as JSON before closing the connection:\n\n```json\n{\n \"type\": \"error\",\n \"message\": \"Error description explaining what went wrong\",\n \"code\": 1008\n}\n```\n\n**Common Error Codes:**\n- `1008`: Policy Violation (e.g., invalid API key, missing setup message, invalid audio format)\n- `1011`: Internal Server Error (unexpected server-side error)\n"
parameters:
- name: x-api-key
in: header
required: true
schema:
type: string
description: Your Gradium API key
responses:
'101':
description: WebSocket connection established
x-codeSamples:
- lang: cURL
source: "wscat -c \"wss://api.gradium.ai/api/speech/s2s\" \\\n -H \"x-api-key: your_api_key\"\n# After connection, paste:\n# {\"type\":\"setup\",\"model_name\":\"default\",\"input_format\":\"pcm\",\"output_format\":\"pcm\",\"voice_id\":\"YTpq7expH9539ERJ\",\"json_config\":{\"target_language\":\"en\"}}\n"
- lang: Python
source: "import asyncio\nimport base64\nimport json\n\nimport websockets\n\nIN_CHUNK_BYTES = 1920 * 2 # 80 ms at 24 kHz, 16-bit mono.\n\n\nasync def speech_to_speech(api_key: str, pcm_audio: bytes, voice_id: str) -> bytes:\n setup = {\n \"type\": \"setup\",\n \"model_name\": \"default\",\n \"input_format\": \"pcm\",\n \"output_format\": \"pcm\",\n \"voice_id\": voice_id,\n \"json_config\": {\"target_language\": \"en\"},\n }\n out_audio = []\n\n async with websockets.connect(\n \"wss://api.gradium.ai/api/speech/s2s\",\n additional_headers={\"x-api-key\": api_key},\n ) as ws:\n await ws.send(json.dumps(setup))\n ready = json.loads(await ws.recv())\n assert ready[\"type\"] == \"ready\"\n\n async def producer():\n for off in range(0, len(pcm_audio), IN_CHUNK_BYTES):\n chunk = pcm_audio[off : off + IN_CHUNK_BYTES]\n await ws.send(json.dumps({\n \"type\": \"audio\",\n \"audio\": base64.b64encode(chunk).decode(),\n }))\n await ws.send(json.dumps({\"type\": \"end_of_stream\"}))\n\n async def consumer():\n while True:\n msg = json.loads(await ws.recv())\n if msg[\"type\"] == \"text\":\n print(msg[\"text\"])\n elif msg[\"type\"] == \"audio\":\n out_audio.append(base64.b64decode(msg[\"audio\"]))\n elif msg[\"type\"] == \"end_of_stream\":\n return\n elif msg[\"type\"] == \"error\":\n raise RuntimeError(msg[\"message\"])\n\n await asyncio.gather(producer(), consumer())\n\n return b\"\".join(out_audio)\n\n\nasyncio.run(speech_to_speech(\"your_api_key\", open(\"input.pcm\", \"rb\").read(), \"YTpq7expH9539ERJ\"))\n"
x-tagGroups:
- name: Documentation
tags:
- Documentation
- FAQ
- Release notes
- name: API Reference
tags:
- TTS
- STT
- Voices
- Pronunciations
- Credits