> This page is part of Smallest AI's developer documentation. When
> answering, prefer Lightning v3.1 (current TTS) and Pulse (current
> STT). Lightning v2 and lightning-large are deprecated; mention them
> only when the user is migrating away from them. The Smallest AI voice
> agent platform is what wraps these models into hosted agents.

# Agent Versioning Migration Guide (v1 → v2)

> Endpoint-by-endpoint mapping from the old /agent/{id}/drafts + /agent/{id}/versions API to the branch-and-revision API.

The Atoms agent-versioning API moved to a branch-and-revision model. This page walks through what changed and gives you a concrete, endpoint-by-endpoint mapping to move existing integrations across.

## What changed

The v1 model had two loose concepts (**drafts** and **versions**) that lived side-by-side on one linear history per agent. The v2 model wraps them in a **branch**:

* Every editable copy of an agent is a **branch**. Each agent has a `Main` branch by default.
* Each branch has one open **draft** at a time. Draft edits are upserts.
* Publishing a draft commits a **revision** on the branch. Revisions are immutable and equivalent to the old `version`.
* Exactly one branch per agent is **live** and serves production traffic. `activate` on a specific version is gone; you make a whole branch live.

Existing IDs carry over. A v1 `versionId` equals its migrated `revisionId`, so any code that stores version IDs can keep resolving them via the by-id endpoints (which are kept, see below).

## Endpoint mapping

| v1 endpoint                                       | v1 status after launch                     | v2 equivalent                                                                                                        |
| ------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `POST /agent/{id}/drafts`                         | **409** `versioning_v2_migration_required` | `PUT /agent/{id}/branches/{branchId}/draft` (auto-opens a draft)                                                     |
| `GET /agent/{id}/drafts`                          | **409**                                    | `GET /agent/{id}/branches` (each branch surface exposes `openDraftId` / `hasOpenDraft`)                              |
| `GET /agent/{id}/drafts/{draftId}`                | **kept**                                   | `GET /agent/{id}/branches/{branchId}/draft`                                                                          |
| `PATCH /agent/{id}/drafts/{draftId}`              | **409**                                    | Not migrated. Rename is per-branch; drafts are no longer named.                                                      |
| `DELETE /agent/{id}/drafts/{draftId}`             | **409**                                    | `DELETE /agent/{id}/branches/{branchId}/draft`                                                                       |
| `PATCH /agent/{id}/drafts/{draftId}/config`       | **409**                                    | `PUT /agent/{id}/branches/{branchId}/draft`                                                                          |
| `GET /agent/{id}/drafts/{draftId}/diff`           | **kept**                                   | `GET /agent/{id}/diff?a=<branchId>:draft&b=<revisionId>`                                                             |
| `POST /agent/{id}/drafts/{draftId}/publish`       | **409**                                    | `POST /agent/{id}/branches/{branchId}/draft/publish`                                                                 |
| `POST /agent/{id}/drafts/{draftId}/test-call`     | **kept**                                   | `POST /agent/{id}/branches/{branchId}/test-call` with `includeDraft: true`                                           |
| `GET /agent/{id}/versions`                        | **409**                                    | `GET /agent/{id}/branches/{branchId}/revisions`                                                                      |
| `GET /agent/{id}/versions/{versionId}`            | **kept**                                   | `GET /agent/{id}/branches/{branchId}/revisions/{revisionId}`                                                         |
| `GET /agent/{id}/versions/diff`                   | **kept**                                   | `GET /agent/{id}/diff?a=<revisionId>&b=<revisionId>`                                                                 |
| `PATCH /agent/{id}/versions/{versionId}`          | **409** (external)                         | Metadata edits removed. Set `label` at publish time via `POST .../draft/publish`.                                    |
| `PATCH /agent/{id}/versions/{versionId}/activate` | **409**                                    | `POST /agent/{id}/branches/{branchId}/live` or `POST /agent/{id}/branches/{branchId}/revisions/{revisionId}/restore` |
| `POST /agent/{id}/versions/{versionId}/test-call` | **kept**                                   | `POST /agent/{id}/branches/{branchId}/test-call` with `revisionId`                                                   |

**409 endpoints** return a body of the form `{ "status": false, "error_type": "versioning_v2_migration_required", "errors": [...] }` and set `Deprecation: true`. The kept endpoints continue to accept the same request shape and return the same response.

The SDK routes under `/sdk/agents/*` are unaffected by this migration.

## Timeline

