Gradium TTS API
Text-to-Speech endpoints for converting text to audio
Text-to-Speech endpoints for converting text to audio
openapi: 3.1.0
info:
title: Gradium metering TTS 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: TTS
description: Text-to-Speech endpoints for converting text to audio
paths:
/speech/tts:
get:
tags:
- TTS
summary: TTS WebSocket Stream
description: "Connect to this endpoint via WebSocket for real-time text-to-speech conversion with low latency audio streaming.\n\n**Connection URL:**\n\n```\nwss://api.gradium.ai/api/speech/tts\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\", \"voice_id\": \"YTpq7expH9539ERJ\", \"model_name\": \"default\", \"output_format\": \"wav\"}` |\n| \U0001F7E2⬇️ Server→Client | Ready | `{\"type\": \"ready\", \"request_id\": \"uuid\"}` |\n| \U0001F535⬆️ Client→Server | Text (stream) | `{\"type\": \"text\", \"text\": \"Hello, world!\"}` |\n| \U0001F7E2⬇️ Server→Client | Audio (stream) | `{\"type\": \"audio\", \"audio\": \"base64...\"}` |\n| \U0001F7E2⬇️ Server→Client | Text (stream) | `{\"type\": \"text\", \"text\": \"Hello\", \"start_s\": 0.2, \"stop_s\": 0.6}` |\n| \U0001F535⬆️ Client→Server | EndOfStream | `{\"type\": \"end_of_stream\"}` |\n| \U0001F7E2⬇️ Server→Client | AEndOfStream | `{\"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 \"voice_id\": \"YTpq7expH9539ERJ\",\n \"output_format\": \"wav\"\n}\n```\n\n**Fields:**\n- `type` (string, required): Must be \"setup\"\n- `model_name` (string, optional): The TTS model to use (default: \"default\")\n- `voice_id` (string, required): Voice ID from the library (e.g., \"YTpq7expH9539ERJ\" for Emma's voice) or custom voice ID\n- `output_format` (string, optional): 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\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}\n```\n\n**Fields:**\n- `type` (string): Will be \"ready\"\n- `request_id` (string): Unique identifier for the session\n\nThis message is sent by the server after receiving the setup message, indicating that the connection is ready to receive text messages.\n\n---\n\n### 3. Text Message (Subsequent Messages)\n\n**Direction:** Client → Server\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"text\",\n \"text\": \"Hello, world!\"\n}\n```\n\n**Fields:**\n- `type` (string, required): Must be \"text\"\n- `text` (string, required): The text to be converted to speech\n\nSend text messages to be converted to speech. You can send multiple text messages in sequence. The server will stream audio back as it's generated.\n\n**Important: split on whitespace, not inside words or before punctuation.** When you send multiple text messages, the server inserts a single whitespace between the contents of consecutive messages. Sending `\"foo\"` followed by `\"bar\"` is therefore equivalent to sending `\"foo bar\"` (a whitespace is added between them), not `\"foobar\"`. Splitting a word across two messages will change its pronunciation. For the same reason, do not split trailing punctuation into its own message: sending `\"foo\"` followed by `\".\"` yields `\"foo .\"` rather than `\"foo.\"`. Keep each message aligned to a whitespace boundary, with any trailing punctuation attached to the preceding word.\n\n---\n\n### 4. 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}\n```\n\n**Fields:**\n- `type` (string): Will be \"audio\"\n- `audio` (string): Base64-encoded audio data in the requested format\n\nWhen using `\"pcm\"` output format, the audio will adhere to the following\nspecifications:\n- **Sample Rate**: 48000 Hz (48kHz)\n- **Format**: PCM (Pulse Code Modulation)\n- **Bit Depth**: 16-bit signed integer\n- **Channels**: Single channel (mono)\n- **Chunk Size**: 3840 samples per chunk (80ms at 48kHz)\n\nWhen using the `\"wav\"` output format, the audio chunks are in WAV format,\nat 48kHz, 16-bit signed integer mono.\n\nWhen using the `\"opus\"` output format, the audio chunks use the Opus codec\nwrapped in an Ogg container.\n\nAlternative output formats include `\"ulaw_8000\"`, `\"alaw_8000\"`, `\"pcm_8000\"`,\n`\"pcm_16000\"`, and `\"pcm_24000\"`.\n\n**Important:** Multiple audio messages will be streamed for each text message. Continue receiving until you detect the end of speech or receive a new message type.\n\n---\n\n### 5. Text Response\n\n**Direction:** Server → Client\n**Format:** JSON Object\n\n```json\n{\n \"type\": \"text\",\n \"text\": \"Hello\",\n \"start_s\": 0.2,\n \"stop_s\": 0.6\n}\n```\n\n**Fields:**\n- `type` (string): Will be \"text\"\n- `text` (string): The portion of text that has been generated into speech\n- `start_s` (float): Start time in seconds of this text segment in the audio\n- `stop_s` (float): Stop time in seconds of this text segment in the audio\n\nThe server sends text messages back to indicate which parts of the input text\nhave been processed into speech as well as the associated timestamps in the\naudio stream.\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\nThis message is sent by the client when it has submitted all the text that it\nwants to be considered. The server will then send back all the remaining audio\nuntil all the text has been processed, then an `EndOfStream` message, and then\ncloses the websocket 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**Error Message Format:**\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)\n- `1011`: Internal Server Error (unexpected server-side error)\n\n---\n\n## Best Practices\n\n1. **Always send setup first**: The server expects a setup message immediately after connection\n2. **Handle audio streaming**: Audio responses are streamed in chunks - buffer and process appropriately\n3. **Implement reconnection logic**: Network issues happen - build in automatic reconnection with exponential backoff\n4. **Monitor connection health**: Implement ping/pong or periodic checks to detect stale connections\n5. **Graceful error handling**: Parse error messages and handle different error codes appropriately\n6. **Reuse connections**: For multiple utterances, keep the connection alive and send multiple text messages\n7. **Close cleanly**: Always close WebSocket connections properly when done\n\n---\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/tts\" \\\n -H \"x-api-key: your_api_key\"\n# After connection, paste:\n# {\"type\":\"setup\",\"voice_id\":\"YTpq7expH9539ERJ\",\"model_name\":\"default\",\"output_format\":\"wav\"}\n# {\"type\":\"text\",\"text\":\"Hello, world!\"}\n# {\"type\":\"end_of_stream\"}\n"
- lang: Python
source: "import asyncio\nimport base64\nimport json\n\nimport websockets\n\n\nasync def synthesise(api_key: str, voice_id: str, text: str) -> bytes:\n setup = {\n \"type\": \"setup\",\n \"voice_id\": voice_id,\n \"model_name\": \"default\",\n \"output_format\": \"wav\",\n }\n audio_chunks = []\n\n async with websockets.connect(\n \"wss://api.gradium.ai/api/speech/tts\",\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 await ws.send(json.dumps({\"type\": \"text\", \"text\": text}))\n await ws.send(json.dumps({\"type\": \"end_of_stream\"}))\n\n while True:\n msg = json.loads(await ws.recv())\n if msg[\"type\"] == \"audio\":\n audio_chunks.append(base64.b64decode(msg[\"audio\"]))\n elif msg[\"type\"] == \"end_of_stream\":\n break\n elif msg[\"type\"] == \"error\":\n raise RuntimeError(msg[\"message\"])\n\n return b\"\".join(audio_chunks)\n\n\naudio = asyncio.run(synthesise(\"your_api_key\", \"YTpq7expH9539ERJ\", \"Hello, world!\"))\nwith open(\"output.wav\", \"wb\") as f:\n f.write(audio)\n"
/post/speech/tts:
post:
tags:
- TTS
summary: TTS POST Endpoint
description: "Use this HTTP POST endpoint for simple, text-to-speech conversion. The audio\ndata is sent back in a streaming way.\n\n**Endpoint URL:**\n\n```\nhttps://api.gradium.ai/api/post/speech/tts\n```\n\n**Authentication:**\nInclude your API key in the request header:\n- Header: `x-api-key: your_api_key`\n\n---\n\n## Quick Example\n\n```bash\ncurl -L -X POST https://api.gradium.ai/api/post/speech/tts \\\n -H \"x-api-key: your_api_key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"text\": \"Hello, this is a test of the text to speech system.\", \"voice_id\": \"YTpq7expH9539ERJ\", \"output_format\": \"wav\", \"only_audio\": true}' \\\n > output.wav\n```\n\n---\n\n## Request Format\n\n**Method:** POST\n**Content-Type:** application/json\n\n**Request Body:**\n```json\n{\n \"text\": \"Hello, this is a test of the text to speech system.\",\n \"voice_id\": \"YTpq7expH9539ERJ\",\n \"output_format\": \"wav\",\n \"json_config\": \"{}\",\n \"only_audio\": true\n}\n```\n\n**Fields:**\n- `text` (string, required): The text to be converted to speech\n- `voice_id` (string, required): Voice ID from the library (e.g.,\n \"YTpq7expH9539ERJ\") or a custom voice ID\n- `output_format` (string, required): Audio format - \"wav\", \"pcm\", or \"opus\"\n (ogg wrapped opus data).\n- `json_config` (string, optional): Additional configuration in JSON string format (e.g., `{\"padding_bonus\": -1.2}`)\n- `model_name` (string, optional): The TTS model to use (default: \"default\")\n- `only_audio` (boolean, optional): When `true`, returns only the raw audio\n bytes. When `false` or omitted, returns a stream of JSON messages containing\n the audio and metadata. The format is the same as with the websocket endpoint.\n\n---\n\n## Response Format\n\n### When `only_audio` is `true`\n\nThe response body contains the raw audio bytes in the requested format. Save directly to a file:\n\n```bash\ncurl ... > output.wav\n```\n\n**Content-Type:** Depends on the output format:\n- `audio/wav` for WAV format\n- `audio/ogg` for Ogg wrapped Opus format\n- `audio/pcm` for PCM format\n\n### When `only_audio` is `false` or omitted\n\nThe response is a stream of JSON messages using the same format as the\nWebSocket endpoint. Read the body line-by-line until it closes — the\nbody closing signals that synthesis is complete.\n\n## Error Handling\n\nIf the request fails before the response stream has started, the server\nresponds with `HTTP 500` and a plain-text body. Two body shapes occur:\n\n- **Upstream errors** (with a numeric code) such as authentication\n failures or worker-level rejections:\n\n ```\n error from server <code>: <reason>\n ```\n\n For example, a revoked or expired API key returns\n `error from server 1008: API key is revoked or expired`.\n\n- **Proxy-level rejections** (e.g. unsupported `Content-Type`, malformed\n request body) come back as raw error strings without the `error from\n server` prefix.\n\nIn both cases the body is plain text (not JSON). Errors that occur\nafter the response stream has started (when `only_audio` is `false`)\nare surfaced as `{\"type\": \"error\", ...}` JSON messages within the\nstream rather than as a different HTTP status.\n\n---\n\n## When to Use POST vs WebSocket\n\nThe POST endpoint is ideal for simple, text-to-speech generations.\nThe main difference with the WebSocket endpoint is that the input is not\nhandled in a streaming way; the entire text is sent in one request. The audio is\nstill streamed back to the client, allowing for efficient handling of large\naudio outputs and lower latency.\n\nSo if your use case involves sending complete text blocks and receiving audio\nresponses, the POST endpoint is a straightforward choice. For more interactive\nor real-time applications where text input is streamed, the WebSocket endpoint\nis more suitable.\n"
parameters:
- name: x-api-key
in: header
required: true
schema:
type: string
description: Your Gradium API key
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- text
- voice_id
- output_format
properties:
text:
type: string
description: The text to convert to speech
voice_id:
type: string
description: Voice ID from the library or custom voice ID
output_format:
type: string
enum:
- wav
- pcm
- opus
- ulaw_8000
- mulaw_8000
- alaw_8000
- pcm_8000
- pcm_16000
- pcm_22050
- pcm_24000
- pcm_44100
- pcm_48000
description: Audio output format
only_audio:
type: boolean
description: When true, returns raw audio bytes instead of JSON
responses:
'200':
description: Audio data returned successfully
'500':
description: 'Pre-stream error. Body is plain text. Upstream errors (authentication, worker rejections) are formatted as `error from server <code>: <reason>`; proxy-level rejections (e.g. malformed request body) come back as raw error strings.'
x-codeSamples:
- lang: cURL
source: "curl -L -X POST https://api.gradium.ai/api/post/speech/tts \\\n -H \"x-api-key: your_api_key\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"text\": \"Hello, world!\", \"voice_id\": \"YTpq7expH9539ERJ\", \"output_format\": \"wav\", \"only_audio\": true}' \\\n > output.wav\n"
- lang: Python
source: "import requests\n\nresp = requests.post(\n \"https://api.gradium.ai/api/post/speech/tts\",\n json={\n \"text\": \"Hello, world!\",\n \"voice_id\": \"YTpq7expH9539ERJ\",\n \"output_format\": \"wav\",\n \"only_audio\": True,\n },\n headers={\"x-api-key\": \"your_api_key\"},\n)\nresp.raise_for_status()\nwith open(\"output.wav\", \"wb\") as f:\n f.write(resp.content)\n"
x-tagGroups:
- name: Documentation
tags:
- Documentation
- FAQ
- Release notes
- name: API Reference
tags:
- TTS
- STT
- Voices
- Pronunciations
- Credits