Is there runnable example code for the VARTA API?
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
/v1prefix — e.g.https://your-varta-host/v1. Set it asVARTA_BASE_URLin each example's.env. - An agent id from the VARTA studio (
VARTA_AGENT_ID). See 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
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 (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.
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
Places a real phone call (POST /v1/calls with "dial": true) and polls it to completion. What
it actually proves is 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.
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 CallResponse03 — Telephony provisioning
Download 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.
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
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.
const sttConfidence = typeof confidence === 'number' && Number.isFinite(confidence) && confidence > 0
? confidence
: undefined
void submitTurn(text, sttConfidence)Download all four
Download 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.