Self-hosting
The default voxa install talks to Voxa's hosted relay and meters voice through Voxa's cloud, so you never have to touch an API key. Self-hosting is the other option: you run the server from source, bring your own Gemini key, and your voice audio goes straight from your laptop to Google, never through Voxa's infrastructure.
Three reasons to do this:
- It's free forever. You pay Google directly for Gemini Live usage (or nothing, inside the free tier), instead of paying Voxa's metered rate.
- You use your own Gemini key. No account with Voxa is required at all.
- Voice never touches Voxa's cloud. In this mode there's no relay and no
/liveproxy in the loop. The only parties that see your audio are your laptop and Google.
What changes vs. hosted
In hosted mode, the laptop's voice operator is RemoteOperator: it forwards your mic audio to Voxa's metered wss://api.voxa.space/live proxy, which runs Gemini with Voxa's key and RPCs tool calls back to your laptop.
Self-hosted, the laptop instead runs GeminiOperator directly against Google's Gemini Live API, using your own GEMINI_API_KEY. Which operator you get is decided entirely by whether VOXA_LIVE_PROXY is set: unset it (or don't set it in the first place) and the laptop talks to Google directly.
Everything downstream of the operator (the orchestrator, the Claude controllers, the session registry) works exactly the same either way.
From-source setup
Self-hosting means running the server from a clone of the repository rather than the packaged voxa command:
git clone https://github.com/voxa-code/voxa.git
cd voxa
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
Once installed, you can start the server directly with:
.venv/bin/python -m server.cli
That runs the exact same launcher as the packaged voxa command (same startup sequence, same environment variables), just from your local checkout instead of a PyPI install. See the CLI reference for what it does step by step.
The fully self-hosted recipe
-
Get a Gemini key. Go to Google AI Studio and create an API key for Gemini Live.
-
Copy the env template.
cp .env.example .env -
Set the two required values in
.env:GEMINI_API_KEY=your-key-hereVOXA_AUTH_TOKEN=any-random-stringGEMINI_API_KEYis what makesGeminiOperatortalk to Google directly instead of needing a proxy.VOXA_AUTH_TOKENis the shared secret your phone presents on the WebSocket; without it the launcher would generate one for you, but for self-hosting it's worth setting explicitly so you know what it is. -
Run the server with Tailscale:
bash scripts/serve.shThis starts uvicorn bound to
127.0.0.1, then runstailscale serve --bg --https=443to expose it over your tailnet, and prints a QR code with the pairing URL.
You'll need Tailscale installed and logged into the same tailnet on both your laptop and your phone. The reason HTTPS matters here isn't optional politeness: browsers only allow microphone access (getUserMedia) from a secure context, so the phone's web client (and the native app's web view) simply won't get mic permission over plain HTTP. Tailscale's serve command terminates HTTPS on your tailnet's domain for you, which is why it's the recommended path.
Alternative transports
If you don't have Tailscale set up, running voxa (or python -m server.cli) directly, rather than scripts/serve.sh, falls back to a Cloudflare quick tunnel by default whenever VOXA_RELAY_URL isn't set and Tailscale isn't reachable. That requires cloudflared on your PATH:
brew install cloudflared
The launcher spawns cloudflared tunnel --url http://127.0.0.1:<port>, waits for a https://<random>.trycloudflare.com URL to appear, and confirms it's actually reachable before showing you the QR code. It retries up to three times before giving up.
Either transport (Tailscale or the Cloudflare tunnel) gives your phone an HTTPS URL that reaches your laptop, which is the only thing that actually matters for self-hosting. scripts/serve.sh just wires up the Tailscale path explicitly and skips the tunnel fallback logic.
Agent-initiated calls (optional)
Self-hosted, the app still works perfectly well as a voice client: you open the pairing URL, talk, and get answers. What you don't get for free is agent-initiated calls, the phone actually ringing like a real call when Claude finishes in the background.
That requires your own Apple Push Notification service (APNs) key, since ringing uses CallKit-style VoIP push under the hood. To enable it, set all four of:
APNS_KEY_PATH=/path/to/your/key.p8
APNS_KEY_ID=...
APNS_TEAM_ID=...
APNS_BUNDLE_ID=...
Getting these requires an Apple developer account (the key comes from the Apple Developer portal). If any of the four are missing, push is simply disabled and Voxa falls back to being a voice-only client, no rings, no errors. There's nothing broken about leaving this unset; it's an optional upgrade.
Advanced: pointing at your own cloud deployment
If you want the convenience of the relay/proxy architecture (dial-out pairing, no inbound port) without using Voxa's hosted cloud, you can run your own copy of the server_cloud service and point your laptop at it instead:
VOXA_RELAY_URL=https://your-own-cloud.example.com
VOXA_LIVE_PROXY=wss://your-own-cloud.example.com/live
This is genuinely advanced: it means running and operating the same cloud service that backs api.voxa.space yourself, including its own Gemini key for the metered proxy path. Most self-hosters don't need this; the Tailscale or tunnel path above is enough for a single laptop and phone.
Running tests
The repository's test suite runs with pytest:
python -m pytest -q