Quickstart.
From signup to first synthesized audio in under 60 seconds.
1. Get an API key
Sign in with Google, then go to API Keys in your dashboard and create a new key. The full key is shown only once at creation, so copy it somewhere safe before closing the dialog.
Generate an API key2. Pick a model
Every request takes a model and text. Each model is steered differently — muga by a global tone, mulberry by a natural-language description:
| Model | Best for |
|---|---|
silk muga 1 muga | Ultra–low-latency streaming. Set the delivery with a global tone — prefix your text with [neutral], [happy], [sad], [excited], [angry], or [whisper]. |
silk mulberry 1.5 mulberry | Expressive instruct-TTS. Steer with a rich natural-language description, or pick one of our preset speaker voices with speaker and tune its f0_up_key pitch (see below). |
mulberry only: set speaker to one of Emma, Mia, Sophia, Ava, speaker_1…speaker_4, Lucas, Noah, Theo or Adam to use one of twelve fixed studio voices, and f0_up_key to shift its pitch by −12…+12 semitones. Omit speaker to use the voice described by description.
3. Synthesize speech
Pass your key as a Bearer token. The response body is a 24 kHz mono WAV (audio/wav). Quickest smoke test:
curl -X POST https://silk-api.rumik.ai/v1/tts \
-H "Authorization: Bearer rk_live_•••••••••" \
-H "Content-Type: application/json" \
-d '{ "model": "muga", "text": "[happy] Hello, world." }' \
--output speech.wavIn Python, for both models:
import requests
API_KEY = "rk_live_•••••••••"
BASE = "https://silk-api.rumik.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# muga — fast voice, tone set via a [tone] prefix on the text
# tones: neutral (default), happy, sad, excited, angry, whisper
r = requests.post(f"{BASE}/v1/tts", headers=HEADERS, json={
"model": "muga",
"text": "[happy] Hello, world.",
})
r.raise_for_status()
open("muga.wav", "wb").write(r.content) # 24 kHz mono WAV
# mulberry — expressive instruct-TTS, steered by a description
# (+ optional preset speaker voice)
r = requests.post(f"{BASE}/v1/tts", headers=HEADERS, json={
"model": "mulberry",
"text": "Welcome to the future of synthetic speech.",
"description": "warm, upbeat narrator",
"speaker": "Emma", # optional preset voice: Emma/Mia/Sophia/Ava, speaker_1..speaker_4, Lucas/Noah/Theo/Adam
"f0_up_key": 0, # optional pitch shift in semitones (-12..12)
})
r.raise_for_status()
open("mulberry.wav", "wb").write(r.content)4. Stream in real time
For real-time playback, mint a one-shot WebSocket session, connect to it, and send a JSON frame with your synthesis parameters. The server streams raw PCM int16 little-endian @ 24 kHz mono as binary frames, then a terminal {"type":"done"} JSON text frame.
Python (using websockets):
import asyncio, json, wave, requests, websockets
API_KEY = "rk_live_•••••••••"
BASE = "https://silk-api.rumik.ai"
async def main():
# 1. Mint a one-shot WS session -> { ws_url, token }
s = requests.post(f"{BASE}/v1/tts/ws-connect",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"model": "mulberry", "text": "Streaming in real time."}).json()
# 2. Connect, then send the synthesis frame
async with websockets.connect(f'{s["ws_url"]}?token={s["token"]}') as ws:
await ws.send(json.dumps({
"text": "Streaming in real time.",
"description": "calm female narrator",
"speaker": "speaker_1", # mulberry only; omit for muga / the described voice
"f0_up_key": 0,
}))
# 3. Collect PCM int16 (24 kHz mono) until the done frame
pcm = bytearray()
async for msg in ws:
if isinstance(msg, bytes):
pcm.extend(msg)
elif json.loads(msg).get("type") == "done":
break
with wave.open("stream.wav", "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(24000)
w.writeframes(pcm)
asyncio.run(main())HTML — play it straight in the browser with the Web Audio API:
<!doctype html>
<html>
<body>
<button id="play">Speak</button>
<script>
const API_KEY = "rk_live_•••••••••"; // use a key with the tts:stream scope
const BASE = "https://silk-api.rumik.ai";
document.getElementById("play").onclick = async () => {
// 1. Mint a one-shot WebSocket session -> { ws_url, token }
const res = await fetch(BASE + "/v1/tts/ws-connect", {
method: "POST",
headers: { "Authorization": "Bearer " + API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ model: "mulberry", text: "Hello from the browser." }),
});
const { ws_url, token } = await res.json();
// 2. Set up 24 kHz mono playback
const ctx = new AudioContext({ sampleRate: 24000 });
let playAt = ctx.currentTime;
// 3. Connect, send the synthesis frame, queue PCM as it arrives
const ws = new WebSocket(ws_url + "?token=" + encodeURIComponent(token));
ws.binaryType = "arraybuffer";
ws.onopen = () => ws.send(JSON.stringify({
text: "Hello from the browser.",
description: "warm, friendly narrator",
speaker: "speaker_1", // mulberry only; omit for muga / the described voice
f0_up_key: 0, // pitch shift in semitones (-12..12)
}));
ws.onmessage = (e) => {
if (e.data instanceof ArrayBuffer) {
const pcm = new Int16Array(e.data);
const buf = ctx.createBuffer(1, pcm.length, 24000);
const ch = buf.getChannelData(0);
for (let i = 0; i < pcm.length; i++) ch[i] = pcm[i] / 32768;
const src = ctx.createBufferSource();
src.buffer = buf;
src.connect(ctx.destination);
playAt = Math.max(playAt, ctx.currentTime);
src.start(playAt);
playAt += buf.duration;
} else if (JSON.parse(e.data).type === "done" || JSON.parse(e.data).error) {
ws.close();
}
};
};
</script>
</body>
</html>Request fields
| Field | Default | Notes |
|---|---|---|
| text | — | Required. Up to 2000 characters. For muga, prefix with a tone tag, e.g. [happy]. |
| model | muga | muga or mulberry. |
| description | — | mulberry only. Natural-language voice description. |
| speaker | — | mulberry only. Preset voice: Emma, Mia, Sophia, Ava, speaker_1..speaker_4, Lucas, Noah, Theo or Adam. Omit to use description. |
| f0_up_key | 0 | mulberry only. Pitch shift in semitones, -12..12. Applied with speaker. |
| temperature | 0.6 | Sampling temperature. |
| top_p | 0.95 | Nucleus sampling. |
| top_k | 50 | Top-k sampling. |
| repetition_penalty | 1.2 | Penalize repeated tokens. |
| max_new_tokens | 2048 | Output length cap. |
Errors and rate limits
All errors return JSON of shape { error, code }. RPM limits are enforced per API key, while concurrent request limits are shared across the account. The response includes a Retry-After header when applicable.
| Status | Meaning |
|---|---|
| 400 | Malformed request body or unknown model |
| 401 | Bearer token missing, invalid, or expired |
| 403 | Key revoked or account disabled |
| 422 | Validation failed. Check the error field |
| 429 | Rate limit hit. See Retry-After |
| 503 | Upstream temporarily unavailable. Retry shortly |