# How do I build a voice agent with VARTA?

Load an agent to open a session, submit the caller's text one turn at a time, and end the session when the conversation is over — three calls, POST /v1/agents/{agent_id}/load, POST /v1/sessions/{session_id}/turn and POST /v1/sessions/{session_id}/end, are all it takes.


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](/docs/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

```bash
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:

```json
{
  "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](/docs/api/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:

```bash
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"}'
```

```json
{
  "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](/docs/api/sessions#submit-a-turn) for the full
response shape, including the flags that show up on filler and
confirmation-readback clips.

## 3. End the session

```bash
curl -X POST "$VARTA_BASE_URL/sessions/se_9021/end" \
  -H "Authorization: Bearer $VARTA_API_KEY"
```

```json
{
  "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](/docs/api/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](/docs/authentication) — whether the instance you're
  calling actually enforces the `Authorization` header you just sent.
- [Idempotency](/docs/idempotency) — how to make a retried request safe,
  which matters most once you move from sessions to
  [`POST /v1/calls`](/docs/api/calls) and a retry could ring the phone twice.
- [Examples](/docs/examples) — worked, runnable versions of this flow.
- [Not yet in v1](/docs/not-yet-in-v1) — what the API does not cover today
  (authoring agents), and where that's headed.
