---
title: "Takeover Call"
method: POST
path: "/api/voice/takeover/{call_id}"
tags: ["voice-control", "voice-control"]
---

# Takeover Call

`POST /api/voice/takeover/{call_id}`

## Request

- Base URL: `https://api.dialogbrain.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.dialogbrain.com/api/voice/takeover/{call_id}`
- Auth: none declared

Operator takes over an in-progress agent-handled call.

Effects:
  1. `voice_sessions.metadata.mode` flips from `'agent'` to
     `'user_call'`. `taken_over_at` + `taken_over_by_user_id`
     recorded for audit. `voice_sessions.user_id` reassigned to
     the taking-over operator so `/rejoin` accepts their
     publish-token mint.
  1b. With `tab_id`, the clicking tab takes the microphone by force
     BEFORE anything fans out. Every inbox tab reacts to the
     `mode_changed` event below by mounting RejoinUserCallOverlay,
     and each /rejoin claims passively — so without this the first
     tab to answer won, and live 2026-08-21 that was a background
     tab: the operator who clicked got listen-only and a
     "Call is in another tab" chip on their own take-over. Same
     rule the dispatch applies for Call (internal_voice.py
     perform_dispatch_room): the acting tab wins, period.
  2. PUBLISH on `voice:call:control:{call_id}` with
     `{"type":"agent_state","disabled":true}` so the running
     worker's `_control_subscriber` sets `_taken_over=true`
     (silences agent's tts_node + llm_node + transcription_node).
  3. Frontend WS event `voice:call:mode_changed` fans out so all
     inbox tabs flip the active-call header to the user_call
     layout and `RejoinUserCallOverlay` auto-mounts.

Worker is NOT re-dispatched. Agent process keeps running silenced
for the rest of the call (cost: one idle worker per taken-over
call — acceptable for voice-with-zero-users today). Take Over is
one-way: operator owns the call until hangup. No "Resume agent"
gesture — drop the call and let a new one in if you need agent.

Idempotent: re-clicking on an already-taken-over call re-asserts
state (no noop guard on metadata.agent_disabled because that
field is no longer written; mode==='user_call' is the truth).

## Path parameters

- `call_id` string, required

## Request body

- RejoinRequest: Optional body for /rejoin and /coexist. `tab_id` is a per-tab UUID generated client-side (sessionStorage) and appended to the LK browser identity so multiple tabs/browsers logged in as the same user don't DUPLICATE_IDENTITY-kick each other in the LK room. Omit to fall back to the legacy `browser:{user}:{call}` shape (single-tab semantics — second tab WILL kick the first).
  - `tab_id` string, nullable

## Response `200`

Successful Response

- TakeoverResponse
  - `status` string, required
  - `call_id` string, required
  - `mode` string, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/dialogbrain/apis/dialogbrain-v2-api-gateway.md) · [All operations](https://skmtc.dev/dialogbrain/apis/dialogbrain-v2-api-gateway/llms.txt) · [OpenAPI document](https://skmtc.dev/dialogbrain/apis/dialogbrain-v2-api-gateway/revisions/77bef09a9209?raw)
