---
title: "Set a voice line"
method: PATCH
path: "/api/agents/{sid}/voice"
tags: ["agents"]
---

# Set a voice line

`PATCH /api/agents/{sid}/voice`

## Request

- Base URL: `https://beta.getsales.io/unibox/v1`
- URL: `https://beta.getsales.io/unibox/v1/api/agents/{sid}/voice`
- Auth: HTTP bearer and API key in header `Team-SID`

Set a line of how an AI SDR talks (by sid; golden_set.voice): length, questions, register or sign_off, in your words, up to 160 characters. A line you set is yours: it stays when the voice forms again from the Golden set (voice.pinned names them) and the prompt tells the agent to keep to it. null lets a line go, so the next forming writes it again. The summary is formed, never set. nothing_to_update when no line is named.

Contract:
- MCP tool `update_agent_voice`, registry package `mcp.unibox/agents`, mount `unibox.agent-memory`.
- Operation `update`, response envelope `update`.
- Flags: none.

## Path parameters

- `sid` string, required: The agent (ub_ag_…).

## Request body

- UpdateAgentVoiceRequest: Request body of `update_agent_voice`.
  - `length` string, nullable: How long its replies run ("2 to 3 sentences"); null lets the line go.
  - `questions` string, nullable: How it asks ("one, at the end"); null lets the line go.
  - `register` string, nullable: The register ("du with agencies, Sie when they wrote Sie"); null lets the line go.
  - `sign_off` string, nullable: How it signs off ("VG, Julia"); null lets the line go.

## Response `200`

`update` success envelope.

