> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zelto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent versions

> Declare the deployed version behind every call so experiments compare the intended agent releases.

An **agent version** identifies a deployed release of an agent. A release can
change its prompt, model, voice, transcription settings, tools, or workflow.
Two versions can have identical prompts and different pipelines.

[Experiments](/docs/experiments) compare calls assigned to exactly two deployed
versions of the **same agent**. Accurate version assignment is what makes the
comparison meaningful: Zelto uses the version reported for the call, not a guess
based on its transcript or the agent's current configuration.

## Three identifiers with different jobs

| Field | Identifies | When it changes |
| - | - | - |
| `agent.externalId` | The agent across releases, within your organization and provider. You can instead send an existing Zelto `agentId`. | When it is a different agent, not a new release or session. |
| Top-level `version` | The deployed release that actually handled the call. | When you deploy a prompt or pipeline change you want to distinguish. |
| `call.externalId` | One call or session. | For each new call. Reuse it for retries of that call. |

For example, `support-agent` can receive calls from both `support-2026-09-a`
and `support-2026-09-b`. Keep `agent.externalId` the same for both. Giving each
release a different agent ID creates separate agents that cannot be selected
as the two versions of one experiment.

## Declare versions in LiveKit or a custom integration

Send the top-level **`version`** field on every upload to
`POST https://ingest.zelto.ai/webhooks/calls`. The wire field is `version`,
not `prompt_version`, `agent.version`, or `metadata.version`.

The API accepts a string of **1–255 characters after trimming surrounding
whitespace**. Labels are case-sensitive: `release-a` and `Release-A` are
different. A release tag, immutable build ID, or hash of your complete versioned
configuration works. A prompt-only hash cannot distinguish pipeline-only changes.

```json theme={null}
{
  "agent": {
    "externalId": "support-agent",
    "name": "Customer Support"
  },
  "version": "support-2026-09-a",
  "call": {
    "externalId": "session-001",
    "startedAt": "2026-09-09T10:00:00Z"
  },
  "endUserId": "customer-123",
  "systemPrompt": "You are a support agent. Confirm the next step before ending the call.",
  "transcript": {
    "turns": [
      {
        "role": "user",
        "content": "When will my order arrive?"
      },
      {
        "role": "assistant",
        "content": "I will check the delivery estimate for you."
      }
    ]
  },
  "versionConfig": {
    "llm": {
      "provider": "your-llm-provider",
      "model": "your-model-id",
      "temperature": 0.2
    },
    "stt": {
      "provider": "your-stt-provider",
      "model": "your-stt-model"
    },
    "tts": {
      "provider": "your-tts-provider",
      "model": "your-tts-model",
      "voice": "your-voice-id"
    },
    "tools": [
      {
        "name": "lookup_order",
        "timeoutMs": 3000
      }
    ],
    "workflow": {
      "entry": "greeting",
      "steps": [
        "greeting",
        "lookup",
        "resolution"
      ]
    },
    "runtime": {
      "releaseCommit": "abc123",
      "region": "your-region"
    }
  }
}
```

