# CSTA Call Control

> Originate, hold, retrieve, transfer, consult, send DTMF, and release calls with the CSTA call-control verbs over the CTI control leg.

Drive a call from your own application the way a PBX operator would. Place
it. Put it on hold. Transfer it, blind or after a consult. Send DTMF. Hang it
up. These are the **call-control verbs** of FirstByte Voice's engine-native
[ECMA CSTA](/modalities/voice/cti/overview) surface. Each wire verb maps to a
standard CSTA operation. If you already speak CSTA, you already know what
these verbs do. You send them over the CTI control leg instead of a vendor
SDK.

> **NOTE:**
> Every verb on this page belongs to the **`call_control`** scope. The
>   short-lived CTI token that your control-plane API mints must carry
>   `call_control` in its `scopes` claim, or the engine refuses the verb. The
>   engine also tenant-checks per-call verbs against the call that they name.
>   You can only act on calls in your own workspace. See
>   [Connect &amp; authorize](/modalities/voice/cti/overview#auth).

## Verbs at a glance

| Wire verb | CSTA operation | What it does |
| --- | --- | --- |
| `makeCall` | Make Call | Originate a new call over a SIP trunk |
| `clearConnection` | Clear Connection | Release / hang up a call |
| `holdCall` | Hold Call | Put a connected party on hold |
| `retrieveCall` | Retrieve Call | Take a held party back off hold |
| `singleStepTransfer` | Single Step Transfer Call | Blind transfer to a destination |
| `consultationCall` | Consultation Call | Hold the call and dial a consult target |
| `transferCall` | Transfer Call | Complete a consultative transfer |
| `cancelConsultation` | Reconnect Call | Drop the consult, return to the held party |
| `generateDigits` | Generate Digits | Send DTMF into the call |

## How a verb call looks on the wire

The CTI control leg carries **length-prefixed, correlated request/reply
lines**. A request is the verb name, a tab, and a JSON argument object. The
engine answers with a JSON reply correlated to that request. (The first line
on any connection is always `auth\t<jwt>` — see the
[overview](/modalities/voice/cti/overview#connect).)

```text Request wire
makeCall	{"trunkId":"trunk_main","to":"+15551234567","from":"+15558675309"}
```

```json Reply
{ "ok": true, "callId": "C-8f3a2b" }
```

A reply is the **acknowledgement** that the engine accepted the verb. The
actual state change arrives asynchronously as a **CSTA event** on the `cti`
event track. Those events are *delivered*, *established*, *held*,
*retrieved*, *transferred*, *cleared*, and *failed*. You subscribe to the
track once for the whole tenant. Treat the reply as "the engine took the
request". Treat the event as "the call actually changed." See
[Routing &amp; events](/modalities/voice/cti/routing-events).

> **TIP:**
> The verb names *are* the API. A polyglot SDK, if you use one, exposes each
>   verb as a method of the same name under the `call_control` surface. It
>   manages the request/reply correlation for you. The JSON args below are
>   identical in both cases. Custom clients that speak raw MoQT or the `cti`
>   WebSocket subprotocol send the wire lines directly.

## Originate a call — `makeCall`

`makeCall` places a new outbound call over a provisioned SIP trunk. It maps
to CSTA **Make Call**. The reply carries the `callId`. You use that `callId`
to address the call in every later verb.

- **`trunkId`** (`string`, required) — The SIP trunk to originate over. Provision it first — see   [SIP trunking](/modalities/voice/transport-telephony/sip-trunking).
- **`to`** (`string`, required) — Destination number in E.164 (`+15551234567`).
- **`from`** (`string`, required) — Caller ID to present. Must be a number routable on `trunkId`.

**Request wire:**
```text
makeCall	{"trunkId":"trunk_main","to":"+15551234567","from":"+15558675309"}
```

**Reply:**
```json
{ "ok": true, "callId": "C-8f3a2b" }
```

The call is now *originating*. Watch for a `delivered` event, then an
`established` event, on the event track. They tell you that the call rang and
connected.

## Release a call — `clearConnection`

`clearConnection` ends a call. It maps to CSTA **Clear Connection**. Use it
to hang up a call that is ringing, connected, or held.

- **`callId`** (`string`, required) — The call to release.
- **`reason`** (`string`) — A short release reason recorded on the call (`normal`, `busy`, `rejected`,   `caller_abandon`, …). Informational; the engine releases the call   regardless.

**Request wire:**
```text
clearConnection	{"callId":"C-8f3a2b","reason":"normal"}
```

**Reply:**
```json
{ "ok": true }
```

A `cleared` event follows after both legs tear down.

## Hold and retrieve — `holdCall` / `retrieveCall`

These verbs put a connected party on hold and take them back. They map to
CSTA **Hold Call** and **Retrieve Call**. Hold plays the trunk's configured
hold treatment to the far party. Retrieve reconnects the media path.

- **`callId`** (`string`, required) — The connected call to hold or retrieve.

**Request wire — hold:**
```text
holdCall	{"callId":"C-8f3a2b"}
```

**Request wire — retrieve:**
```text
retrieveCall	{"callId":"C-8f3a2b"}
```

**Reply:**
```json
{ "ok": true }
```

Check the result with the `held` and `retrieved` events. A hold is also the
first thing that `consultationCall` does for you — see below.

## Blind transfer — `singleStepTransfer`

`singleStepTransfer` transfers a call directly to another destination. You
do not speak to the destination first. It maps to CSTA **Single Step Transfer
Call**. The engine redirects the party and drops your control of the original
leg in one step. There is no consult phase.

- **`callId`** (`string`, required) — The call to transfer away.
- **`destination`** (`string`, required) — Where to send it — an E.164 number or a routable extension/agent address.
- **`callerId`** (`string`) — Caller ID to present to the destination. Defaults to the original party's   identity when omitted.

**Request wire:**
```text
singleStepTransfer	{"callId":"C-8f3a2b","destination":"+15557654321","callerId":"+15558675309"}
```

**Reply:**
```json
{ "ok": true, "callId": "C-8f3a2b" }
```

A `transferred` event fires when the new leg is established. After a blind
transfer, you no longer control the call.

## Attended (consultative) transfer

An attended transfer is a three-verb sequence:

1. Hold the caller and dial a consult target.
2. Talk to the target.
3. **Either** complete the transfer **or** cancel and return to the caller.

This is the standard CSTA consult-then-transfer flow.

  1. **Start the consult — consultationCall (CSTA Consultation Call)**
This verb puts `callId` on hold. It then originates a new consult leg to
    `target` over a trunk. You talk privately to the target while the
    original party waits on hold. The reply returns the **consult call's** id.

    - **`callId`** (`string`, required) — The active call to consult from. The engine places it on hold.
    - **`target`** (`string`, required) — Who to consult — E.164 number or routable extension/agent address.
    - **`trunkId`** (`string`, required) — The SIP trunk to originate the consult leg over.
    - **`fromDisplay`** (`string`) — Caller ID / display name to present on the consult leg.

    
    **Request wire:**
```text
    consultationCall	{"callId":"C-8f3a2b","target":"+15557654321","trunkId":"trunk_main","fromDisplay":"Front Desk"}
    ```

    **Reply:**
```json
    { "ok": true, "consultCallId": "C-11d0e7" }
    ```

  2. **Complete it — transferCall (CSTA Transfer Call)**
This verb connects the held original party directly to the consulted
    party. It drops you out of both legs. Name the **original** `callId`. The
    engine joins it to its outstanding consult.

    - **`callId`** (`string`, required) — The original call whose consult you complete.

    
    **Request wire:**
```text
    transferCall	{"callId":"C-8f3a2b"}
    ```

    **Reply:**
```json
    { "ok": true }
    ```
    

    A `transferred` event confirms that the two parties are now connected to
    each other.

  3. **…or abandon it — cancelConsultation (CSTA Reconnect Call)**
If the consult does not work out, drop the consult leg and reconnect to
    the party that you had on hold. This is CSTA **Reconnect Call**. It clears
    the consult connection and retrieves the held original in one verb.

    - **`callId`** (`string`, required) — The original call to return to. The engine clears the consult leg and       retrieves this call from hold.

    
    **Request wire:**
```text
    cancelConsultation	{"callId":"C-8f3a2b"}
    ```

    **Reply:**
```json
    { "ok": true }
    ```
    

    You are back in conversation with the original party, exactly as before
    the consult. A `retrieved` event confirms it.

> **NOTE:**
> Use `singleStepTransfer` when you do not need to speak to the destination
>   first. Use the `consultationCall` → `transferCall` / `cancelConsultation`
>   sequence when you do. The only difference is whether there is a consult
>   phase that you can back out of.

## Send DTMF — `generateDigits`

`generateDigits` pushes DTMF digits into a call. Use it to drive a far-end
IVR, enter a conference PIN, or navigate a menu. It maps to CSTA **Generate
Digits**.

- **`callId`** (`string`, required) — The call to send digits into.
- **`digit`** (`string`, required) — The digit(s) to send: `0`–`9`, `*`, `#`, and `A`–`D`. Send one or a short   string.
- **`mode`** (`string`, default `rfc2833`) — How the digits are carried:   <br />• `rfc2833` — in-band RTP telephone-event tones (RFC 2833 / 4733). The   default and the most widely interoperable.   <br />• `info` — out-of-band SIP signalling. Use when the far end expects   signalled DTMF rather than RTP events.

**Request wire — RTP telephone-event:**
```text
generateDigits	{"callId":"C-8f3a2b","digit":"1234","mode":"rfc2833"}
```

**Request wire — out-of-band signalling:**
```text
generateDigits	{"callId":"C-8f3a2b","digit":"#","mode":"info"}
```

**Reply:**
```json
{ "ok": true }
```

> **WARNING:**
> Not every far end accepts every DTMF mode. If a downstream IVR ignores your
>   digits, try the other `mode`. Mismatched DTMF transport (RTP events vs.
>   signalled) is the usual cause.

## Errors

A refused verb replies with `ok: false` and a machine-readable `error`.
Common causes:

- The token is missing the `call_control` scope.
- The `callId` does not belong to your tenant, or it no longer exists.
- The trunk named in `makeCall` / `consultationCall` is not provisioned.

```json Reply — refused
{ "ok": false, "error": "scope_required", "detail": "call_control" }
```

> **NOTE:**
> A reply only tells you that the verb was accepted or rejected. It never
>   carries the final call state. Always drive your UI and workflow from the
>   [event track](/modalities/voice/cti/routing-events). The track delivers
>   `delivered` / `established` / `held` / `retrieved` / `transferred` /
>   `cleared` / `failed` as they happen.

## Related

  - **[CTI (CSTA) overview](/modalities/voice/cti/overview)** — Connect the control leg, mint a scoped CTI token, and learn the frame
    format that these verbs ride on.
  - **[Routing &amp; events](/modalities/voice/cti/routing-events)** — Subscribe to the CSTA event track. Register as a route point to select
    call destinations.
  - **[Agent state](/modalities/voice/cti/agent-state)** — Set login / ready / not-ready / after-call-work with `setAgentState`.
  - **[PBX &amp; ACD](/modalities/voice/transport-telephony/pbx-acd)** — See how hold, transfer, and queueing fit the contact-center routing model.
  - **[SIP trunking](/modalities/voice/transport-telephony/sip-trunking)** — Provision the trunks that `makeCall` and `consultationCall` originate
    over.
  - **[High availability](/modalities/voice/high-availability)** — CTI verbs resolve against the surviving nodes through a node failure.
