Gradium TTS API

Text-to-Speech endpoints for converting text to audio

OpenAPI Specification

gradium-tts-api-openapi.yml Raw ↑
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