Skip to main content
VARTA

How do I build a voice agent with VARTA?

Building a voice agent with VARTA is three calls: POST /v1/agents/{agent_id}/load opens a session against an agent you built in the studio, POST /v1/sessions/{session_id}/turn sends the caller's transcribed text and gets back the agent's reply, and POST /v1/sessions/{session_id}/end closes the session and finalises its cost. Everything else — speech recognition, speech synthesis, the conversation logic, cost tracking — VARTA does for you between those calls.

Prerequisites

  • A reachable VARTA instance and its base URL. Every example below uses $VARTA_BASE_URL, which should already include the version prefix — for example https://your-varta-host/v1.
  • An agent id from the VARTA studio. An agent bundles a language model, a speech recogniser, a speech synthesiser, a voice and a conversation flow under one id; you build it in the studio, then load it from your code. This page uses ag_412 as a placeholder.
  • A credential for the Authorization header, if the instance you're talking to runs in enforce mode. See Authentication before you point this at anything other than your own machine — a default self-hosted instance does not require one, and that is not something to rely on past your own network.

Everything here runs against your own VARTA instance — there is no shared VARTA Cloud endpoint to call yet. VARTA Cloud will be the same API at a different host; it isn't open yet, and the waitlist is the way in when it is.

1. Load the agent

curl -X POST "$VARTA_BASE_URL/agents/ag_412/load" \
  -H "Authorization: Bearer $VARTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer_name": "Rahul", "context": {"order_id": "SO-4821"}}'

Every field in the body is optional. The response carries the opening line with its audio already synthesised, so your client never has to assemble a greeting itself:

{
  "session_id": "se_9021",
  "agent_id": "ag_412",
  "opening": {
    "text": "Namaste Rahul, main Priya baat kar rahi hoon...",
    "audio_url": "https://.../opening_9021.mp3",
    "clips": [ { "intent_key": "opening_greeting", "text": "Namaste Rahul, main Priya baat kar rahi hoon...", "audio_url": "https://.../opening_9021.mp3" } ]
  },
  "filler_clips": {},
  "behavior_clips": {},
  "runtime": { "agent_name": "Priya", "language": "hi", "steps": 9 },
  "ended": false
}

The field to hold on to is session_id — every following call is scoped to it. runtime tells you which model, speech recogniser and speech synthesiser this session actually resolved to (an agent can pin a provider or fall back to the deployment default; see Agents for the full shape). Play opening.audio_url to the caller and you're already talking.

2. Submit a turn

Once you have the caller's words as text — from your own speech recogniser, or from a browser's built-in transcription — send it in:

curl -X POST "$VARTA_BASE_URL/sessions/se_9021/turn" \
  -H "Authorization: Bearer $VARTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "haan boliye", "stt_confidence": 0.94, "stt_provider": "deepgram"}'
{
  "session_id": "se_9021",
  "text": "Aapka order kal deliver ho jayega.",
  "audio_url": "https://.../turn_4.mp3",
  "cache_hit": true,
  "ended": false,
  "trace": { "layer": "l1_exact_match", "llm_used": false, "latency_ms": { "total": 210 } },
  "cost": { "llm_inr": 0, "tts_inr": 0, "stt_inr": 0, "total_inr": 0 },
  "session_totals": { "total_inr": 0.05, "turns": 4, "cache_hit_percent": 75 }
}

Three fields are worth reading on every turn, not just the reply text: trace.layer names which layer of the engine decided the reply (a keyword match, a rule, the LLM — useful for debugging why the agent said what it said, not a stable enum to branch on), cache_hit tells you whether the audio came free from the pre-synthesis cache or a live text-to-speech call, and cost / session_totals give you the turn's spend and the session's running total. Call this endpoint again for every subsequent thing the caller says — see Sessions for the full response shape, including the flags that show up on filler and confirmation-readback clips.

3. End the session

curl -X POST "$VARTA_BASE_URL/sessions/se_9021/end" \
  -H "Authorization: Bearer $VARTA_API_KEY"
{
  "session_id": "se_9021",
  "ended": true,
  "summary": { "...": "structured end-of-call summary" },
  "cost": { "llm_inr": 0.04, "tts_inr": 0.01, "stt_inr": 0, "total_inr": 0.05 }
}

Costs stop accruing once the session is ended, and summary — step outcomes, tool calls, overall outcome, and whatever the instance's summariser extracted — becomes available. It is a generated summary, not a structured slot map: state.slots on each turn is always empty, as Sessions explains.

Building that summary is best-effort and never fails the request, but the failure is coerced to an empty object, not a null: (result or {}).get("summary") or {} (backend/app/api/v1/sessions.py:97). So summary is never null — check for {} instead, which means the summary build failed or produced nothing.

Next steps

  • Authentication — whether the instance you're calling actually enforces the Authorization header you just sent.
  • Idempotency — how to make a retried request safe, which matters most once you move from sessions to POST /v1/calls and a retry could ring the phone twice.
  • Examples — worked, runnable versions of this flow.
  • Not yet in v1 — what the API does not cover today (authoring agents), and where that's headed.