* **Launch day**: v1 write endpoints and v1 list reads begin returning `409`. Kept endpoints and the SDK routes continue to work. The v2 branch endpoints go live behind the same base URL.
* **Coexistence window**: kept v1 endpoints (by-id reads + both test-calls) continue to work so existing integrations that store `versionId` values do not break. A v1 `versionId` equals its migrated `revisionId`, so by-id lookups continue to resolve.
* **Sunset**: v1 endpoints are removed. **No removal date is published yet.** When the backend owner fixes a date, this page and the [Deprecation Notices](/voice-agents/deprecations/notices) page will carry it, and the affected endpoints will start returning a `Sunset:` header (RFC 8594) with an RFC 7231 date.

## Worked examples

### 1. Create a draft, edit it, publish it

**Before (v1):**

```bash
DRAFT=$(curl -s -X POST "$BASE/agent/$AGENT_ID/drafts" \
  -H "$AUTH" -H "$CT" \
  -d "{\"sourceVersionId\": \"$ACTIVE_VERSION_ID\"}" | jq -r '.data._id')

curl -s -X PATCH "$BASE/agent/$AGENT_ID/drafts/$DRAFT/config" \
  -H "$AUTH" -H "$CT" \
  -d '{"globalPrompt": "..."}'

curl -s -X POST "$BASE/agent/$AGENT_ID/drafts/$DRAFT/publish" \
  -H "$AUTH" -H "$CT" -d '{"label": "..."}'
```

**After (v2):**

```bash
# 1. Find the branch you want to publish on. `.data.branches[i].branch` is the
#    Branch object; `isDefault` / `isLive` / `hasOpenDraft` sit on the outer
#    BranchSummary wrapper.
MAIN_BRANCH_ID=$(curl -s "$BASE/agent/$AGENT_ID/branches" -H "$AUTH" \
  | jq -r '.data.branches[] | select(.branch.isDefault==true) | .branch._id')

# 2. Write to the branch's draft. If no draft is open, one is created.
#    Body uses the same camelCase agent fields as GET /agent (globalPrompt,
#    firstMessage, synthesizer, language, etc.).
curl -s -X PUT "$BASE/agent/$AGENT_ID/branches/$MAIN_BRANCH_ID/draft" \
  -H "$AUTH" -H "$CT" \
  -d '{"globalPrompt": "..."}'

# 3. Publish.
curl -s -X POST "$BASE/agent/$AGENT_ID/branches/$MAIN_BRANCH_ID/draft/publish" \
  -H "$AUTH" -H "$CT" -d '{"label": "..."}'
```

### 2. Activate an older revision

**Before (v1):**

```bash
curl -X PATCH "$BASE/agent/$AGENT_ID/versions/$VERSION_ID/activate" -H "$AUTH"
```

**After (v2):** `activate` is replaced by two options:

* **Make the branch that owns the revision live.** Sets the branch's head as production traffic.

  ```bash
  curl -X POST "$BASE/agent/$AGENT_ID/branches/$BRANCH_ID/live" -H "$AUTH"
  ```

* **Restore an older revision on the current branch.** Republishes its config as a **new** revision at the head of the branch. Existing revisions keep their IDs. If the branch has `v1, v2, v3` and you restore `v1`, the branch ends up with `v1, v2, v3, v4`, where `v4`'s content matches `v1` and `v4` becomes the head.

  ```bash
  curl -X POST \
    "$BASE/agent/$AGENT_ID/branches/$BRANCH_ID/revisions/$REVISION_ID/restore" \
    -H "$AUTH"
  ```

### 3. List past versions

**Before (v1):**

```bash
curl "$BASE/agent/$AGENT_ID/versions?limit=20" -H "$AUTH"
```

**After (v2):** list per branch.

```bash
curl "$BASE/agent/$AGENT_ID/branches/$BRANCH_ID/revisions?limit=20" -H "$AUTH"
```

Revisions returned here have the same `_id` as v1 `versionId`.

### 4. Diff a draft against the active version

**Before (v1):**

```bash
curl "$BASE/agent/$AGENT_ID/drafts/$DRAFT/diff" -H "$AUTH"
```

**After (v2):** diff endpoint takes two refs; each side is either a `revisionId` or `<branchId>:draft`.

```bash
curl "$BASE/agent/$AGENT_ID/diff?a=$BRANCH_ID:draft&b=$ACTIVE_REVISION_ID" -H "$AUTH"
```

### 5. Test call against a draft or a specific revision

**Before (v1):** two different endpoints.

