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

# Operators

Human-in-the-loop call monitoring, takeover, escalation management, and operator analytics.

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" %}
**API integration required.** The Developer Console does not provide operator pages or takeover controls. This page documents the operator-scoped endpoints (`/operators/{operator_id}/...`). The [Runs API](/developer-guide/platform-api/conversations/runs.md#operator-takeover) addresses controls by channel-neutral `run_id`. Both paths act on supported underlying sessions; confirm runtime support and operator access before implementing your own intervention interface. A route in the public schema does not establish support on every voice provider.
{% endhint %}

## Operator Lifecycle

```mermaid
%%{init: {"theme":"base","themeVariables":{"fontFamily":"Inter, sans-serif","fontSize":"16px","background":"#FAF8F6","primaryColor":"#F1EAE7","primaryTextColor":"#1A1817","primaryBorderColor":"#D7D2D0","secondaryColor":"#FDFCFB","tertiaryColor":"#F6F2F0","lineColor":"#575452","textColor":"#3D3937","clusterBkg":"#FDFCFB","clusterBorder":"#E8E2DF","edgeLabelBackground":"#FAF8F6","actorBkg":"#F1EAE7","actorTextColor":"#1A1817","actorBorder":"#D7D2D0","actorLineColor":"#575452","signalColor":"#575452","signalTextColor":"#3D3937","labelBoxBkgColor":"#F1EAE7","labelBoxBorderColor":"#D7D2D0","labelTextColor":"#3D3937","loopTextColor":"#3D3937","noteBkgColor":"#F6F2F0","noteBorderColor":"#D7D2D0","noteTextColor":"#3D3937","activationBkgColor":"#F1EAE7","activationBorderColor":"#D7D2D0","altSectionBkgColor":"#FAF8F6","altSectionColor":"#3D3937"},"fontFamily":"Inter, sans-serif"}}%%
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. Agent speech and turn processing are suspended while the operator drives. When the operator leaves the call from takeover, the agent resumes in the same session with what was said during the takeover, the caller's and the operator's turns, as context for its next reply. Switching back to listen also resumes the agent 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. During takeover, the caller's speech and the operator's, which comes through a dedicated per-participant transcription stream, are captured for the handback when transcription succeeds.

## Connection Methods

Voice operators join by phone. The platform dials the phone number in the operator profile and adds the answered leg to the existing conference. Registering or updating an operator with a number that has an extension, such as `+16095550199x123`, returns `422`, so enter the direct number. Your integration provides controls and displays available transcripts; the phone carries audio. Operators do not join through browser WebRTC.

Request listen mode explicitly when the operator should monitor silently before taking over. Check the join operation's default in its embedded contract below, and implement a confirmation step before requesting takeover. The API does not supply a user-interface confirmation flow.

Joining is idempotent for the same operator and call. A different operator attempting to join an already occupied call receives a conflict response.

## Operator Call Errors

Operator actions run as the operator named in the path. Join call, switch mode, leave call, send guidance, and wrap-up need a credential that belongs to that operator: the credential's user must be that operator, matched by entity or by the email on the operator's profile. A credential acting as another operator, or one with no user identity, gets `403`.

Join call, switch mode, and leave call act on the live call through telephony and report failures by cause:

| Status | When                                                                                                                                                                                                 |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `403`  | The credential isn't the operator in the path, or the call belongs to another workspace.                                                                                                             |
| `404`  | The operator, the active call, or the operator's leg on the call wasn't found.                                                                                                                       |
| `409`  | The call has already ended or the operator's leg has left the conference, another operator is already on the call, the operator is already on a call, or the operator's profile has no phone number. |
| `422`  | The request was refused, for example an operator phone number that isn't in the line's `allowed_forwarding_numbers`, a malformed call ID, or a mode other than `listen` or `takeover`.               |
| `502`  | Any other telephony or voice runtime failure.                                                                                                                                                        |
| `503`  | The voice runtime is unavailable.                                                                                                                                                                    |
| `504`  | Telephony or the voice runtime did not respond in time.                                                                                                                                              |

If muting a listen-mode operator fails after their phone was dialed, the platform removes that leg before it returns the error, so the operator is not left on the call unmuted. A `504` can leave the outcome unclear, so check the call before you retry. Correct the request before you retry a `409` or `422`.

## 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":{"fontFamily":"Inter, sans-serif","fontSize":"16px","background":"#FAF8F6","primaryColor":"#F1EAE7","primaryTextColor":"#1A1817","primaryBorderColor":"#D7D2D0","secondaryColor":"#FDFCFB","tertiaryColor":"#F6F2F0","lineColor":"#575452","textColor":"#3D3937","clusterBkg":"#FDFCFB","clusterBorder":"#E8E2DF","edgeLabelBackground":"#FAF8F6","actorBkg":"#F1EAE7","actorTextColor":"#1A1817","actorBorder":"#D7D2D0","actorLineColor":"#575452","signalColor":"#575452","signalTextColor":"#3D3937","labelBoxBkgColor":"#F1EAE7","labelBoxBorderColor":"#D7D2D0","labelTextColor":"#3D3937","loopTextColor":"#3D3937","noteBkgColor":"#F6F2F0","noteBorderColor":"#D7D2D0","noteTextColor":"#3D3937","activationBkgColor":"#F1EAE7","activationBorderColor":"#D7D2D0","altSectionBkgColor":"#FAF8F6","altSectionColor":"#3D3937"},"fontFamily":"Inter, sans-serif"}}%%
flowchart TB
    A["Audio received"] --> B{"Operator STT<br/>detects speech?"}
    B -->|Yes| C["Speaker = Operator"]
    B -->|No| D{"Caller STT<br/>detects speech?"}
    D -->|Yes| E["Speaker = Caller"]
    D -->|No| F["Speaker = Caller<br/>(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 integration requests operator join in 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":{"fontFamily":"Inter, sans-serif","fontSize":"16px","background":"#FAF8F6","primaryColor":"#F1EAE7","primaryTextColor":"#1A1817","primaryBorderColor":"#D7D2D0","secondaryColor":"#FDFCFB","tertiaryColor":"#F6F2F0","lineColor":"#575452","textColor":"#3D3937","clusterBkg":"#FDFCFB","clusterBorder":"#E8E2DF","edgeLabelBackground":"#FAF8F6","actorBkg":"#F1EAE7","actorTextColor":"#1A1817","actorBorder":"#D7D2D0","actorLineColor":"#575452","signalColor":"#575452","signalTextColor":"#3D3937","labelBoxBkgColor":"#F1EAE7","labelBoxBorderColor":"#D7D2D0","labelTextColor":"#3D3937","loopTextColor":"#3D3937","noteBkgColor":"#F6F2F0","noteBorderColor":"#D7D2D0","noteTextColor":"#3D3937","activationBkgColor":"#F1EAE7","activationBorderColor":"#D7D2D0","altSectionBkgColor":"#FAF8F6","altSectionColor":"#3D3937"},"fontFamily":"Inter, sans-serif"}}%%
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":{"fontFamily":"Inter, sans-serif","fontSize":"16px","background":"#FAF8F6","primaryColor":"#F1EAE7","primaryTextColor":"#1A1817","primaryBorderColor":"#D7D2D0","secondaryColor":"#FDFCFB","tertiaryColor":"#F6F2F0","lineColor":"#575452","textColor":"#3D3937","clusterBkg":"#FDFCFB","clusterBorder":"#E8E2DF","edgeLabelBackground":"#FAF8F6","actorBkg":"#F1EAE7","actorTextColor":"#1A1817","actorBorder":"#D7D2D0","actorLineColor":"#575452","signalColor":"#575452","signalTextColor":"#3D3937","labelBoxBkgColor":"#F1EAE7","labelBoxBorderColor":"#D7D2D0","labelTextColor":"#3D3937","loopTextColor":"#3D3937","noteBkgColor":"#F6F2F0","noteBorderColor":"#D7D2D0","noteTextColor":"#3D3937","activationBkgColor":"#F1EAE7","activationBorderColor":"#D7D2D0","altSectionBkgColor":"#FAF8F6","altSectionColor":"#3D3937"},"fontFamily":"Inter, sans-serif"}}%%
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 speech and turn processing suspended

    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://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}/send-guidance" method="post" %}
<https://platform.api-us.concurrence.com/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, and the credential must belong to that operator (see [Operator Call Errors](#operator-call-errors)). 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

These are API views for an integration, not current Developer Console pages.

| 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://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/operators/dashboard" method="get" %}
<https://platform.api-us.concurrence.com/v1/openapi.json>
{% endopenapi %}

### Get Operator Performance

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

### List Active Escalations

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

### Get Escalation Statistics

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

### List Escalation Events

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

### Query the Operator Audit Log

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

### Get an Operator Call Transcript

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

### Get the Operator Queue

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

### Generate an Operator Briefing

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

### Join a Call

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

### Leave a Call

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

### Switch Operator Mode

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

### Record Operator Wrap-Up

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

### Register an Operator

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

### List Operators

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

### Get an Operator

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

### Update an Operator

{% openapi src="<https://platform.api-us.concurrence.com/v1/openapi.json>" path="/v1/{workspace\_id}/operators/{operator\_id}" method="patch" %}
<https://platform.api-us.concurrence.com/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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.concurrence.com/developer-guide/platform-api/conversations/operators.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
