# PBX / ACD Integration

> Peer with an existing PBX over SIP, or route calls through the built-in ACD with skills, queues, and VDN vectors.

You have two ways to put agents on calls without a change to your phone system.
The first way: **peer with your PBX**. Keep Asterisk or FreeSWITCH answering
the PSTN, and hand selected calls to FirstByte Voice over a plain SIP trunk.
The second way: **route through the built-in ACD**. Let the platform own the
number, walk callers through an IVR vector, and distribute calls to skill
queues. Those queues can mix AI agents with human agents on browser softphones
or real SIP deskphones.

Pick the first way when you migrate incrementally and the PBX stays the system
of record. Pick the second way when you want the platform to be the
contact-center brain end to end.

> **NOTE:**
> The SIP signalling plane is an internal B2BUA with its own built-in registrar.
> There is **no separate SIP proxy or registrar service to run**. Your PBX talks
> to it as an ordinary SIP peer.

## Option A — Peer with a tenant PBX over SIP

The internal realm reaches your PBX as a SIP peer. You define a trunk whose
`realm` is `internal`. You point it at your PBX's SIP address. You then route
calls across it in either direction. Media is G.711 (PCMU/PCMA) on the wire.
The engine decodes it to the runtime's PCM16 bus and transcodes it back. Any
PBX that speaks standard SIP + RTP interoperates.

  1. **Create an internal-realm trunk to the PBX**
Add a trunk that matches on the PBX's source IP (or a DID map) and carries
    its SIP/RTP addressing. Set `realm: "internal"` so the engine treats the
    trunk as a trusted peer, not as a carrier edge. Internal-realm INVITEs
    bypass the registrar ACL. Trunks are sealed at rest and hydrated to the
    engine. See
    [SIP Trunking](/modalities/voice/transport-telephony/sip-trunking) and the
    full field list in [admin trunks](/admin/trunks).

    ```jsonc
    // AddTrunk DTO (abridged) — a peer link to an on-prem Asterisk/FreeSWITCH
    {
      "trunk_id": "pbx-hq",
      "direction": "both",
      "realm": "internal",
      "gateway_ip": "10.20.0.10",          // the PBX SIP address
      "internal_sip_ip": "10.20.0.2",
      "internal_rtp_ip": "10.20.0.2",
      "codec_preferences": ["PCMU", "PCMA", "OPUS"],
      "inbound_rule": 3,                    // HANDLE_AI: hand the leg to an agent
      "agent_id": "support-agent"
    }
    ```

  2. **Send calls from the PBX to an agent**
In your PBX dialplan, route the extensions or DIDs that you want AI-handled
    to the trunk. In Asterisk, that is a `Dial(PJSIP/${EXTEN}@pbx-hq)`. In
    FreeSWITCH, that is a `bridge` to the gateway. The INVITE lands on the SIP
    gateway, which resolves the trunk by source IP. Because `inbound_rule` is
    `HANDLE_AI`, the gateway starts the bound agent and answers with early
    media. The answer sequence is 100 Trying → 183 with SDP → 200 with the same
    SDP. An SDP-less 180 makes some carriers drop the call, so the gateway
    always offers early media.

  3. **Send calls from an agent back to the PBX**
Outbound originations and transfers can target the same trunk. An AI
    agent can therefore hand a caller to a PBX extension — a human on a
    deskphone that the PBX already manages. Use the SDK's `Calls.originate`
    with the `pbx-hq` `trunk_id`, or use an in-call transfer. The platform
    sends a blind or attended transfer as SIP `REFER`.

  
    ```ts
    import { Voice } from "@clutchcall/agents";

    const voice = new Voice({ apiKey: process.env.CLUTCHCALL_API_KEY });

    // Ring a PBX extension across the peer trunk and attach an agent.
    const call = await voice.calls.originate({
      trunkId: "pbx-hq",
      to: "sip:2001@10.20.0.10",   // extension on the tenant PBX
      app: "AI_BIDIRECTIONAL_STREAM",
      agentId: "support-agent",
    });

    // Later, transfer the live leg to a human extension via SIP REFER.
    await call.transfer({ to: "sip:2002@10.20.0.10" });
    ```
  
  
    ```python
    from clutchcall.voice import Voice
    import os

    voice = Voice(api_key=os.environ["CLUTCHCALL_API_KEY"])

    call = voice.calls.originate(
        trunk_id="pbx-hq",
        to="sip:2001@10.20.0.10",
        app="AI_BIDIRECTIONAL_STREAM",
        agent_id="support-agent",
    )

    call.transfer(to="sip:2002@10.20.0.10")
    ```
  

> **NOTE:**
> Inbound peer `REFER` (RFC 3515) is acknowledged (auto-202), and it emits a
> `transferred` lifecycle event. But **execution of the outbound leg from a
> PBX-originated REFER is only partially implemented**. Platform-side
> operator-initiated transfer (the SDK `transfer` call above) is fully wired.
> Drive transfers from the platform side for now.

> **TIP:**
> The engine is itself a FreeSWITCH-style modular core: an internal registrar,
> dialplan, and B2BUA modeled on the FreeSWITCH internal profile. A full cutover
> is therefore usually just a re-point of trunks and agents. There is no proxy to
> migrate. See
> [Migrate from Asterisk / FreeSWITCH](/modalities/voice/migration/from-asterisk-freeswitch).

## Option B — Route through the built-in ACD