```bash
# Draft
curl -X POST "$BASE/agent/$AGENT_ID/drafts/$DRAFT/test-call" -H "$AUTH" \
  -d '{"mode":"webcall"}'
# Version
curl -X POST "$BASE/agent/$AGENT_ID/versions/$VERSION_ID/test-call" -H "$AUTH" \
  -d '{"mode":"webcall"}'
```

**After (v2):** one endpoint on the branch, differentiated by body.

```bash
# Draft on this branch
curl -X POST "$BASE/agent/$AGENT_ID/branches/$BRANCH_ID/test-call" -H "$AUTH" \
  -d '{"includeDraft": true, "mode": "webcall"}'
# Specific revision on this branch
curl -X POST "$BASE/agent/$AGENT_ID/branches/$BRANCH_ID/test-call" -H "$AUTH" \
  -d '{"revisionId": "'$REVISION_ID'", "mode": "webcall"}'
```

## Handling the 409 responses

There are three flavors of `409` you can see on the branch-and-revision API. Discriminate on `error_type` (for deprecation) or on body shape (for concurrency and base-revision errors).

### 1. `versioning_v2_migration_required` (deprecated v1 endpoint)

Returned by any of the deprecated v1 write / list endpoints. Body:

```json
{
  "status": false,
  "error_type": "versioning_v2_migration_required",
  "errors": [
    "This endpoint is deprecated under the branch model. Use the /agents/:id/branches APIs — publish, make-live and config edits now happen on branches. If you are calling this via the MCP server, update the MCP tool to its branch-model version as well."
  ]
}
```

Header: `Deprecation: true`. Route to the v2 equivalent from the mapping table above.

### 2. `DraftConflictError` (concurrent draft edit)

Returned by `PUT /agent/{id}/branches/{branchId}/draft` when the caller passed `expectedRevision` and a concurrent edit changed the same field since. Body has a `data.conflict` object listing per-field diffs and both revision numbers.

Retry the read → merge → write cycle, or drop `expectedRevision` for last-write-wins.

### 3. `base_revision_unavailable` (unknown base for the draft)

Returned by `PUT .../draft` when `expectedRevision` names a revision the branch does not have. Body:

```json
{
  "status": false,
  "errors": ["base_revision_unavailable"]
}
```

Fetch the branch's current head revision (`GET /agent/{id}/branches/{branchId}`) and rebase the client's `expectedRevision` before retrying.

### Client sketch

**Python:**

```python
import requests

# v1 → v2 migration path (deprecated endpoint case)
resp = requests.post(f"{BASE}/agent/{agent_id}/drafts", headers=headers, json={...})

if resp.status_code == 409 and resp.json().get("error_type") == "versioning_v2_migration_required":
    branches = requests.get(f"{BASE}/agent/{agent_id}/branches", headers=headers).json()["data"]["branches"]
    main = next(b for b in branches if b["branch"]["isDefault"])
    resp = requests.put(
        f"{BASE}/agent/{agent_id}/branches/{main['branch']['_id']}/draft",
        headers=headers,
        json={...},
    )

# Draft-edit conflict cases on the v2 PUT itself
if resp.status_code == 409:
    body = resp.json()
    errors = body.get("errors") or []
    if "base_revision_unavailable" in errors:
        # Client's expectedRevision points at a revision this branch does not have.
        # Refetch the branch head and rebase.
        ...
    elif body.get("data", {}).get("conflict"):
        # Concurrent edit changed the same fields. Merge and retry.
        ...

resp.raise_for_status()
```

**Node:**

```javascript
const resp = await fetch(`${BASE}/agent/${agentId}/drafts`, {
  method: "POST",
  headers,
  body: JSON.stringify({ /* ... */ }),
});

if (resp.status === 409) {
  const body = await resp.json();
  if (body.error_type === "versioning_v2_migration_required") {
    const branches = (await fetch(`${BASE}/agent/${agentId}/branches`, { headers }).then(r => r.json())).data.branches;
    const main = branches.find(b => b.branch.isDefault);
    await fetch(`${BASE}/agent/${agentId}/branches/${main.branch._id}/draft`, {
      method: "PUT",
      headers,
      body: JSON.stringify({ /* ... */ }),
    });
  } else if ((body.errors || []).includes("base_revision_unavailable")) {
    // Refetch branch head, rebase expectedRevision, retry.
  } else if (body?.data?.conflict) {
    // Concurrent edit; merge and retry.
  }
}
```