> 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=:draft&b=` | | `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=&b=` | | `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. > **Note** > > 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](/api-reference/deprecations/voice-agents) 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 `: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. } } ``` > Endpoint-by-endpoint mapping from the old drafts and versions API to branches.