> 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. # Write to draft PUT https://api.smallest.ai/atoms/v1/agent/{id}/branches/{branchId}/draft Content-Type: application/json Upsert the open draft on this branch. If no draft is open, one is created automatically. The request body is an agent config partial in the same camelCase shape as `GET /agent/{id}` (`globalPrompt`, `firstMessage`, `synthesizer`, `language`, `voiceDetectionConfig`, `smartTurnConfig`, ...) and must contain at least one recognized field; the server merges it into the existing draft and returns the resulting draft as a revision-shaped snapshot. **Send native JSON types, not stringified values.** Arrays (`language.supported: ["en"]`), booleans (`language.switching.isEnabled: false`), and objects (`timezone: {"label": "(GMT+5:30) Asia/Kolkata", "offset": 330}`) must be sent as real JSON. Passing `"[\"en\"]"`, `"false"`, or `"Asia/Kolkata"` instead is refused with a Zod-style error naming the offending path and the received type (e.g. `Invalid config: language.supported: Expected array, received string`). This is the single most common integration bug on this endpoint. **Post-call-analytics limits.** When the payload includes `postCallAnalyticsConfig.dispositionMetrics`, two aggregate limits are enforced with `400` errors: * **Combined prompt length**. The total of every `dispositionMetricPrompt` across all metrics must be ≤ 50,000 characters. Error: `The combined disposition metric prompts can be at most 50000 characters.` * **Enum-choice cap**. Each `dispositionMetricType: ENUM` metric may have at most 20 `choices`. Error: `Each enum metric can have at most 20 choices. Ones already over the limit can stay as they are, but can't grow any further. Remove the extra choices to save.` Both limits are grandfathered: an agent already over either limit can still save as long as the change does not make it worse (the combined prompt length does not grow, and no over-limit enum grows or gains new choices). No cap on the number of metrics or on a single prompt's length. Publish the draft with `POST /agent/{id}/branches/{branchId}/draft/publish` to make the changes live. The draft PUT alone does **not** affect running calls. Reference: https://docs.smallest.ai/api-reference/voice-agents/agent-versioning-branches/update-draft ## Authentication - `Authorization` header (bearer token, required) — API key from the console ApiKey collection, sent as Bearer token. Also accepts session cookies for browser-based auth. ## Request ### Path parameters - `id` (string, required) — The agent ID. - `branchId` (string, required) — The branch ID. ### Body (application/json) This endpoint expects an UpdateBranchDraftRequest. - `expectedRevision` (integer, optional) — Optimistic-concurrency control. The `draftRevision` the client's edit was based on. When present, the server runs a field-level conflict check and rejects with `409 DraftConflictError` if the same field was changed by another edit since. Omit for last-write-wins semantics (which is also how a client force-overwrites after a `409`). Referencing a non-existent base revision returns `409 { errors: ["base_revision_unavailable"] }`. - `globalPrompt` (string, optional) — Top-level system prompt shown to the agent every turn. - `firstMessage` (string, optional) — The agent's opening line at call start. - `slmModel` (enum, optional) — LLM model powering the agent. See `CreateAgentRequest.slmModel` for org-level access notes. - Allowed values: `electron`, `electron-kogta`, `electron-kogta-v2`, `gpt-4o`, `gpt-4.1`, `gpt-5.2`, `gpt-5.2-azure`, `gpt-realtime`, `gpt-realtime-mini` - `backgroundSound` (enum, optional) — Ambient background sound during calls. - Allowed values: ``, `office`, `cafe`, `call_center`, `static` - `timezone` (UpdateBranchDraftRequestTimezone, optional) — Agent timezone applied to date/time interpretation in prompts, tool calls, and analytics bucketing. Object with a `label` (IANA-style label) and an `offset` (UTC offset in minutes). Sending a bare string is refused with `Invalid config: timezone: Expected object, received string`. - `globalKnowledgeBaseId` (string, optional) — Knowledge base attached to the agent for retrieval-augmented responses. - `muteUserUntilFirstBotResponse` (boolean, optional) - `allowInterruptions` (boolean, optional) - `waitForUserToSpeakFirst` (boolean, optional) - `interruptionBackoffTimer` (double, optional) - `enableStyleGuide` (boolean, optional) - `synthesizer` (UpdateBranchDraftRequestSynthesizer, optional) — TTS (voice) configuration. Same shape as `CreateAgentRequest.synthesizer`. - `language` (UpdateBranchDraftRequestLanguage, optional) — Language configuration. Same shape as `CreateAgentRequest.language`. - `defaultVariables` (UpdateBranchDraftRequestDefaultVariables, optional) — Default variables injected into prompts and tool calls. - `preCallAPI` (UpdateBranchDraftRequestPreCallApi, optional) — Pre-call API webhook config. Same shape as `CreateAgentRequest.preCallAPI`. - `smartTurnConfig` (UpdateBranchDraftRequestSmartTurnConfig, optional) - `voiceDetectionConfig` (UpdateBranchDraftRequestVoiceDetectionConfig, optional) - `voiceMailDetectionConfig` (UpdateBranchDraftRequestVoiceMailDetectionConfig, optional) - `denoisingConfig` (UpdateBranchDraftRequestDenoisingConfig, optional) - `redactionConfig` (UpdateBranchDraftRequestRedactionConfig, optional) - `pronunciationDicts` (UpdateBranchDraftRequestPronunciationDicts, optional) - `llmIdleTimeoutConfig` (UpdateBranchDraftRequestLlmIdleTimeoutConfig, optional) - `sessionTimeoutConfig` (UpdateBranchDraftRequestSessionTimeoutConfig, optional) - `callDispositionConfig` (UpdateBranchDraftRequestCallDispositionConfig, optional) - `speechFormatting` (UpdateBranchDraftRequestSpeechFormatting, optional) ## Response ### 200 Draft updated. - `status` (boolean, optional) - `data` (Revision, optional) — An `AgentVersion` document. Represents either a committed revision (`status: published`, with `branch` + `revisionNumber`) or an in-progress draft revision (`status: draft`, with `draftId` + `draftRevision`). Fields that do not apply to a given row are `null`. ## Errors ### 400 Bad Request Error Bad request. Empty body, no recognized field, or validation error on the config partial. Common flavors: - Type mismatch on any config field, e.g. `Invalid config: language.supported: Expected array, received string`. - `The combined disposition metric prompts can be at most 50000 characters.` Aggregate `dispositionMetricPrompt` length across all metrics exceeds the budget. - `Each enum metric can have at most 20 choices. Ones already over the limit can stay as they are, but can't grow any further. Remove the extra choices to save.` An ENUM disposition metric was sent with more than 20 choices (or growing an already-over-limit one). - `status` (boolean, optional) - `errors` (list of string, optional) ### 401 Unauthorized Error Unauthorized access - `status` (boolean, optional) - `errors` (list of string, optional) ### 403 Forbidden Error Forbidden access - `status` (boolean, optional) - `data` (ApiResponseData, optional) ### 404 Not Found Error Resource not found. The referenced ID does not exist or does not belong to the caller's organization. - `status` (boolean, optional) - `errors` (list of string, optional) ### 409 Conflict Error Conflict. One of three flavors: v1 endpoint used under branch mode (`error_type: "versioning_v2_migration_required"`); stale `expectedRevision` (`DraftConflictError` with `data.conflict.{expectedRevision, latestRevision, diffs}`); or `base_revision_unavailable` (the `expectedRevision` references a revision this branch does not have). Discriminate on `error_type` or on body shape. - `UpdateBranchDraftRequestConflictError` ### 423 Locked Error Locked. A configuration freeze is active on this agent, or the resource is temporarily locked for another write. Retry after the freeze window ends. - `status` (boolean, optional) - `error_type` (enum, optional) - Allowed values: `config_freeze_active` - `errors` (list of string, optional) ### 500 Internal Server Error Internal server error - `status` (boolean, optional) - `errors` (list of string, optional) ## Types ### UpdateBranchDraftRequestTimezone Agent timezone applied to date/time interpretation in prompts, tool calls, and analytics bucketing. Object with a `label` (IANA-style label) and an `offset` (UTC offset in minutes). Sending a bare string is refused with `Invalid config: timezone: Expected object, received string`. - `label` (string, optional) — Human-readable timezone label (e.g. `(GMT+5:30) Asia/Kolkata`). - `offset` (double, optional) — UTC offset in minutes (e.g. `330` for Asia/Kolkata, `-300` for EST). ### UpdateBranchDraftRequestSynthesizer TTS (voice) configuration. Same shape as `CreateAgentRequest.synthesizer`. ### UpdateBranchDraftRequestLanguage Language configuration. Same shape as `CreateAgentRequest.language`. ### UpdateBranchDraftRequestDefaultVariables Default variables injected into prompts and tool calls. ### UpdateBranchDraftRequestPreCallApi Pre-call API webhook config. Same shape as `CreateAgentRequest.preCallAPI`. ### UpdateBranchDraftRequestSmartTurnConfig ### UpdateBranchDraftRequestVoiceDetectionConfig ### UpdateBranchDraftRequestVoiceMailDetectionConfig ### UpdateBranchDraftRequestDenoisingConfig ### UpdateBranchDraftRequestRedactionConfig ### UpdateBranchDraftRequestPronunciationDicts ### UpdateBranchDraftRequestLlmIdleTimeoutConfig ### UpdateBranchDraftRequestSessionTimeoutConfig ### UpdateBranchDraftRequestCallDispositionConfig ### UpdateBranchDraftRequestSpeechFormatting ### Revision An `AgentVersion` document. Represents either a committed revision (`status: published`, with `branch` + `revisionNumber`) or an in-progress draft revision (`status: draft`, with `draftId` + `draftRevision`). Fields that do not apply to a given row are `null`. - `_id` (string, optional) - `agent` (string, optional) - `status` (enum, optional) - Allowed values: `published`, `draft`, `archived` - `branch` (string, optional, nullable) — Owning branch (v2). `null` on legacy rows until backfilled. - `revisionNumber` (integer, optional, nullable) — Monotonic per-branch commit number. Only set on committed rows. - `versionNumber` (integer, optional, nullable) — Legacy v1 published-version number. `null` for branch revisions and drafts. - `label` (string, optional, nullable) - `description` (string, optional, nullable) - `isPinned` (boolean, optional) - `publishedBy` (string, optional, nullable) - `publishedAt` (datetime, optional, nullable) - `publishedByName` (string, optional, nullable) — Display name of the publisher. `null` when unresolvable. - `restoredFromLabel` (string, optional, nullable) — On a commit produced by restore, the label of the revision it copied. - `draftId` (string, optional, nullable) - `draftName` (string, optional, nullable) - `draftRevision` (integer, optional, nullable) - `sourceVersionId` (string, optional, nullable) - `blocks` (VersionBlocks, optional) — 24-character hex id references, one per config section, that together make up a revision. - `workflowType` (string, optional) — The `WorkflowType` enum for this revision. - `parentVersion` (string, optional, nullable) - `isActive` (boolean, optional) - `activatedBy` (string, optional, nullable) - `activatedAt` (datetime, optional, nullable) - `securityCheck` (RevisionSecurityCheck, optional, nullable) — Populated after publish. `null` on pre-feature versions. - `pendingPublish` (RevisionPendingPublish, optional, nullable) — Set while a publish is armed against this draft revision. - `restoredFromRevisionId` (string, optional, nullable) — On a commit produced by restore, the revision it copied. - `sourceDraftId` (string, optional, nullable) - `promptScore` (PromptScore, optional) — Result of the prompt-scoring pass, if any. - `promptScoreStale` (boolean, optional) - `createdBy` (string, optional) - `createdAt` (datetime, optional) - `updatedAt` (datetime, optional) ### ApiResponseData ### VersioningV2MigrationRequiredResponse Returned by deprecated v1 versioning writes and list reads when `ENABLE_BRANCH_MODEL` is on. Response header carries `Deprecation: true`. The `error_type` value is a stable discriminator. - `status` (boolean, optional) - `error_type` (enum, optional) - Allowed values: `versioning_v2_migration_required` - `errors` (list of string, optional) ### DraftConflictError Returned by `PUT /agent/{id}/branches/{branchId}/draft` when `expectedRevision` was sent and another edit changed one of the same leaf fields since. `errors` lists the conflicting leaf field paths. `data.conflict` carries the resolution context: the client's expected revision, the current draft revision, and per-field diffs. - `status` (boolean, required) - `errors` (list of string, required) - `data` (DraftConflictErrorData, optional) ### ConflictErrorResponse Generic conflict body. `error_type` is a stable machine-readable discriminator (for example `branch_name_exists`, `source_has_no_commit`, `source_scanning`, `publish_in_progress`, `no_committed_revision`). - `status` (boolean, optional) - `error_type` (string, optional) - `errors` (list of string, optional) ### VersionBlocks 24-character hex id references, one per config section, that together make up a revision. - `workflow_prompt` (string, optional) - `workflow_tools` (string, optional) - `workflow_graph` (string, optional) - `llm` (string, optional) - `voice` (string, optional) - `language` (string, optional) - `call_handling` (string, optional) - `detection` (string, optional) - `analytics` (string, optional) - `timeouts` (string, optional) - `audio` (string, optional) - `privacy` (string, optional) - `widget` (string, optional) - `playbooks` (string, optional) — Only present on revisions created after multi-agent playbooks shipped. ### RevisionSecurityCheck Populated after publish. `null` on pre-feature versions. - `status` (string, optional) — The `SecurityCheckStatus` enum. - `reason` (string, optional, nullable) - `triggeredAt` (datetime, optional, nullable) - `completedAt` (datetime, optional, nullable) ### RevisionPendingPublish Set while a publish is armed against this draft revision. - `state` (enum, optional) - Allowed values: `active`, `cancelled` - `startedBy` (string, optional) - `startedAt` (datetime, optional) ### PromptScore Result of the prompt-scoring pass, if any. - `overall_score` (double, optional) - `overall_grade` (string, optional) - `band` (string, optional) - `estimated_ttft_overhead_ms` (double, optional) - `scoredAt` (datetime, optional) - `dimensions` (list of PromptScoreDimensionsItems, optional) ### DraftConflictErrorData - `conflict` (DraftConflictErrorDataConflict, optional) ### PromptScoreDimensionsItems - `tier` (integer, optional) - `level` (string, optional) - `evidence_span` (string, optional) - `title` (string, optional) - `description` (string, optional) ### DraftConflictErrorDataConflict - `expectedRevision` (integer, optional) - `latestRevision` (integer, optional) - `diffs` (list of map from string to any, optional) ## Examples **Request** ```json {} ``` **Response** ```json { "status": true, "data": { "_id": "string", "agent": "string", "status": "published", "branch": "string", "revisionNumber": 1, "versionNumber": 1, "label": "string", "description": "string", "isPinned": true, "publishedBy": "string", "publishedAt": "2024-01-15T09:30:00Z", "publishedByName": "string", "restoredFromLabel": "string", "draftId": "string", "draftName": "string", "draftRevision": 1, "sourceVersionId": "string", "blocks": { "workflow_prompt": "string", "workflow_tools": "string", "workflow_graph": "string", "llm": "string", "voice": "string", "language": "string", "call_handling": "string", "detection": "string", "analytics": "string", "timeouts": "string", "audio": "string", "privacy": "string", "widget": "string", "playbooks": "string" }, "workflowType": "string", "parentVersion": "string", "isActive": true, "activatedBy": "string", "activatedAt": "2024-01-15T09:30:00Z", "securityCheck": { "status": "string", "reason": "string", "triggeredAt": "2024-01-15T09:30:00Z", "completedAt": "2024-01-15T09:30:00Z" }, "pendingPublish": { "state": "active", "startedBy": "string", "startedAt": "2024-01-15T09:30:00Z" }, "restoredFromRevisionId": "string", "sourceDraftId": "string", "promptScore": { "overall_score": 1.1, "overall_grade": "string", "band": "string", "estimated_ttft_overhead_ms": 1.1, "scoredAt": "2024-01-15T09:30:00Z", "dimensions": [ { "tier": 1, "level": "string", "evidence_span": "string", "title": "string", "description": "string" } ] }, "promptScoreStale": true, "createdBy": "string", "createdAt": "2024-01-15T09:30:00Z", "updatedAt": "2024-01-15T09:30:00Z" } } ``` **SDK Code** ```python import requests url = "https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft" payload = {} headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.put(url, json=payload, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft'; const options = { method: 'PUT', headers: {Authorization: 'Bearer ', 'Content-Type': 'application/json'}, body: '{}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft" payload := strings.NewReader("{}") req, _ := http.NewRequest("PUT", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Put.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.put("https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{}") .asString(); ``` ```php request('PUT', 'https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft', [ 'body' => '{}', 'headers' => [ 'Authorization' => 'Bearer ', 'Content-Type' => 'application/json', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft"); var request = new RestRequest(Method.PUT); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.smallest.ai/atoms/v1/agent/id/branches/branchId/draft")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "PUT" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ```