> For the complete documentation index, see [llms.txt](https://docs.amigo.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.amigo.ai/developer-guide/platform-api/conversations/operators.md).

# Operators

Operators are human agents who can monitor, join, and take over live voice calls. The operator system provides escalation management, live call monitoring, call transcripts, and operator-action audit records built on the platform's [world model](/developer-guide/platform-api/data-world-model.md).

{% hint style="info" %}
**Two ways to address a live call.** This page documents the operator-scoped endpoints (`/operators/{operator_id}/...`), which act on a call through the operator's own identity. The [Runs](/developer-guide/platform-api/conversations/runs.md#operator-takeover) surface exposes the same takeover, handback, switch-mode, access-token, and guidance verbs addressed by the channel-neutral `run_id` instead - the path the Developer Console uses so a takeover resolves to the correct run regardless of how the operator reached it. Both paths drive the same underlying session.
{% endhint %}

## Operator Lifecycle

```mermaid
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#D4E2E7", "primaryTextColor": "#100F0F", "primaryBorderColor": "#083241", "lineColor": "#575452", "textColor": "#100F0F"}}}%%
stateDiagram-v2
    [*] --> Available: Register
    Available --> Listening: Join Call (listen)
    Available --> OnCall: Join Call (takeover)
    Listening --> OnCall: Switch to Takeover
    OnCall --> Listening: Switch to Listen
    OnCall --> Available: Leave Call
    Listening --> Available: Leave Call
    Available --> [*]: Deregister
```

* **Listen mode**: the operator monitors the call silently. The AI agent continues handling the conversation. The operator is muted at the telephony level.
* **Takeover mode**: the AI agent's audio output is muted (speaker suppressed) and the operator speaks directly with the caller. The agent's processing loop continues running. When the operator leaves or switches back to listen, the agent can resume without re-initializing the session; control and telephony propagation can add delay.

**Mode switching**: toggling between listen and takeover requests changes to the telephony mute state and agent speaker mute without recreating the underlying session. The change takes effect after the relevant control and telephony updates propagate. Operator transcripts during takeover can be captured as `human_segment` turns through a dedicated per-participant STT stream.

## Connection Methods

Operators can join calls via two methods, each using the [conference architecture](/developer-guide/platform-api/conversations/voice-agent.md#conference-architecture):

```mermaid
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#D4E2E7", "primaryTextColor": "#100F0F", "primaryBorderColor": "#083241", "lineColor": "#575452", "textColor": "#100F0F", "clusterBkg": "#F1EAE7", "clusterBorder": "#D7D2D0"}}}%%
flowchart TB
    subgraph Phone["Phone Connection"]
        P1["POST .../operators/{operator_id}/join-call\n(phone number)"] --> P2["Platform dials\noperator's phone"]
        P2 --> P3["Operator answers\n→ joins conference"]
        P3 --> P4["Dedicated STT stream\nfor operator audio"]
    end

    subgraph Browser["Browser WebRTC Connection"]
        B1["POST .../operators/{operator_id}/access-token"] --> B2["Returns JWT +\nconnect_params"]
        B2 --> B3["POST .../operators/{operator_id}/join-call\n(caches metadata)"]
        B3 --> B4["Frontend: Voice SDK\ndevice.connect()"]
        B4 --> B5["Browser audio joins\nconference directly"]
    end
```

| Method      | Audio Transport                 | STT                              | Latency                   | Use Case                                |
| ----------- | ------------------------------- | -------------------------------- | ------------------------- | --------------------------------------- |
| **Phone**   | PSTN (telephony dials operator) | Dedicated per-participant stream | Higher (PSTN round-trip)  | Remote operators, PSTN connectivity     |
| **Browser** | WebRTC (direct browser audio)   | Browser-handled                  | Lower (direct connection) | Desktop operators, real-time monitoring |

**Idempotent join**: joining is idempotent. The same operator joining the same call returns the cached response. A *different* operator attempting to join the same call receives a conflict error (only one operator per call).

### Browser Connection Flow

1. Request an access token via `POST .../operators/{operator_id}/access-token` → returns a JWT and `connect_params`.
2. Call `POST .../operators/{operator_id}/join-call` → caches operator metadata (no telephony API call for browser).
3. Frontend creates a Voice SDK device with the token, registers, and calls `device.connect({ params: connectParams })`.
4. The telephony system calls the voice agent's call-control instruction endpoint, which detects the browser operator via the client identity in the token.
5. The endpoint returns conference join instructions; browser audio joins the existing conference directly.
6. For listen-mode joins, the operator is muted at the telephony level.

## Three-Party Speaker Resolution

When an operator is on the call, three participants produce audio simultaneously. Speaker attribution uses a priority chain:

```mermaid
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#D4E2E7", "primaryTextColor": "#100F0F", "primaryBorderColor": "#083241", "lineColor": "#575452", "textColor": "#100F0F", "clusterBkg": "#F1EAE7", "clusterBorder": "#D7D2D0"}}}%%
flowchart TB
    A["Audio received"] --> B{"Operator STT\ndetects speech?"}
    B -->|Yes| C["Speaker = Operator"]
    B -->|No| D{"Caller STT\ndetects speech?"}
    D -->|Yes| E["Speaker = Caller"]
    D -->|No| F["Speaker = Caller\n(default)"]
```

Recorded turns can carry `speaker_id` and `speaker_role` to support transcript attribution in three-party conversations. Clients should still tolerate missing or ambiguous speaker metadata when a stream or persistence step is incomplete.

## Escalations

An escalation hands a call to a human. There are two paths:

| Path                      | How It Happens                                                                                                                                                                                                                                                                                                  |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Operator join**         | An operator joins the call from the dashboard or escalation queue (listen or takeover mode). Joining a call that has no open escalation creates one with trigger `operator`.                                                                                                                                    |
| **Agent call forwarding** | The agent's context graph reaches a forwarding action (`forward_call`) and transfers the caller to a human phone number - for example when the caller asks for a person or the flow requires human judgment. See [call forwarding](/developer-guide/platform-api/conversations/voice-agent.md#call-forwarding). |

### Escalation Lifecycle

```mermaid
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#D4E2E7", "primaryTextColor": "#100F0F", "primaryBorderColor": "#083241", "lineColor": "#575452", "textColor": "#100F0F"}}}%%
stateDiagram-v2
    [*] --> Requested: Escalation created
    Requested --> Connected: Operator joins call
    Connected --> Handback: Operator returns to listen
    Handback --> Completed: Call ends or operator completes
    Connected --> Completed: Call ends or operator completes
    Requested --> [*]: Call ends (no operator available)
```

**Dual-entity event writes**: supported escalation events are written to the call entity and operator entity when both writes succeed, enabling independent state recomputation for each. The call entity tracks escalation history; the operator entity tracks availability and performance.

**Event chain**: successful escalation events can supersede the previous event (`escalation.requested` → `escalation.connected` → `escalation.handback` → `escalation.completed`), forming a versioned event history.

| Event                  | Written When                               | Key Data                                           |
| ---------------------- | ------------------------------------------ | -------------------------------------------------- |
| `escalation.requested` | Escalation created                         | `escalation_id`, trigger, trigger source, reason   |
| `escalation.connected` | Operator joins and takes over              | Operator entity ID, connection type, response time |
| `escalation.handback`  | Operator returns to listen mode            | Agent resumes                                      |
| `escalation.completed` | Call ends or operator explicitly completes | `handle_time_seconds`, outcome                     |

## Operator Flow

```mermaid
%%{init: {"theme": "base", "themeVariables": {"actorBkg": "#083241", "actorTextColor": "#FFFFFF", "actorBorder": "#083241", "signalColor": "#575452", "signalTextColor": "#100F0F", "labelBoxBkgColor": "#F1EAE7", "labelBoxBorderColor": "#D7D2D0", "labelTextColor": "#100F0F", "loopTextColor": "#100F0F", "noteBkgColor": "#F1EAE7", "noteBorderColor": "#D7D2D0", "noteTextColor": "#100F0F", "activationBkgColor": "#E8E2EB", "activationBorderColor": "#083241", "altSectionBkgColor": "#F1EAE7", "altSectionColor": "#100F0F"}}}%%
sequenceDiagram
    participant Op as Operator
    participant API as Platform API
    participant VA as Voice Agent
    participant Caller

    Note over Caller,VA: Active call in progress

    Op->>API: POST .../operators/{operator_id}/join-call
    API->>VA: Add to conference
    VA->>VA: Create operator STT stream
    Note over Op: Listen mode (muted)

    Op->>API: POST .../operators/{operator_id}/switch-mode (takeover)
    API->>VA: Unmute operator, mute agent speaker
    Note over VA: Agent processing continues (muted)

    Op-->>Caller: Operator speaks directly
    Caller-->>Op: Caller responds
    Note over VA: Captures human_segment turns

    Op->>API: POST .../operators/{operator_id}/switch-mode (listen)
    API->>VA: Mute operator, unmute agent speaker
    Note over VA: Agent resumes speaking

    Note over Op: Listen mode - monitoring

    Op->>API: POST /send-guidance
    API->>VA: Inject guidance event
    Note over VA: Interrupts agent speech
    Note over VA: Agent acts on guidance
    Note over Caller: Caller unaware of guidance

    Op->>API: POST .../operators/{operator_id}/leave-call
    API->>VA: Remove from conference
    Note over VA: Persist human transcripts
    Note over VA: Clean up operator STT
    Note over VA: Write escalation.completed
```

## Operator Guidance

Operators in listen mode can send text guidance to the agent without taking over the call:

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/send-guidance" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

```json
{
  "call_sid": "CA1234...",
  "message": "Ask for their insurance ID before confirming the appointment"
}
```

The guidance is injected into the active session as a `guidance` event type. When delivery succeeds, the session can interrupt current agent speech and incorporate the guidance into its next response. Delivery does not guarantee exact response timing or that the model will follow the instruction verbatim. This is distinct from operator takeover: the operator does not speak directly to the caller.

**Permission**: requires `Operator:Update` on the operator entity. The operator must belong to the same workspace as the call.

**How it works**: the platform delivers the guidance to the active voice session as an injected event (see [session event injection](/developer-guide/platform-api/conversations/voice-agent.md#session-event-injection)). The operator's identity is recorded in the call transcript as the event sender.

**Delivery status**: the response includes a `status` field. `"delivered"` confirms the session received the guidance; `"failed"` indicates the guidance could not be delivered. The `"queued_no_subscriber"` semantics (call ended or session not yet listening) apply only to the session event-injection endpoint documented in [session event injection](/developer-guide/platform-api/conversations/voice-agent.md#session-event-injection).

## Dashboard & Analytics

| View                   | Description                                                                                                 |
| ---------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Dashboard**          | Composite view: operator status counts, active escalation count, daily escalation stats, recent escalations |
| **Active Escalations** | Paginated queue of active escalations with trigger and call context                                         |
| **Escalation Stats**   | Aggregation by period (`day`, `week`, `month`) and dimension (`status`, `trigger`, `operator`)              |
| **Performance**        | Operator rankings by response time, resolution rate, and average handle time                                |
| **Call Transcript**    | Human-segment events for a specific operator's call participation                                           |

## Audit Log

The supported lifecycle actions below emit world model events when their event writes succeed, providing an operator-action audit record:

| Action              | Event Written           | Entities Updated                       |
| ------------------- | ----------------------- | -------------------------------------- |
| Join call           | `operator.joined_call`  | Call + Operator (status → busy)        |
| Switch to takeover  | `operator.mode_changed` | Call (audit)                           |
| Switch to listen    | `operator.mode_changed` | Call (audit)                           |
| Leave call          | `operator.left_call`    | Call + Operator (status → online)      |
| Complete escalation | `escalation.completed`  | Call + Operator (handle time recorded) |

**Dual-entity writes**: supported escalation events are written to both entities when both writes succeed, enabling entity-scoped queries for a call or operator without a cross-entity join. This design does not establish an API latency or computational-complexity guarantee.

## Entity Types

### Operator Entity

Projected state from operator events:

| Field                       | Description                                                |
| --------------------------- | ---------------------------------------------------------- |
| `status`                    | Current availability (online, busy, offline)               |
| `profile`                   | Name, contact info                                         |
| `total_escalations_handled` | Lifetime count of escalations handled                      |
| `avg_handle_time_seconds`   | Average escalation duration                                |
| `last_call_at`              | Timestamp of the operator's last completed escalation call |
| `last_status_change`        | Timestamp of the last availability transition              |

### Call Entity (Escalation Fields)

The call entity's projected state includes escalation-specific fields:

| Field               | Description                                                                                                |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| `escalation.status` | Current escalation state (none, requested, connected, handback, completed)                                 |
| `escalation`        | Escalation detail: trigger, trigger source, requested/connected/completed timestamps, wait and handle time |
| `human_segments`    | Operator speech transcripts captured during takeover                                                       |
| `audit`             | Operator action audit summary (event count, latest event)                                                  |

## Operator API

### Get the Operator Dashboard

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/dashboard" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Get Operator Performance

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/performance" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### List Active Escalations

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/escalations/active" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Get Escalation Statistics

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/escalations/stats" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### List Escalation Events

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/escalations" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Query the Operator Audit Log

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/audit-log" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Get an Operator Call Transcript

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/calls/{call\_sid}/transcript" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Get the Operator Queue

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/queue" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Generate an Operator Briefing

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/briefing" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Join a Call

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/join-call" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Leave a Call

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/leave-call" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Generate a Browser Audio Access Token

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/access-token" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Switch Operator Mode

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/switch-mode" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Record Operator Wrap-Up

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/wrap-up" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Register an Operator

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators" method="post" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### List Operators

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Get an Operator

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}" method="get" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Update an Operator

{% openapi src="<https://api.platform.amigo.ai/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}" method="patch" %}
<https://api.platform.amigo.ai/v1/openapi.json>
{% endopenapi %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.amigo.ai/developer-guide/platform-api/conversations/operators.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
