# Is there runnable example code for the VARTA API?

Four small, runnable examples — a text conversation, an idempotent outbound call, telephony provisioning, and a browser voice widget — each downloadable as a zip built fresh from this repository at every deploy, so the download can never go stale relative to the code you're reading here.


Yes. Four runnable examples live in this repository's `examples/` directory, one per major shape
of `/v1` usage: a plain-text conversation, a phone call that actually dials, telephony
provisioning, and a browser voice widget. Each is a complete, working project — `fetch` and the
Node or browser standard library only, no `@varta/*` package to install — that you can download as
a zip and run against your own VARTA instance in well under sixty seconds. The zips below are built
from this same source at every deploy of this site, not hand-assembled once and left to drift, so
what you download always matches what this page shows.

## Prerequisites

Every example below needs the same three things:

- A reachable VARTA instance and its base URL, including the `/v1` prefix — e.g.
  `https://your-varta-host/v1`. Set it as `VARTA_BASE_URL` in each example's `.env`.
- An agent id from the VARTA studio (`VARTA_AGENT_ID`). See [Quickstart](/docs/quickstart) if you
  haven't built one yet.
- Node 20 or later.

Each example also has its own `.env.example` to copy to `.env`, and its own `README.md` with a
verified transcript of what happens when you run it — including what it prints when
`VARTA_BASE_URL` is unset or unreachable, with no backend required to see that part.

## 01 — Text conversation CLI

**[Download `varta-example-01-text-conversation-cli.zip`](/downloads/varta-example-01-text-conversation-cli.zip)**

The fastest way to see the `/v1` API work end to end: load an agent, type replies at a prompt,
end the session. No audio, no telephony — proves the three-call shape from
[Quickstart](/docs/quickstart) (`load` → `turn` → `end`) actually works against a real agent, with
`trace.layer` (which engine layer decided each reply) and `cost.total_inr` (that turn's spend)
printed next to every line the agent says.

```ts
for await (const rawLine of rl) {
  const text = rawLine.trim()
  if (text === '/quit') break
  if (!text) continue

  const turn = (await api(`/sessions/${sessionId}/turn`, {
    method: 'POST',
    body: JSON.stringify({ text }),
  })) as TurnResponse

  // trace.layer names which decision layer of the engine produced the reply (a keyword match,
  // a rule, the LLM) -- see /docs/api/sessions. cost is this turn's spend.
  console.log(`agent [${turn.trace.layer}, ${fmtCost(turn.cost)}]: ${turn.text}`)

  if (turn.ended) {
    console.log('\n(the agent ended the session on this turn)')
    break
  }
}
```

## 02 — Outbound call

**[Download `varta-example-02-outbound-call.zip`](/downloads/varta-example-02-outbound-call.zip)**

Places a real phone call (`POST /v1/calls` with `"dial": true`) and polls it to completion. What
it actually proves is [idempotency](/docs/idempotency): a `--ref` you pass on the command line
becomes a stable `Idempotency-Key`, so running the script twice with the same `--ref` replays the
first call's response (`Idempotency-Replayed: true`) instead of dialling the same person twice —
and the same key with a different request body comes back `409 idempotency_key_reuse` rather than
silently merging or redialling. Read this one before you write any retry logic near
`POST /v1/calls`.

```ts
const idempotencyKey = `outbound-call:${ref}`

console.log(`Placing call to ${TO} (Idempotency-Key: ${idempotencyKey})`)

// "dial": true is set explicitly here -- POST /v1/calls is prepare-only by default (it loads
// the agent, pre-fetches context and pre-synthesises the opening line, but never rings
// anyone). Only "dial": true, against a deployment with a configured trunk, places the real
// SIP dial. This example exists to show that path, so it always sets it.
const body: Record<string, unknown> = { agent_id: AGENT_ID, to: TO, dial: true }
if (FROM) body.from = FROM
if (TRUNK_ID) body.trunk_id = TRUNK_ID

const placed = (await api('/calls', {
  method: 'POST',
  headers: { 'Idempotency-Key': idempotencyKey },
  body: JSON.stringify(body),
})) as CallResponse
```

## 03 — Telephony provisioning

**[Download `varta-example-03-provision-telephony.zip`](/downloads/varta-example-03-provision-telephony.zip)**

Registers a SIP trunk, registers a DID number with VARTA, and binds that number to an agent for
inbound — the dial plan a deployment needs before `POST /v1/calls` can place a real call, or
before it can answer one. It also proves the distinction that trips up first-time integrators:
`POST /v1/numbers` **registers** a number you already hold with a carrier, it does not purchase or
provision anything — buy the DID from your carrier first.

```ts
const trunk = (await api('/trunks', {
  method: 'POST',
  headers: { 'Idempotency-Key': uuid() },
  body: JSON.stringify(trunkBody),
})) as Trunk
console.log(`  trunk id=${trunk.id} has_credentials=${trunk.has_credentials}`)

const numberRecord = (await api('/numbers', {
  method: 'POST',
  headers: { 'Idempotency-Key': uuid() },
  body: JSON.stringify({ number: NUMBER, trunk_id: trunk.id }),
})) as NumberRecord
console.log(`  number id=${numberRecord.id}`)

const bound = (await api(`/numbers/${encodeURIComponent(NUMBER!)}/bind`, {
  method: 'POST',
  headers: { 'Idempotency-Key': uuid() },
  body: JSON.stringify({ agent_id: AGENT_ID }),
})) as NumberRecord
console.log(`  ${bound.number} now bound to agent_id=${bound.agent_id}`)
```

## 04 — Browser voice widget

**[Download `varta-example-04-browser-voice-widget.zip`](/downloads/varta-example-04-browser-voice-widget.zip)**

A single web page with a mic button: press it, speak, hear the agent reply. It proves two things
at once. First, that `POST /v1/sessions/{id}/turn` takes transcribed text, not audio — VARTA does
not do speech recognition on this path, so this widget uses the browser's own Web Speech API to
produce a transcript itself, and is careful about a real gotcha in doing so: Chrome commonly
reports `confidence: 0` on a final result, which is not the same signal as "no confidence
available" to the engine's low-confidence gate, so a bare `0` is never forwarded as
`stt_confidence`. Second, that a browser client has to keep its API key server-side: `server.mjs`
is a small allowlisted proxy that attaches `Authorization` to exactly the three routes this widget
calls — the browser itself never holds the key.

```js
const sttConfidence = typeof confidence === 'number' && Number.isFinite(confidence) && confidence > 0
  ? confidence
  : undefined
void submitTurn(text, sttConfidence)
```

## Download all four

**[Download `varta-examples.zip`](/downloads/varta-examples.zip)** — all four examples in one
archive, each nested under its own directory inside a single top-level `varta-examples/` folder
(`varta-examples/01-text-conversation-cli/`, `varta-examples/02-outbound-call/`, and so on), so
extracting it doesn't scatter four projects' worth of files into wherever you extracted it.