Use `Authorization: Bearer $ZELTO_API_KEY`, `Content-Type: application/json`,
and `X-Zelto-Provider: livekit` for a LiveKit worker, or `zelto` for custom
uploads. See the [LiveKit example](/docs/integrations/livekit#forward-a-session)
and [call-upload contract](/docs/integrations/api-call-upload).

You can register a version before sending calls (see below). Otherwise, the first
**processed call** with a new label creates the declared version;
later calls with the same agent and label reuse it. A separate version-creation
request is optional. Zelto also assigns a display number such as **v1** or
**v2** in first-seen order. That number is separate from your label; send your
release label, not a display number inferred from the dashboard.

<Note>
  `version` is optional for general call ingestion. For integrations intended to
  run experiments, send an explicit version on every call so attribution follows
  the release that handled it.
</Note>

## Keep a label tied to one release

* Change the label for prompt, model, voice, transcription, tool, or workflow
  changes that should form a distinct version.
* Keep it stable across calls on that release. Do not use a room ID, customer ID,
  per-call timestamp, or random UUID as the version label.
* Capture the label and configuration from the session that handled the call.
  A deployment can happen while a call is running; do not label a delayed upload
  using whichever release is current at upload time.
* On retry, resend the original call ID, version, and snapshots. Do not relabel
  old calls to make them join a different experiment arm.
* For a rollback to an unchanged release, reuse that release's label. If you
  need to distinguish two deployments of identical code, use separate release
  labels consistently.

Reusing a label after changing its configuration mixes those releases' calls
into one version. A label is supplied by your integration; Zelto does not verify
that every call carrying it used an identical build.

## Register a version in Zelto

Organization owners and admins can open **Agents → your agent → Versions →
Register version**. Enter the release label, optional system prompt, and the
LLM, STT, and TTS providers/models. TTS also has a voice ID field.

Expand **Additional configuration (JSON)** to record tools, workflows,
knowledge bases, runtime settings, or any custom context. Extra settings such
as `llm.temperature` can go here; the provider/model fields above take precedence
when both specify the same key. Registration records context; it does not deploy
or run this configuration. Draft prompt candidates remain separate.

## Register a version through the API

Use an organization API key owned by an owner or admin. The agent must already
exist in that organization. On the platform API host, call
`POST /v1/agents/{id}/versions`, using the Zelto agent UUID:

```bash theme={null}
curl -X POST "https://api.zelto.ai/v1/agents/$AGENT_ID/versions" \
  -H "Authorization: Bearer $ZELTO_API_KEY" \
  -H "Content-Type: application/json" \
  -d @version.json
```

```json title="version.json" theme={null}
{
  "version": "support-2026-09-a",
  "systemPrompt": "You are a support agent. Confirm the next step before ending the call.",
  "config": {
    "llm": { "provider": "your-llm-provider", "model": "your-model-id", "temperature": 0.2 },
    "stt": { "provider": "your-stt-provider", "model": "your-stt-model" },
    "tts": { "provider": "your-tts-provider", "model": "your-tts-model", "voice": "your-voice-id" },
    "tools": [{ "name": "lookup_order", "timeoutMs": 3000 }],
    "workflow": { "entry": "greeting", "steps": ["greeting", "lookup", "resolution"] },
    "runtime": { "releaseCommit": "abc123", "region": "your-region" }
  }
}
```

A new registration returns HTTP `201` with `{ "id": "<version UUID>",
"created": true }`. Retrying with the same label and configuration returns
HTTP `200` with `created: false`. A supplied prompt or configuration that differs
from an existing version returns **`409 Conflict`**: create a new release label
instead of rewriting history. Omitted fields on a retry leave existing values
alone. Internal prefixes `draft:` and `generated:` are reserved.

The call-upload endpoint remains on `https://ingest.zelto.ai/webhooks/calls`.
Registering a release does not make it the fallback for unversioned calls. It becomes observed when a processed call explicitly reports its label.

Registration uses `config`; call upload uses **`versionConfig`** alongside
`version`. Both write the same version configuration. After registration,
report that label on each call. Do not substitute the returned UUID or Zelto's
display number for your release label.

## Version identity and configuration are separate

`version` assigns calls to a release. The configuration is context supplied by
your organization: it can describe everything that release uses, including
provider/model choices, voice, tools, retrieval, workflow, deployment settings,
and custom components. All supplied JSON fields are preserved and available in
the version detail and comparison, including experiment setup.

Supply a JSON **object**, up to **64 KiB when serialized as UTF-8**. The keys
`llm`, `stt`, and `tts` are conventions matching the form, not a closed list.
Other keys and nested objects/arrays are supported. Include configuration,
not API keys or other credentials. This is descriptive context; Zelto does not
execute workflows, configure providers, or discover missing settings for you.

Send the actual prompt as `systemPrompt`, or in a leading `system` transcript
turn for call uploads. A version label alone does not supply a prompt.

A first call can create the version with `versionConfig`. Existing snapshots
are not replaced by later call uploads; a missing snapshot can be filled. Send
complete configuration from the start, and register a new label when it changes.
A configuration already captured from legacy call metadata is also an existing
snapshot: later uploads do not merge a new `versionConfig` into it.

For older versions without explicitly supplied configuration, Zelto shows only
the captured model, transcription, and voice settings from call metadata.
Missing snapshots are unavailable, not evidence of identical pipelines. Full
workflow and tool context is available when your integration or the form supplies
it; it is not collected automatically.

## Provider-managed and automatic versions

| Integration | How to supply version identity |
| - | - |
| LiveKit or custom call upload | Send top-level `version` on each call. Your worker owns the label and snapshots. |
| Vapi | Published assistant-version labels reported on call webhooks identify releases. Model, voice, and transcription settings can be captured from the webhook. Calls without a reported label use automatic fallback. |
| Retell | Zelto uses the call's reported `agent_version`, including `0`. This captures identity; the current agent prompt is not substituted for an unavailable historical snapshot. |

For LiveKit/custom calls with no explicit `version`, Zelto creates an initial
generated version if necessary, then assigns subsequent calls to the latest
deployed version. A weekly job can create another generated version when the
generalized prompt changes. It does **not** detect pipeline-only changes, and
it does not immediately distinguish two releases running concurrently. Use
explicit labels for experiments.

A **draft candidate** created in Zelto is for simulations. It has not handled
real calls and cannot be selected as an experiment arm. Deploy the release in
your own stack and upload calls with its version label to make it available.

## Verify before launching an experiment

1. Send a call from release A and another from release B with the **same agent
   ID**, distinct call IDs, and distinct version labels.
2. Wait for processing. HTTP `200` with `{ "received": true }` acknowledges
   acceptance; it does not mean the call and version are already visible.
3. Open the agent's **Versions** tab. Confirm both labels and inspect which
   calls belong to each. Check prompt/configuration snapshots where available.
4. Open **Experiments → New experiment** and select that agent and those two
   versions. Review the captured differences and recent call counts.
5. Choose configured monitors and use **Check recent traffic**. Historical
   traffic checks the setup; it does not backfill the new experiment's results.
6. Enter a name and launch. Keep reporting the actual version on each future
   call and route callers consistently in your own platform. Send a stable
   `endUserId` where available for caller-level analysis.

The experiment's selected versions are fixed at launch. Calls from a third
release do not automatically replace A or B. A new comparison needs a new
experiment. Zelto observes traffic; it does not deploy releases or route calls.

## Troubleshooting

| Symptom | Check |
| - | - |
| Both releases appear as one version | Send distinct top-level `version` values. Check for a missing label or a label reused across releases. |
| Every call creates a version | Stop using per-session values in `version`; keep `call.externalId` unique instead. |
| The two versions cannot be selected together | They must be registered or observed versions of the same agent. Keep the agent ID stable and check that neither is a draft. |
| A version exists but its prompt/configuration is unavailable | Send the actual snapshots from your worker. A label alone contains no configuration. |
| Changing the model did not create a new version | Automatic detection is prompt-based. Send a new explicit release label. |
| Recent calls are visible but the new experiment has no results yet | The traffic preview includes historical calls; experiment results use eligible calls in the experiment window and require evaluation processing. |
