Versioning Lifecycle

View as Markdown

Every Atoms agent’s runtime config — prompt, tools, voice, language, post-call metrics — is managed as versions. You don’t edit the live config directly; you fork a draft, change what you need, publish it as a new version, and activate that version. This guide walks the full lifecycle from code.

For the dashboard equivalent and a deeper conceptual overview, see Agent Versioning.

All examples assume BASE="https://api.smallest.ai/atoms/v1" and SMALLEST_API_KEY is set in your environment.


The flow

1. Create the agent POST /agent
2. Read its current version GET /agent/{agentId}
3. Fork a draft POST /agent/{agentId}/drafts
4. Edit the draft PATCH /agent/{agentId}/drafts/{draftId}/config
5. Publish the draft POST /agent/{agentId}/drafts/{draftId}/publish
6. Activate the new version PATCH /agent/{agentId}/versions/{versionId}/activate
7. (Optional) place a call POST /conversation/outbound
8. Verify the active config GET /agent/{agentId}

Steps 5 and 6 are separate. POST .../publish does not activate the new version, even if you pass activate: true in the body. Always call activate explicitly.


Full walkthrough (curl)

$export BASE=https://api.smallest.ai/atoms/v1
$export AUTH="Authorization: Bearer $SMALLEST_API_KEY"
$export CT="Content-Type: application/json"
$
$# 1. Create the agent. `language` is optional, but if you pass it the
$# `switching` object is required (otherwise the request rejects with
># 400 "Invalid Input: Required").
$AGENT_ID=$(curl -s -X POST "$BASE/agent" -H "$AUTH" -H "$CT" \
> -d '{"name": "lifecycle-demo", "language": {"switching": {"isEnabled": false}}}' \
> | jq -r '.data')
$echo "agent: $AGENT_ID"
$
$# 2. Read the auto-populated active version id.
$ACTIVE_VERSION_ID=$(curl -s "$BASE/agent/$AGENT_ID" -H "$AUTH" \
> | jq -r '.data.activeVersionId')
$
$# 3. Fork a draft from the active version. The body must include either
$# `sourceVersionId` or `sourceDraftId` — an empty body returns 400.
$DRAFT_ID=$(curl -s -X POST "$BASE/agent/$AGENT_ID/drafts" -H "$AUTH" -H "$CT" \
> -d "{\"sourceVersionId\": \"$ACTIVE_VERSION_ID\"}" \
> | jq -r '.data.draftId')
$
$# 4. Patch the draft's config. Any subset of fields is allowed.
>curl -s -X PATCH "$BASE/agent/$AGENT_ID/drafts/$DRAFT_ID/config" \
> -H "$AUTH" -H "$CT" \
> -d '{"firstMessage": "Hello, this is the lifecycle demo agent."}' \
> > /dev/null
>
># 5. Publish the draft. Returns the new version id.
>VERSION_ID=$(curl -s -X POST "$BASE/agent/$AGENT_ID/drafts/$DRAFT_ID/publish" \
> -H "$AUTH" -H "$CT" \
> -d '{"label": "v2 - greeting added"}' \
> | jq -r '.data._id')
>
># 6. Activate. A security check runs after publish; if you activate too
># quickly you'll get a "security check pending" error. Sleep 6 s.
$sleep 6
$curl -s -X PATCH "$BASE/agent/$AGENT_ID/versions/$VERSION_ID/activate" \
> -H "$AUTH" > /dev/null
$
$# 7. (Optional) Place an outbound call to verify the new prompt is live.
$CONV_ID=$(curl -s -X POST "$BASE/conversation/outbound" -H "$AUTH" -H "$CT" \
> -d "{\"agentId\": \"$AGENT_ID\", \"phoneNumber\": \"+1...\", \"fromProductId\": \"...\"}" \
> | jq -r '.data.conversationId')
$
$# 8. Verify _resolvedConfig.firstMessage matches what you set in step 4.
$curl -s "$BASE/agent/$AGENT_ID" -H "$AUTH" \
> | jq '.data._resolvedConfig.firstMessage'

Python (using the SDK + raw requests)

The agent CRUD endpoints are exposed through the SmallestAI REST client. The draft/version endpoints are not yet first-class in the Python SDK, so they’re called directly:

