# CSTA Routing & Events

> Register your app as a route point to steer calls ACD-style, and subscribe to the CSTA event stream to track call state.

Two things make a CTI app more than a remote control. It can **decide where
calls go**, and it can **see what calls are doing**. This page covers both.
First, register your app as a **route point**, so the engine asks *you* for a
destination on each inbound call. Second, subscribe to the **CSTA event
stream**, so you observe the real call-state changes that your verbs (and
everyone else's) produce.

Both build on a connected CTI leg. If you have not connected yet, start with
the [CTI overview](/modalities/voice/cti/overview). You need a session
authenticated with the **`routing`** scope for `routeRegister`. Any
authenticated CTI leg can subscribe to the event stream.

## Verbs change state; events report it

The CSTA verb API is **request/reply**. You send a verb (`holdCall`,
`singleStepTransfer`, `makeCall`, …). The engine replies on the same
correlated line to say that it **accepted** the request. That reply is an
acknowledgement, not the outcome. The actual hold, connect, or clear is an
**asynchronous state change**. The engine reports it to you as an **event**
on the `cti/<tenant>` stream. It reports the same event to every other
observer of that tenant.

> **NOTE:**
> Treat the verb reply as "the engine took my request". Treat the event as
>   "the call reached this state." A `holdCall` reply tells you that the engine
>   accepted the hold. The matching `held` event tells you that the call is now
>   held. Drive your app state off the **events**, not the verb replies.

This split is why the event stream matters even for a pure call-control app.
Transfers complete, callers hang up, and consult legs answer without any verb
of yours. Only the event stream tells you.

## The CSTA event stream

CSTA events ride a durable per-tenant MoQT track, `cti/<tenant>`. Your SDK
subscribes to it **directly** over the same connection as the rest of the
SDK. The track is durable, so a briefly disconnected client resumes without
loss of the events that it missed. Every observer of a tenant sees the same
ordered stream.

Each event names a **kind**. It carries the identifiers that you need to
correlate it back to a call and connection:

| Event kind    | CSTA meaning                | Fires when                                             |
| ------------- | --------------------------- | ------------------------------------------------------ |
| `delivered`   | Delivered                   | A call is offered/ringing at a device.                 |
| `established` | Established                 | A call is answered and the media path is up.           |
| `held`        | Held                        | A connection is placed on hold (`holdCall`).           |
| `retrieved`   | Retrieved                   | A held connection is taken back (`retrieveCall`).      |
| `transferred` | Transferred                 | A transfer completes and the call re-homes to the destination. |
| `cleared`     | Connection Cleared          | A connection (or the whole call) is released/hung up.  |
| `failed`      | Failed                      | A call attempt or operation could not complete.        |

> **NOTE:**
> Each event carries at least the `callId` that it concerns. You can then
>   correlate it to the verb that you issued or to the route that you selected.
>   Events also carry a CSTA cause. The cause lets you distinguish, for
>   example, a normal `cleared` from an abandoned one. The exact field layout
>   is documented in the [CTI overview](/modalities/voice/cti/overview). The
>   examples below key only off `kind` and `callId`, which are always present.

## Register as a route point

A **route point** (a CSTA *route dialog*) inverts control. Instead of you
telling the engine what to do with a known call, the engine asks **you**
where an inbound call should go, and it waits for your answer. This is how
you implement **ACD-style third-party routing** in your own app:
skills-based selection, data-directed routing, or custom queueing. The engine
keeps ownership of the media and signalling.

You claim that role with **`routeRegister`**, which needs the **`routing`**
scope:

- **`timeoutMs`** (`number`, required) — Per-request decision deadline. When the engine hands you a routing request,   you have this long to select a destination. If you do not answer in time,   the engine stops the wait and applies its default handling for that call.   A slow or stuck route point therefore never wedges an inbound call.

- **`ttlSec`** (`number`, required) — Lease duration for the registration itself. The engine treats your app as   the route point for this many seconds. Renew the lease: call   `routeRegister` again before it expires. If your app dies without renewal,   the lease lapses. Routing then falls back to default handling instead of a   hang on a dead route point.

After registration, routing requests for the tenant arrive on the **same
`cti/<tenant>` event stream** that you already subscribe to. For each
request, inspect the call (caller, dialed number, attached data). Then answer
with the destination: an agent, a queue/skill, a number, or a trunk leg.
Reject the request, or let it time out, to fall through to default handling.

> **WARNING:**
> A route point is a **live decision loop**, not fire-and-forget. Renew the
>   registration within `ttlSec`. Answer every request within `timeoutMs`. A
>   route point that stalls silently degrades every inbound call to default
>   routing until its lease expires.

> **NOTE:**
> The wire verb that returns your chosen destination is part of the CSTA
>   route dialog. It is documented with the full verb schema in the
>   [CTI overview](/modalities/voice/cti/overview). The loop below shows where
>   that reply goes. Substitute the exact select verb and its argument shape
>   from the reference for your SDK version.

## Subscribe and run the route loop

The pattern is:

1. Connect and authenticate the CTI leg (see the
   [overview](/modalities/voice/cti/overview)).
2. Subscribe to the `cti/<tenant>` track.
3. Call `routeRegister` once to claim the route point.
4. Loop over events. Answer routing requests and react to call-state changes.

Frames are the correlated `<verb>\t<args-json>` lines described in the
overview.

**TypeScript:**
```ts
import { CtiSession } from "clutchcall_connect/cti";

// A connected, authenticated CTI leg (see the CTI overview).
// Token must carry the `routing` scope for routeRegister.
const cti = await CtiSession.connect({
  host: "engine.firstbyte.fast",
  token: ctiToken,        // short-lived, control-plane-minted, scopes: ["routing"]
  tenant: workspaceId,
});

// 1) Subscribe the per-tenant CSTA event stream.
const events = cti.subscribeEvents(); // async iterator over cti/<tenant>

// 2) Claim the route point. Renew before the lease expires.
await cti.send("routeRegister", { timeoutMs: 4000, ttlSec: 300 });
const renew = setInterval(
  () => cti.send("routeRegister", { timeoutMs: 4000, ttlSec: 300 }),
  240_000, // renew comfortably inside ttlSec
);

// 3) One loop for routing decisions AND state observation.
for await (const ev of events) {
  switch (ev.kind) {
    case "route": {
      // The engine is asking where this call should go.
      const dest = await pickDestination(ev); // your ACD logic
      // Answer with the destination on the same correlated request.
      await cti.selectRoute(ev, { destination: dest });
      break;
    }
    case "delivered":
      console.log("ringing", ev.callId);
      break;
    case "established":
      console.log("answered", ev.callId);
      break;
    case "held":
    case "retrieved":
    case "transferred":
      console.log(ev.kind, ev.callId);
      break;
    case "cleared":
      console.log("hung up", ev.callId, ev.cause);
      break;
    case "failed":
      console.warn("failed", ev.callId, ev.cause);
      break;
  }
}

clearInterval(renew);
```

**Python:**
```python
from clutchcall-agents.cti import CtiSession

# A connected, authenticated CTI leg (see the CTI overview).
# Token must carry the `routing` scope for routeRegister.
cti = await CtiSession.connect(
    host="engine.firstbyte.fast",
    token=cti_token,        # short-lived, control-plane-minted, scopes: ["routing"]
    tenant=workspace_id,
)

# 1) Subscribe the per-tenant CSTA event stream.
events = cti.subscribe_events()  # async iterator over cti/<tenant>

# 2) Claim the route point, then renew before the lease expires.
await cti.send("routeRegister", {"timeoutMs": 4000, "ttlSec": 300})

async def renew():
    while True:
        await asyncio.sleep(240)  # comfortably inside ttlSec
        await cti.send("routeRegister", {"timeoutMs": 4000, "ttlSec": 300})

asyncio.create_task(renew())

# 3) One loop for routing decisions AND state observation.
async for ev in events:
    if ev.kind == "route":
        dest = await pick_destination(ev)          # your ACD logic
        await cti.select_route(ev, destination=dest)
    elif ev.kind == "delivered":
        print("ringing", ev.call_id)
    elif ev.kind == "established":
        print("answered", ev.call_id)
    elif ev.kind in ("held", "retrieved", "transferred"):
        print(ev.kind, ev.call_id)
    elif ev.kind == "cleared":
        print("hung up", ev.call_id, ev.cause)
    elif ev.kind == "failed":
        print("failed", ev.call_id, ev.cause)
```

> **NOTE:**
> `subscribeEvents` / `subscribe_events` and `selectRoute` / `select_route`
>   above stand in for your SDK version's exact names. The protocol fixes these
>   parts: the `routeRegister` verb, its `{ timeoutMs, ttlSec }` arguments, the
>   `cti/<tenant>` track, and the event kinds. Check the SDK method names
>   against the CTI reference before you ship.

## Related

  - **[CTI (CSTA) overview](/modalities/voice/cti/overview)** — Connect, authenticate, read the scopes, and see the full event/verb
    schema.
  - **[CSTA call control](/modalities/voice/cti/call-control)** — Use the verbs that change call state: make, hold/retrieve, transfer,
    consult, clear.
  - **[CSTA agent state](/modalities/voice/cti/agent-state)** — Log agents in and out. Set ready / not-ready so routing has targets.
  - **[High availability](/modalities/voice/high-availability)** — Routing and CTI control continue to resolve through a node failure.
