# Quickstart for servers

> Run the example relay against Kheelona Voice in a few minutes, first with typed text, then with your own recording.

Source: https://docs.voice.kheelona.com/get-started/quickstart-server/

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](https://docs.astral.sh/uv/).

## 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

```sh
export KHEELONA_API_KEY=kv_live_…
```

The examples read the key only from this variable. They never print it.

## 3. Install

**Node**

```sh
cd examples/node-server
pnpm install
```

**Python**

```sh
cd examples/python-server
uv sync
```

## 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.

**Node**

```sh
pnpm relay --text "Hello! Tell me a short story."
```

**Python**

```sh
uv run python relay.py --text "Hello! Tell me a short story."
```

## 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.

```text
event session.ready
event state
…
event reply.sentence
  text: Once upon a time, a little dragon found a shiny stone.
…
event reply.done
event state
event session.ended
session ses_… ended client_ended replies 1 audio 8.4s saved reply.wav
```

Open `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](https://docs.voice.kheelona.com/concepts/turn-taking.md).

If something is wrong, the relay prints one line to standard error and exits with 1:

```text
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](https://docs.voice.kheelona.com/guides/errors-and-retries.md).

## 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:

```sh
ffmpeg -i in.wav -ac 1 -ar 16000 -sample_fmt s16 question.wav
```

Then run the relay with it:

**Node**

```sh
pnpm relay --wav question.wav --out reply.wav
```

**Python**

```sh
uv run python relay.py --wav question.wav --out reply.wav
```

You 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

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](https://docs.voice.kheelona.com/reference/generated/api/getsession.md). 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):

```sh title="create-session.sh"
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.

## Next

- [Server-to-server](https://docs.voice.kheelona.com/guides/server-to-server.md): how the connection works, step by step.
- [Sessions](https://docs.voice.kheelona.com/concepts/sessions.md): client secrets, idempotency and ending.
- [Quickstart for browsers](https://docs.voice.kheelona.com/get-started/quickstart-browser.md): the same, from a web page with the browser SDK.
- [Limits](https://docs.voice.kheelona.com/concepts/limits.md): what is capped, and what "busy" means.
