Home / Product / Stack capture

Connect your voice stack once. The key stays in a 0600 file on your disk.

hotato connect captures a vendor’s credentials once, runs a live auth check against that vendor’s own API, and stores them in ~/.hotato/connections.json at file mode 0600. After that, pull and sweep need no key on the command line.

bash
$ hotato connect vapi --api-key YOUR_API_KEY

# or read it from the environment instead of a flag
$ VAPI_API_KEY=YOUR_API_KEY hotato connect vapi

$ hotato connect twilio --account-sid AC... --auth-token AUTH_TOKEN
The credentials go to the vendor’s own API and nowhere else. A failed auth check exits 2 and stores nothing; --no-verify skips the check and just stores.

Two shapes of stack

Either the vendor hosts the recording, or you capture it yourself.

Which one you are on decides whether hotato ever touches the network.

vendor-hosted recording

Connect, then pull by call id.

Eight stacks are connectable: Vapi, Twilio, Retell, Bland, ElevenLabs, Synthflow, Millis and Cartesia. Some need one extra field — Twilio an Account SID and Auth Token, Synthflow a model id, Cartesia an agent id, Millis a regional base URL — and connect asks for exactly that field.

capture in your infrastructure

Scaffold the recorder, then hand over the file.

LiveKit and Pipecat keep the audio in your own systems. hotato setup --stack livekit prints the copy-paste scaffold for turning on dual-channel capture so caller and agent stay on separate channels, plus the command that scores the result.

Scoring is offline either way. On a vendor-hosted stack the only network call is the direct recording download; on a self-captured stack there is none.

The full adapter matrix, including what each vendor’s API leaves out →

bash
$ hotato capture --stack vapi --call-id CALL_ID
$ hotato capture --stack twilio --recording-sid RE...
$ hotato capture --stack livekit --caller a.wav --agent b.wav
$ hotato capture --stack pipecat --stereo captured.wav
$ hotato capture --stack vapi --demo
--demo proves the capture-to-score loop on a bundled two-channel reference, offline, with no API key and no dependencies.
  • exit 0 · captured and scored
  • exit 1 · a scorable event failed
  • exit 2 · missing credentials or unusable input

A mono recording needs a flag, because it costs you a measurement.

Bland, ElevenLabs, Synthflow, Millis and Cartesia return a single combined track. Passing one requires --allow-mono, and the tool says what that degrades.

Separated talk-over cannot be attributed on mono: with both voices on one track there is no evidence for who was speaking during an overlap. Silence timing still measures, with a confidence per finding. A mono recording is not candidate-eligible for separated scoring, and it cannot be pinned into a failure contract at all.

The opt-in --diarize front-end reports whether a mono file is confidently separable into caller and agent as a high, low or refuse tier — so you know before scoring whether a diarized-mono verdict would be confident or only indicative. The local pyannote backend is the default. The hosted backend uploads audio off the machine and therefore requires --egress-opt-in on every call.

Every network action is one you named.

Credentials resolve in a fixed order — the flag you passed, then the stored connection, then the stack’s environment variable — so there is always one place to look when a pull fails.

What leaves the machine, per command
CommandNetwork
connectOne live auth check against the vendor’s API, listing one recent call. Suppressed by --no-verify.
capture · pullThe direct recording download from the vendor. Scoring runs offline afterwards.
setupNone. It prints a scaffold.
autopsy · scan · trustNone. No audio leaves the machine.
contract verify --notifyAn opt-in webhook POST of pass/fail counts and failing contract ids with their measured timing. No audio, no credentials, no transcript, no file paths. Off by default, and a down webhook leaves the exit code alone.

The full security posture →