Quickstart for servers
In a few minutes you will have a voice answering from your own machine. You need a Kheelona API key, and the example
folder for your language: the examples you received from the Kheelona team, with the SDK package. They are proprietary and
shared privately; the commands below run inside that folder, so cd examples/node-server means the node-server folder
of what you received.
- Node: Node 24 or newer and pnpm.
- Python: Python 3.12 or newer and uv.
1. Get an API key
Section titled “1. Get an API key”Sign in to your Kheelona console. From the public launch, the first time anyone signs in to your console, they set up a second sign-in step with an authenticator app on their phone (before the launch it may be optional). Have the phone at hand if asked. Then open API keys and create a key. Copy it when it is shown: it is shown once. It
starts with kv_live_.
The key belongs on your server only. Never put it in a toy, an app or a web page.
2. Set it
Section titled “2. Set it”export KHEELONA_API_KEY=kv_live_…The examples read the key only from this variable. They never print it.
3. Install
Section titled “3. Install”cd examples/node-serverpnpm installcd examples/python-serveruv sync4. Run the relay with text
Section titled “4. Run the relay with text”A typed turn needs no microphone and no recording. The relay creates a session, connects, sends your text, saves the reply audio and ends the session.
pnpm relay --text "Hello! Tell me a short story."uv run python relay.py --text "Hello! Tell me a short story."5. What you should see
Section titled “5. What you should see”The relay prints each event on one line, and the text of what the voice says under it. The middle lines vary with the reply. The last line is always the summary.
event session.readyevent state…event reply.sentence text: Once upon a time, a little dragon found a shiny stone.…event reply.doneevent stateevent session.endedsession ses_… ended client_ended replies 1 audio 8.4s saved reply.wavOpen reply.wav to hear it (24 kHz, 16-bit, mono).
A typed turn gives one reply, as above. A recording can give more: the voice may hear it as more than one turn, and you
may see input.speech_started and interrupted events when the person, or the clip, keeps talking while the voice is
replying. Then there is more than one reply.done, and the count in the last line is higher. That is normal. See
Turn-taking.
If something is wrong, the relay prints one line to standard error and exits with 1:
error unauthenticated request_id req_…Quote that line if you ask us for help: a request ID comes with a refused request, and the session ID with an error during a session. An exit code of 2 means a usage mistake, such as a missing key or no
--text and no --wav. See Errors and retries.
6. Send your own recording
Section titled “6. Send your own recording”The relay sends a WAV file the way a toy would: 20 ms frames, at real time. The file must be 16 kHz, 16-bit, mono. Convert any recording with ffmpeg:
ffmpeg -i in.wav -ac 1 -ar 16000 -sample_fmt s16 question.wavThen run the relay with it:
pnpm relay --wav question.wav --out reply.wavuv run python relay.py --wav question.wav --out reply.wavYou now see transcript.final with what was heard, and the reply as before. The relay keeps sending silence after
your recording ends, as a live microphone does, until the reply is finished. Set the languages the session listens for with
--languages, for example --languages en,hi.
You can give both --text and --wav. The typed turn goes first and the recording follows once its reply is done. A
recording can be heard as more than one turn, so expect more than one reply.done and, at times, interrupted events.
7. Read the session back
Section titled “7. Read the session back”Every session leaves a record: when it ran, how long, how many turns, why it ended. It never holds what was said. Read
the one you just ran with GET /v1/sessions/{id} (the ID is in the relay’s last line). The call, with its fields and
errors, is in the REST reference. Your API key needs the sessions:read scope.
To see the call that creates a session, here is the same thing with curl (it needs uuidgen; the key goes to curl on
standard input so it never shows in a process list):
API_URL="${KHEELONA_API_URL:-https://api.voice.kheelona.com}"VOICE_ID="${KHEELONA_VOICE_ID:-lumi-dragon}"
# One Idempotency-Key per logical session: repeating the request with the same key returns the same session.printf 'Authorization: Bearer %s\n' "$KHEELONA_API_KEY" | curl -sS --fail-with-body \ -H @- \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: $(uuidgen)" \ -X POST "$API_URL/v1/sessions" \ -d "{\"voice_id\": \"$VOICE_ID\", \"languages\": [\"en\", \"hi\"]}"The answer holds a client secret. It works once and for about 60 seconds. Don’t paste it into chat or logs.
- Server-to-server: how the connection works, step by step.
- Sessions: client secrets, idempotency and ending.
- Quickstart for browsers: the same, from a web page with the browser SDK.
- Limits: what is capped, and what “busy” means.