1import os, time, requests
2from smallestai import SmallestAI
3
4BASE = "https://api.smallest.ai/atoms/v1"
5KEY = os.environ["SMALLEST_API_KEY"]
6HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
7client = SmallestAI(api_key=KEY) # only used for create + delete here
8
9# 1. Create the agent (via SDK)
10agent_id = client.atoms.agents.create_a_new_agent(
11 name="lifecycle-demo",
12 language={"switching": {"isEnabled": False}},
13).data # response.data is the agent id (string)
14
15# 2. Read active version id
16agent = requests.get(f"{BASE}/agent/{agent_id}", headers=HEADERS).json()
17active_version_id = agent["data"]["activeVersionId"]
18
19# 3. Fork a draft from the active version
20draft_id = requests.post(
21 f"{BASE}/agent/{agent_id}/drafts",
22 headers=HEADERS,
23 json={"sourceVersionId": active_version_id},
24).json()["data"]["draftId"]
25
26# 4. Patch the draft config
27requests.patch(
28 f"{BASE}/agent/{agent_id}/drafts/{draft_id}/config",
29 headers=HEADERS,
30 json={"firstMessage": "Hello, this is the lifecycle demo agent."},
31).raise_for_status()
32
33# 5. Publish — returns the new version
34version_id = requests.post(
35 f"{BASE}/agent/{agent_id}/drafts/{draft_id}/publish",
36 headers=HEADERS,
37 json={"label": "v2 - greeting added"},
38).json()["data"]["_id"]
39
40# 6. Activate (after security-check delay)
41time.sleep(6)
42requests.patch(
43 f"{BASE}/agent/{agent_id}/versions/{version_id}/activate",
44 headers=HEADERS,
45).raise_for_status()
46print(f"agent {agent_id} now serving version {version_id}")

Common pitfalls

The publish endpoint accepts activate in its body, but the field is not honored — the new version comes back with isActive: false. Always call PATCH /versions/{versionId}/activate explicitly. Confirmed against the live API on 2026-06-11.

Each publish triggers a backend security check that takes ~6 seconds. Activating before it completes returns an error. The simplest fix is sleep 6 between publish and activate; for production, poll GET /agent/{id}/versions/{versionId} and wait for securityCheck.status: "passed".

The body must include either sourceVersionId (fork from a published version) or sourceDraftId (fork from another draft). An empty {} returns 400 "Either sourceVersionId or sourceDraftId is required".

The response body returns the new draftRevision counter and a few rotated block ids, but not the fields you sent in the PATCH. To confirm a value persisted, fetch the draft after with GET /agent/{id}/drafts/{draftId}.

If you include the language object in the create body, it must contain a switching object — even an empty one ("switching": {"isEnabled": false}) is enough. Omitting language entirely is also fine.

The agent delete endpoint is DELETE /agent/{id}/archive, not plain DELETE /agent/{id}. Plain DELETE returns 404.


What about PATCH /workflow/{workflowId}?

A legacy endpoint, PATCH /workflow/{workflowId} (the workflowId is on the agent document, not the agent id), writes directly to the underlying workflow document. On a versioned agent it bypasses the version lifecycle — the change isn’t captured as a version and fields missing from the payload can be silently overwritten. Always go through the drafts → publish → activate flow above.


API endpoint reference

StepEndpoint
Create agentPOST /agent
Read agent + active versionGET /agent/{agentId}
List draftsGET /agent/{agentId}/drafts
Fork a draftPOST /agent/{agentId}/drafts — body {sourceVersionId} or {sourceDraftId}
Rename a draftPATCH /agent/{agentId}/drafts/{draftId}
Discard a draftDELETE /agent/{agentId}/drafts/{draftId}
Patch draft configPATCH /agent/{agentId}/drafts/{draftId}/config
Diff draft vs versionGET /agent/{agentId}/drafts/{draftId}/diff?versionId=...
Publish a draftPOST /agent/{agentId}/drafts/{draftId}/publish
List versionsGET /agent/{agentId}/versions
Get one versionGET /agent/{agentId}/versions/{versionId}
Diff two versionsGET /agent/{agentId}/versions/diff?baseId=...&compareId=...
Activate a versionPATCH /agent/{agentId}/versions/{versionId}/activate
Archive the agentDELETE /agent/{agentId}/archive