- UpdateAgentVoiceResponse
  - `success` true, required
  - `operation` 'update', required
  - `item` object, required
    - `sid` string, required: Agent sid (ub_ag_…). Stable for the life of the agent; a playtable or a workflow picks an agent by it.
    - `team_sid` string, required: Owning team (ts_tm_…, external - gs.service.id). Many agents per team since block 10 (2026-10-30), at most 20.
    - `state` 'setup_required' | 'on' | 'off', required: setup_required = provisioned but disabled, inbox classifies only; on = planning and acting per the mode ladder; off = classification continues, nothing is planned or sent.
    - `mode` 'copilot' | 'autopilot' | 'off', required: FALLBACK autonomy. The effective mode of a thread is the per-thread override, else the owning automation, else this.
    - `name` string, required: Display name, 1..255 chars. The first agent ships as "AI SDR".
    - `is_default` boolean, required: The team's default (block 10, 2026-10-30): exactly one agent per team carries it. A thread with no agent of its own, a playtable or a workflow that picks none, falls to it. Moved with update_agent {is_default: true}; never taken off directly.
    - `knowledge` object, required: The questions and answers it answers from, the booking mode, the sites read. Its prose became the Brief on 2026-10-20.
      - `booking_mode` 'link' | 'native': link sends the host's link (default, the only live mode; the sender host's since block AC4, where the Playbook goal's link stood from 2026-10-19 and the knowledge's calendar link before); native (slots in thread) is accepted but inert.
      - `source_urls` string[]: Up to 20 sites scraped into the knowledge pack.
      - `interview_transcript_ref` string, nullable: Reference to the gs.agents.sdr setup-interview session, max 255 chars. A pointer, never a copy of the transcript.
    - `brief` object, required: The Brief (2026-10-20): the team's one markdown document every turn reads. A row stored before it reads its old offer, ICP, goal, general context and tone composed into the five sections.
      - `text` string, required: The Brief in markdown, the team's own words, max 49152 chars. A line in brackets ("- [a topic it should leave to you]") is a slot still open: the model never reads it.
      - `compact` string, nullable, required: [ro] The copy the model reads, max 8192 chars, written by the compaction after a save that moved the text; null until it lands (the text without its slots is read meanwhile).
      - `digest` string, nullable, required: [ro] The classifier's digest, max 400 chars, written by the same compaction; null until it lands.
    - `settings` object, required: The behavior caps the mode gate and the planners enforce server-side.
      - `max_auto_messages_per_conversation` integer: Agent-authored sends allowed on one thread. 1..20, default 4; the gate parks the next send for approval once it is spent.
      - `followups_for_synced_threads` boolean: Default false: threads an external sequencer already follows up on are left alone (double-touch protection).
      - `escalation_triggers` string[]: Built-in hand-offs on; default: all but pricing_question and call_request. Six judged by the classifier, multiple_reschedules at the third, low_confidence under 0.5. A firing stops the agent; a task names it; the thread goes needs_you.
      - `classification_merge_seconds` integer: Debounce window that merges an inbound burst into ONE classify run. 10..600, default 60.
      - `messages_recency_days` integer: How far back the thread tail is read into LLM context. 1..365, default 30.
      - `custom_escalation_triggers` string[]: The team's own hand-offs in words, each finishing: hand the thread to a person when the lead ... Up to 10, 3..255 chars, distinct; default none. They hand off like the built-in ones, the task titled <lead>: <condition>. Replaced whole.
      - `after_person_message` 'plan_followups' | 'only_when_asked': After a person on the team writes in a thread the AI SDR works on, it stops sending there. plan_followups (default): it plans the follow-ups for them, in their words, as a plan waiting for their OK. only_when_asked: nothing until Make follow-ups.
      - `person_followups_count` integer: How many follow-ups it plans after a person's message. 1..3, default 2.
      - `person_followups_every_days` integer: Days between those follow-ups, counted from the person's message; each goes at ten in the morning of the workspace's day, never on a weekend. 1..30, default 3.
      - `person_followups_then` 'task' | 'agent_continues': What ends them when none is answered: task (default) opens a task for the person and the thread is theirs again; agent_continues lets the AI SDR follow up on its own, under its caps.
      - `reply_split` 'one_message' | 'two_or_three': How a long LinkedIn reply goes out (2026-10-19): two_or_three (default) cuts it at its paragraphs or sentences into two or three messages a few seconds apart, as a person sends them; one_message sends it whole. An email never splits.
      - `credits_cap_per_conversation` integer: The credits the agent may spend on one thread (its turns' model cost at 25 credits a dollar), 1..1000, default 40. At the cap the thread is handed to a person with a task; closing the task lets it go on, and the meter keeps counting.
      - `handoff_notify_email` boolean: Default true: a hand-off (a trigger that fired, the credits cap) is also told by email to the thread's sender profile assignee, else the workspace owner. The task in the inbox is always there.
      - `reply_language` 'lead' | 'en' | 'de' | 'es' | 'fr' | 'pt' | 'ru' | 'ar' | 'hi' | 'zh': The language the agent writes in (2026-10-19): lead (default) mirrors the language they write in; a code pins every reply to that language whatever they write.
      - `learning` object: The golden set's roads (2026-10-20): what it learns from (the history walk, a person's own replies, the drafts a person edited) and whether an edit waits for a Keep.
        - `from_history` boolean: Default true: after a sender profile's history walk, its threads are paired by rule into the set.
        - `from_own_sends` boolean: Default true: a person's own reply in a live thread joins the set.
        - `from_approved_edits` boolean: Default true: an agent draft a person edited before it went joins the set, the person's words as the answer.
        - `edits_join` 'after_ok' | 'on_its_own': after_ok (default): an edited draft waits under From your edits for a Keep; on_its_own: it joins at once.
    - `created_by` object, required: Actor that provisioned the row (actor_type is system: the bootstrap job).
      - `actor_type` 'user' | 'support' | 'api_key' | 'system' | 'agent', required
      - `actor_sid` string, nullable, required
      - `team_sid` string, required
      - `actor_name` string, nullable: The OAuth client that acted ("Claude", "n8n"); null for a user, an API key or a system job.
      - `oauth_client_sid` string, nullable: The acting OAuth client (id_oc_*); null off the OAuth path.
      - `reason` string, nullable: Why a system actor wrote the row (snapshot_capture_job, ...); null otherwise.
      - `permissions` object, required: Internal audit context: the grant set the actor held at write time. Not a contract.
      - `cluster_id` integer, nullable: Internal audit context: the cluster that served the write.
      - `trace_id` string, nullable: Internal audit context: the trace id of the request that wrote the row.
    - `rules` object, required: The Rules (block AC4, 2026-10-26): what every reply keeps to; a row written before them reads as the corpus defaults. Read and changed on get_agent_rules / update_agent_rules too.
      - `reply_words` object: The length band per channel, in words.
        - `linkedin` object: Default 15 to 60: short replies get an answer about twice as often.
          - `min` integer: The fewest words a reply runs on this channel, 5..200.
          - `max` integer: The most, 5..200, at least the min.
        - `email` object: Default 25 to 80: the middle half of the replies people write back by email in the corpus.
          - `min` integer: The fewest words a reply runs on this channel, 5..200.
          - `max` integer: The most, 5..200, at least the min.
      - `meeting_ask` 'suggest_call_link_on_request' | 'ask_their_time' | 'suggest_call_with_link': How it asks for the meeting: suggest_call_link_on_request (default: a call suggested in one line, the link only when they ask or say yes), ask_their_time (their time asked for, no link), suggest_call_with_link (the call with the link in the same message).
      - `confirm_named_time` boolean: Default true: a time they name is confirmed in one line and nothing else is asked; a person puts it on the calendar.
      - `answer_then_call` boolean: Default true: a question before the call is answered in short, then the ask.
      - `acknowledge_low_signal` boolean: Default true: a lead who only says thanks gets one friendly line and the thread resumes its workflow.
      - `reply_delay_minutes` object: The wait before a reply on autopilot, a uniform pick between the two, in minutes, 0..60; default 1 to 2.
        - `min` integer
        - `max` integer
      - `quiet_hours` object: When no reply goes, in the lead's zone (the workspace's today); default 22:00 to 07:00. from equal to to is never quiet.
        - `from` string: "HH:MM" 24h.
        - `to` string: "HH:MM" 24h; a to before the from spans midnight.
      - `weekends` boolean: Default true: it replies on weekends; false keeps the weekend quiet.
      - `followups` integer: How many follow-ups when the lead goes quiet, 0..6; default 3 (the corpus: blocks climb with the fourth).
      - `followup_days` integer: Days between them, 1..14; default 4.
      - `something_new` boolean: Default true: every follow-up brings something new (a proof, a shorter ask, an easy way out), never a bump.
      - `checks` object: The checks before it sends, each on (default) or off. Stop after a no is the platform's and always on.
        - `unfilled_fields` boolean: A placeholder a template left ({{first_name}}, [Company]); refused as unfilled_field.
        - `same_text_twice` boolean: A text the thread already carries from us; refused as same_text_twice.
        - `link_before_reply` boolean: A booking link before the lead ever wrote; refused as link_before_reply.
        - `stop_words` boolean: The Stop words of the golden set; refused as words_caught.
        - `long_dashes` boolean: An em or en dash; refused as words_caught.
        - `template_after_reply` boolean: The sequence's template sent after the lead replied; the runner's check step.
        - `signature_block` boolean: An email reply that ends in a signature block of its own (the mailbox appends the team's); refused as signature_block. Email only.
    - `hosts` object[], required: Who hosts the meetings (block AC4): the sender row first, always, then colleagues; replaced whole on update_agent_hosts.
      - `id` string, required: Stable inside the list: sender for the sender profile's row, a minted handle (host_...) for a person; send it back to keep the row.
      - `kind` 'sender' | 'person', required: sender: the sender profile itself (one row, always kept); person: a colleague.
      - `member_sid` string, nullable, required: A team member (us_mb_...) when the host is one; null for a free name.
      - `name` string, nullable, required: The name shown and said; a member's is read at the member when null.
      - `link` string, nullable, required: Their own booking link, a URL; the sender row's is the team's link to offer (the campaign's and the table's win over it).
      - `introduced_as` string, nullable, required: How the reply introduces them when they are not the sender ("Peter, our co-founder, runs the demos"); said once, the first time the call comes up.
      - `scenario_keys` string[], required: The platform keys and the team's own rows' sids this host hosts; a key hosted by one host at most (the first that names it keeps it). A scenario nobody names is the sender's.
    - `goal` object, nullable, required: The agent's goal (block 10 step 10.3, 2026-10-30): the kind of what the offer asks for (call, signup, webinar, custom) and its words. Null = a call with no words. The link is never here: it is the sender host's (hosts[0].link), and a custom goal carries none.
      - `kind` 'call' | 'signup' | 'webinar' | 'custom', required: What the offer asks for: call (the stage after a yes books it), signup, webinar or custom (no stage after the yes, no link).
      - `words` string, required: What it is, in the team's words; empty = the kind alone.
    - `golden_set` object, required: The golden set's Words and voice (2026-10-20); the rows are search_agent_examples. A row written before it reads as no Words and no voice.
      - `words` object[], required: The Words, up to 50, one entry per word in any case.
        - `never` string, required: A word or a phrase the agent never writes, matched whole and in any case, max 64 chars.
        - `instead` string, nullable: What it says instead, max 128 chars; null: leave it out.
      - `voice` object, nullable, required: The voice formed from the golden set, a minute after it changed, with the lines a person set (update_agent_voice); null until it has rows or a line.
        - `summary` string, nullable, required: The team's voice in a few sentences, as the model reads it; null while the set has not formed one and a person set a line alone (block AC5).
        - `length` string, nullable: How long their replies run.
        - `questions` string, nullable: How they ask.
        - `register` string, nullable: The register they write in.
        - `sign_off` string, nullable: How they sign off.
        - `formed_from` integer, nullable: How many replies it was formed from.
        - `formed_at` string, nullable: ISO 8601 UTC, when it was formed.
        - `model` string, nullable: What formed it.
        - `pinned` string[]: The lines a person set (update_agent_voice, block AC5), among length, questions, register, sign_off: theirs, kept when the voice forms again.
    - `setup` object, required: The advised steps to go live the team skipped (2026-10-20); include readiness says where each step stands.
      - `skipped` string[], required: The advised steps the team skipped on the way to go live, in the steps' order.
    - `llm_config` object, nullable, required: Per-team model routing; null (the normal case) = platform defaults.
      - `classification_model` string, nullable: Per-team override of the classify model, max 128 chars. null = platform default.
      - `agent_model` string, nullable: Per-team override of the strategize / draft model, max 128 chars. null = platform default.
    - `setup_completed_at` string, nullable, required: ISO 8601 UTC, stamped the first time state left setup_required. null = setup never finished.
    - `created_at` string, required: ISO 8601 UTC.
    - `updated_at` string, required: ISO 8601 UTC.
    - `deleted_at` string, nullable, required: ISO 8601 UTC. Null on every agent a search lists; stamped by delete_agent.
  - `updated_fields` string[], required
  - `previous_values` object, required
  - `kept_fields` string[]: The fields this write did not change because the person's own value stands on them (stamped person); read the item for what landed.
  - `meta` object, required
    - `trace_id` string, required: UUID v7; same 128-bit value as the X-Trace-Id header.
    - `span_id` string, required: 16 hex chars, root span of this request.
    - `timestamp` string, required: ISO 8601 UTC (Y-m-dTH:i:sZ), response time.
    - `duration_ms` integer, required: Server-side wall clock.
    - `team_sid` string, nullable: The team this call ran in (the token team, or the team_sid override). Null when unauthenticated; absent from pre-2026-08-20 backends.
    - `actor_type` string, nullable: user | agent | api_key | system. Null when unauthenticated; absent from pre-2026-08-20 backends.

## Other responses

- `4XX`: MCP error envelope. `error.code` is one of validation_failed, nothing_to_update, not_found, relation_not_found, invalid_transition, limit_exceeded, payment_required, duplicate_rejected, conflict, delete_blocked, unauthorized, forbidden, rate_limited, not_implemented.
- `5XX`: MCP error envelope with `error.code` internal_error or service_unavailable.

---

[API](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-unibox.md) - [All operations](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-unibox/llms.txt) - [OpenAPI document](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-unibox/revisions/8f1bc317897f?raw)