The ACD is Avaya-shaped. A **VDN** maps an inbound number to an entry point. A
**vector** runs an IVR program. **Skill queues** distribute matched calls to
agents. The ACD works for AI agents, human agents, or a warm AI→human handoff
in the same queue.

### The routing objects

  - **VDN** — Maps a DID to a routing entry point, plus a `default_skill_id` /
    `default_hotline_id`. An inbound number lands here before any IVR.
  - **Vector** — The IVR program: an ordered list of steps —
    `announcement`, `wait`, `collect_digits`, `check_skill`, `queue_to_skill`,
    `route_to_vdn`, `goto_step`, `busy`, `disconnect`.
  - **Skill** — A competency tag. Agents are members of skills. The ACD queues calls to a
    skill, and the picker matches the best available agent.
  - **Queue / callback** — A queued request carries a `queue_priority` (1–4). It moves through
    `queued → matched → accepted → completed`, or ends at `abandoned` /
    `expired`.

### Wire up a queue

  1. **Define a VDN for the number**
Point the DID at a VDN with a default skill. Manage VDNs, vectors, skills,
    and hotlines from the Contact Center console at `agent.firstbyte.fast`. The
    corresponding admin API routers back the same objects.

  2. **Author the vector (IVR)**
Sequence the caller experience: play an announcement, optionally
    `collect_digits`, then `queue_to_skill`. The prompt-playback service
    renders announcements from pre-recorded 8 kHz prompt clips. There is no
    TTS or GPU in the IVR path, so prompts are deterministic and low-latency.

    ```jsonc
    // dim_vector_step (abridged) — greet, gather, queue
    [
      { "kind": "announcement",   "prompt": "welcome" },
      { "kind": "collect_digits", "max_digits": 1, "timeout_ms": 5000 },
      { "kind": "check_skill",    "skill_id": "billing", "on_no_agents": 4 },
      { "kind": "queue_to_skill", "skill_id": "billing", "priority": 2 }
    ]
    ```

  3. **Choose a distribution algorithm**
The picker supports Avaya-style algorithms: `ead_mia` (expected-agent-delay
    with most-idle-agent, the default), `mia`, `ucd` (round-robin), `loa`,
    `lar`, and `manual`. Set the algorithm per skill.

  4. **Let agents accept offered calls**
The ACD offers a queued call to a picked agent. The agent accepts, and the
    media bridges. Browser softphone agents accept over their control channel.
    SIP deskphone agents are rung with an INVITE. The accept signal is unified
    across both endpoint types. Unaccepted offers re-queue (RONA) with a
    first-wins glare guard.

### Human agent endpoints

An agent can be a **browser softphone**, a **real SIP deskphone/softphone**, or
a **PSTN callback**. SIP-phone agents REGISTER to the internal registrar with
their AOR credentials (sealed at rest). On an ACD claim, the gateway looks up
the registration, sends the INVITE, and bridges RTP on the caller's own shard.
The ring strategy is `all` or `first`. The gateway transcodes PCMU↔PCMA as
needed. This is how the ACD puts a call on a deskphone that your PBX would
otherwise own — without the PBX in the path. See
[Number Provisioning](/modalities/voice/transport-telephony/number-provisioning)
for the per-tenant `<workspace-id>.sip.firstbyte.fast` domain that agents
register against.

### Let an AI agent hand off into a queue

A tool bridges the two worlds. An AI agent in the runtime can call the
**route-to-skill** telephony tool to place the live caller into an ACD queue
for a human. This is the platform's warm-handoff pattern for regulated
verticals: the LLM does intake, qualification, and compliance capture, then
routes to a human before audio cuts in.

```jsonc
// Agent tool config — expose a live human-queue transfer to the model
{
  "type": "telephony",
  "name": "route_to_skill",
  "skill_id": "billing",
  "priority": 1
}
```

Under the hood, this posts to the ACD queue endpoint. When a human accepts, the
AI leg's egress pauses, and the caller is mirrored to the human agent.
[AI-to-Human Handoff](/modalities/voice/transport-telephony/ai-human-handoff)
covers the mechanics of pausing the AI leg and bridging the human. Tool
definitions live in [Tool Calling](/modalities/voice/runtime/tool-calling).

> **NOTE:**
> The ACD data model, picker algorithms, queue/accept flow, and SIP-phone agent
> endpoints are shipped. The DID **"buy a number" catalog** is currently a demo
> stub. Import existing numbers, or point real DIDs from your carrier / PBX. Do
> not rely on the in-console number search. See
> [Number Provisioning](/modalities/voice/transport-telephony/number-provisioning).

## Which one should you use?

  - **Peer with your PBX** — The PBX still answers the PSTN and owns extensions and voicemail. You hand
    selected calls to agents over a SIP trunk. You can transfer calls back to
    PBX extensions. This is the lowest-friction way to add AI to an existing
    deployment.
  - **Built-in ACD** — The platform owns the number, IVR, queues, and agents (AI + human). Use it
    when you want the contact-center brain — skills, VDN vectors, and warm
    AI→human handoff — to live here.

## Related

  - **[SIP Trunking](/modalities/voice/transport-telephony/sip-trunking)** — Trunk fields, realms, and carrier interop.
  - **[AI-to-Human Handoff](/modalities/voice/transport-telephony/ai-human-handoff)** — How the live-call handoff bridges audio.
  - **[Migrate from Asterisk / FreeSWITCH](/modalities/voice/migration/from-asterisk-freeswitch)** — Full cutover: re-point trunks and agents.
