VaaniEval
Reference

HTTP API

Every dashboard endpoint — the three-step upload protocol, session and audio reads, STT evaluation and pricing — with status codes and validation rules.

The dashboard exposes a small HTTP API. The SDKs use the upload endpoints; the console uses the rest.

There is no authentication. The authorization header is accepted and ignored; any non-empty API key works. Every endpoint, including audio download, is open to anyone who can reach the service. Bind it to localhost or put it behind an authenticating proxy.

Upload protocol

Three steps, in order.

POST /v1/sessions

Creates a session from a manifest.

Headers

HeaderRequiredNotes
content-type: application/jsonyes
idempotency-keynoWhen present, must equal session_id or the request is a 400
authorization: Bearer <key>noAccepted, not validated

Response 201

{ "session_id": "9f2c…", "upload_urls": { "events.jsonl": "/v1/uploads/9f2c…/events.jsonl" } }

Re-posting the same manifest is idempotent.

PUT /v1/uploads/{session_id}/{object_name}

Uploads one object. No authorization header — the returned URL is the credential.

ConstraintValue
Allowed object_nameevents.jsonl, call.audio, caller.audio, agent.audio
Max body128 MiB
Success204 No Content
Unknown object name404
Over the cap413

The body is streamed to a .part file and renamed on success, so a failed upload never leaves a partially written object in place.

The 128 MiB cap is a hard limit, not a soft one. Raw stereo PCM at 16 kHz is roughly 3.8 MB per minute, so a call longer than about 35 minutes cannot be uploaded. The binding constraint arrives sooner at higher sample rates: at 48 kHz the limit is around 12 minutes.

POST /v1/sessions/{session_id}/complete

Marks the session complete and verifies every object.

{
  "objects": {
    "events.jsonl": { "byte_size": 48213, "sha256": "3f9a…" },
    "call.audio":   { "byte_size": 5898240, "sha256": "c14b…" }
  }
}
OutcomeStatus
Accepted202
Byte size or digest mismatch400
A declared object was never uploaded400

The session's status becomes:

StatusMeaning
readyObjects verified and operations imported
partialObjects verified but no operations could be read

Verification is why a truncated upload is a 400 rather than a call with quietly missing turns. partial is what surfaces as unverifiable in the dashboard.

Reading sessions

GET /v1/sessions

Lists sessions with status and manifest summary.

GET /v1/sessions/{session_id}

Full session detail: manifest, status, timestamps and imported operations.

Audio

GET /v1/sessions/{session_id}/audio/{track}

ParameterValues
trackcall | caller | agent | mixed
previewwav — wrap raw PCM in a WAV header for browser playback
from_ms, to_msCut a clip

Honours HTTP Range. This is not optional politeness — Safari refuses to play any media response that does not support ranges.

mixed is WAV-preview only. Audio is stored once as raw PCM; the WAV wrapper is generated per request rather than stored as a second copy.

GET /v1/sessions/{session_id}/audio/{track}/peaks

Server-rendered waveform envelope, one peak per pixel column, so a long call does not ship megabytes of samples to the browser.

STT evaluation

GET /v1/sessions/{session_id}/stt-evaluation

Every measured production and challenger value for the call: streaming timing, per-turn transcripts, alignment, estimated WER, risk judgements and the cohort comparison.

POST /v1/sessions/{session_id}/challenger-evaluation

Queues a challenger run.

{ "model": "elevenlabs_scribe_v2" }
OutcomeStatus
Queued202
Unsupported model422

Supported models come from CHALLENGER_MODELS, currently one entry: ElevenLabs Scribe v2. Requires ELEVENLABS_API_KEY.

GET /v1/sessions/{session_id}/challenger-evaluation

Polls job status. Jobs execute off the request thread on a two-worker in-process pool, with state in SQLite.

Jobs that were queued or in_progress when the process stopped are marked failed on startup and are not resumed. A half-finished run is never presented as complete — re-queue it.

Pricing

GET /v1/pricing · PUT /v1/pricing

Read or override the cost model used for switch decisions.

Published costs are list prices with provenance (estimated_list_price vs configured), not invoices. They exist to compare options, not to reconcile a bill.

Pages and health

MethodPathPurpose
GET/Call console
GET/stt-evaluation?session=<id>STT evaluation workspace
GET/healthHealth check

Uploading manually

Useful for replaying an archived package:

SESSION=9f2c1e4a-3b7d-4c88-9a21-0e5f7b2d1c34
BASE=http://localhost:8000

curl -sS -X POST "$BASE/v1/sessions" \
  -H 'content-type: application/json' \
  -H "idempotency-key: $SESSION" \
  --data-binary @manifest.json

for object in events.jsonl call.audio; do
  curl -sS -X PUT "$BASE/v1/uploads/$SESSION/$object" --data-binary "@$object"
done

curl -sS -X POST "$BASE/v1/sessions/$SESSION/complete" \
  -H 'content-type: application/json' \
  -d "{\"objects\":{
        \"events.jsonl\":{\"byte_size\":$(wc -c < events.jsonl),\"sha256\":\"$(shasum -a 256 events.jsonl | cut -d' ' -f1)\"},
        \"call.audio\":{\"byte_size\":$(wc -c < call.audio),\"sha256\":\"$(shasum -a 256 call.audio | cut -d' ' -f1)\"}
      }}"

Next

On this page