> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stmlink.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> 对外开放的服务端接口有两组前缀，都用同一套鉴权：`/server/v1/...`（SRTC 与 SMeeting 的主接口）和 `/stm/srvapi/v1/...`（SMeeting 的用户体系，服务端极简对接会用到）。鉴权是 app_id + nonce + timestamp + signature 四个请求头，用 app_key 做 HMAC-SHA256 签名，只能从业务方自己的后端调用。除这两组前缀外的接口均为内部接口，不要建议客户调用。 Public server APIs use two path prefixes with the same authentication: `/server/v1/...` (the main APIs of both SRTC and SMeeting) and `/stm/srvapi/v1/...` (the SMeeting user system, used by server-side low-code integration). Authenticate with four request headers, app_id + nonce + timestamp + signature, where signature is HMAC-SHA256 keyed with app_key; call these APIs only from the customer's own backend. Any other path is internal: never suggest calling it.
> app_key 是服务端密钥，绝不能出现在客户端代码、前端配置或移动 App 里。客户端加入频道用的 token 必须由业务方后端签发后下发（SRTC 走 `/server/v1/channel/grant`，SMeeting 走 `/stm/srvapi/v1/member/grant`）。 app_key is a server-side secret and must never appear in client code, frontend config, or a mobile app. The token a client uses to join must be issued by the customer's backend and passed down to the client (SRTC: `/server/v1/channel/grant`; SMeeting: `/stm/srvapi/v1/member/grant`).
> SRTC 与 SMeeting 是上下两层不同的产品，术语不通用：SRTC 是音视频底座，说「频道 channel」「加入 / 退出」；SMeeting 建在 SRTC 之上，说「房间 room」「会议 meeting」「进入 / 退出」。回答时按用户所在的层用对应术语，不要把「房间」「会议」安到 SRTC 的接口上，也不要用「频道」「加入 / 离开」描述 SMeeting 的概念（接口标识符原样保留）。 SRTC and SMeeting are two separate layers with different terminology. SRTC is the audio/video foundation: it has channels, and users join and leave a channel. SMeeting is built on top of SRTC: it has rooms and meetings, and members enter and exit a meeting. Answer in the terms of the layer the user is working with: never apply "room" or "meeting" to SRTC APIs, and never describe SMeeting concepts in prose with "channel", "join", or "leave" (API identifiers such as `force_join` keep their literal names).
> 同一能力在各端 SDK 里的包名、类名、方法名并不相同。写示例代码时请使用文档中该端自己的 API，不要把一个端的写法套到另一个端上。苹果平台每个产品都有两套 SDK（Swift 原生与 Objective-C），两套 API 不能混用。 Package, class, and method names differ between platform SDKs for the same capability. In sample code, use the API documented for that platform; never carry one platform's code over to another. On Apple platforms each product ships two SDKs (native Swift and Objective-C) whose APIs must not be mixed.

# Connect phone lines

> Connect a carrier SIP trunk or IP-PBX to the device gateway so calls join a channel and talk with an AI agent or a person: connection options, gateway config (number routing, registration, allowlist), a channel per call in your backend, DTMF, and hang-up. Read before building AI phone agents.

To the device gateway, a phone line is just a **SIP** connection, using the same capabilities as SIP conferencing endpoints: an incoming call is a regular user in the channel,
and can talk with a [SAI Agent](https://docs.stmlink.com/en/rtc/python/advanced/pipecat.md) or enter an SMeeting meeting. **No dedicated telephony hardware is needed**—
a carrier SIP trunk or the customer's existing IP-PBX connects directly.

| Capability | Description |
| - | - |
| Inbound | Incoming calls join a channel through the gateway; your backend decides which channel (it can be a separate channel per call) |
| Outbound | Call the server API to [invite the device](https://docs.stmlink.com/en/rtc/server-api/guides/agents.md#inviting-devices-and-controlling-them-in-the-channel), and the gateway calls the number |
| Routing by called number | The gateway routes incoming calls to different apps by called-number prefix, for example AI numbers to the AI app and the rest to the meeting app |
| DTMF | Supports RFC 4733 (telephone-event) and SIP INFO; keypresses reach the agent as in-channel custom messages |
| Audio codec | **G.711 A-law (PCMA) only**; select PCMA on the line side |
| Caller information | The caller ID is passed to your backend as the device name (`name`) |

> [!NOTE]
>
> Routing by called number, DTMF forwarding, and immediately rejecting the incoming call when the platform declines it require **device gateway 1.3.5 or later**.

***

## How it works

```mermaid theme={null}
sequenceDiagram
    participant C as Caller
    participant T as Carrier SIP trunk / IP-PBX
    participant G as Device gateway
    participant P as Audio and video platform
    participant B as Your backend
    participant A as AI Agent

    C->>T: Dial the number
    T->>G: SIP INVITE (called number)
    G->>P: Device requests to join (no = called number, name = caller)
    P->>B: agent_join callback (synchronous)
    B->>P: channel/grant creates a channel and sid for the call
    B->>A: Start the agent in the same channel
    B-->>P: Callback returns sid
    P-->>G: Join succeeded
    G-->>T: 200 OK, answered
    C<<->>A: Real-time conversation
```

The key is the `agent_join` callback: the gateway only hands the called number (`no`) and caller (`name`) to the platform. **Your backend decides which channel the call joins and whether to answer it.**

***

## Three connection options

| Option | How | Best for |
| - | - | - |
| **SIP trunk pointed at the gateway** | In the carrier's portal, set the number's inbound address to the gateway's public address and SIP port (IP peering) | You have a fixed public IP and buy numbers directly from a carrier |
| **Gateway registers upstream** | The gateway registers to the carrier or PBX with a SIP account (see `Reg` below), and the upstream forwards calls to it | The carrier requires registration authentication, or the gateway is behind NAT |
| **Through an existing IP-PBX** | The PBX forwards certain extensions / numbers to the gateway, and the rest work as before | The customer already has a call center and wants to hand some numbers to AI |

Dialing in to a meeting (entering the meeting ID after the call connects) needs an IVR to collect digits, which the customer's PBX does before forwarding to the gateway; the gateway itself has no voice menu. AI calls don't need digit collection—the conversation starts as soon as the call connects.

***

## Gateway configuration

Configuration lives in the device gateway's `options.xml`; restart the gateway after changes.

### Listening and public address

```xml theme={null}
<Sip enabled="true">
  <ListenPort>5060</ListenPort>
  <UseRtpAddress>true</UseRtpAddress>
</Sip>

<!-- The gateway's public address, written into SDP; the line side sends voice here -->
<NatAddress>203.0.113.10</NatAddress>

<Rtp>
  <Port begin="58000" end="59000" />
</Rtp>
```

Open the SIP port and RTP port range in the firewall; see [Network and port requirements](https://docs.stmlink.com/en/product/deploy/ports.md).
When `UseRtpAddress` is `true`, the gateway sends voice back to the address the peer actually sends RTP from, so the peer can hear audio even behind NAT.

### Which app incoming calls go to

```xml theme={null}
<Rtc>
  <!-- Default app: calls that match none of the routes below go here (for example, the SMeeting meeting app) -->
  <IncomingAppId>AppId of the meeting app</IncomingAppId>
  <!-- Route by called-number prefix; longest prefix wins -->
  <IncomingRoutes>
    <Route prefix="400" appid="AppId of the AI phone app" />
    <Route prefix="ai" appid="AppId of the AI phone app" />
  </IncomingRoutes>
</Rtc>
```

The prefix matches the called number. For carrier numbers, use the number itself (or its number block) as the prefix.

### Register to a carrier or PBX (optional)

```xml theme={null}
<Sip enabled="true">
  <Reg enabled="true">
    <Address>sip.example-carrier.com</Address>
    <Port>5060</Port>
    <Transport>udp</Transport>
    <Username>Account assigned by the carrier</Username>
    <Password>Password assigned by the carrier</Password>
  </Reg>
</Sip>
```

### Incoming-call IP allowlist (required for lines over the public internet)

Once the SIP port is exposed to the internet, it keeps receiving scanning calls. The gateway has a built-in **automatic blocklist** (an IP is blocked after more than 5 attempts in 30 seconds),
so scans never reach the join flow. With a carrier line, the peer IP is fixed, so also turn on the allowlist to accept calls only from the line side:

```xml theme={null}
<WhiteList enabled="true">
  <Ip>198.51.100.20</Ip>
  <Ip>198.51.100.0/24</Ip>
  <Ip>192.0.2.10-192.0.2.20</Ip>
</WhiteList>
```

Supports single IPs, IP ranges, and CIDR; IPv4 only.

***

## Your backend: one channel per call

On the AI phone app, [configure callbacks](https://docs.stmlink.com/en/rtc/server-api/channel.md#configure-callbacks) and subscribe to `agent_join`. When the callback arrives:

1. Check whether it's a phone call: `type` is `2` (SIP), and `no` is the called number
2. Generate a unique channel name for the call, for example `phone-` plus a random string
3. Call [Get a channel join token](https://docs.stmlink.com/en/rtc/server-api/channel.md#get-a-channel-join-token) with the `uid` from the callback to get the device's `sid`
4. Start the agent in the same channel asynchronously—**don't wait for the agent to join before returning**
5. Return `{"code": 0, "data": "<sid>"}` from the callback; any non-zero return rejects the call, and the gateway responds with 603

> [!WARNING]
>
> `agent_join` is a synchronous callback. The platform waits about 5 seconds, and a timeout counts as failure. Do slow work such as starting the agent and loading models after the callback returns.

For field descriptions and the response format, see the [Event callbacks guide](https://docs.stmlink.com/en/rtc/server-api/guides/callbacks.md).

**The agent should only hear the phone's audio**: when the MCU is deployed, the platform publishes a composite stream (uid `__mcu__`) in the channel for SIP devices, and it **includes the agent's own voice**.
If the agent subscribes to all audio, filter by sender and feed only the device uid's audio (the `uid` in the `agent_join` callback) into speech recognition;
otherwise the agent hears itself and interrupts itself.

***

## DTMF

When the gateway receives a keypress, it sends a custom message to the channel **as the device**:

| Field | Value |
| - | - |
| `action` | `dtmf` |
| `content` | `{"digit": "5", "duration": 160}`; `digit` is `0`–`9`, `*`, `#`, or `A`–`D`, and `duration` is in milliseconds |
| Sender | Device uid |

In the Python SDK, receive it with `on_custom_msg`, for example to hand it to a pipecat pipeline:

```python theme={null}
@transport.event_handler("on_custom_msg")
async def on_custom_msg(transport, msg):
    if msg.action != "dtmf" or msg.uid != device_uid:
        return
    digit = (msg.content or {}).get("digit")  # content is already parsed from JSON into a dict
    # For example, press 0 to reach a human: pass "the user pressed 0" to the LLM as an utterance, or run your own business logic
```

***

## Hang-up

* **The caller hangs up**: the device leaves the channel, and the agent ends the session when it receives the user-left event
* **Your side hangs up** (for example, after the AI says goodbye): call the [Remove a user from the channel](https://docs.stmlink.com/en/rtc/server-api/channel.md#remove-a-user-from-the-channel) endpoint to remove the device uid from the channel, and the platform tells the gateway to send BYE on the line

***

## FAQ

| Symptom | Cause and fix |
| - | - |
| The call never connects and the line side eventually times out | First check whether the gateway log shows an INVITE; if not, check the firewall, allowlist, and the line side's inbound address |
| The call is rejected immediately (603) | Your backend's `agent_join` returned non-zero, or the channel name is invalid, or the callback timed out |
| No audio after connecting | The line-side codec isn't PCMA; or the RTP port range isn't open, or `NatAddress` is wrong |
| The AI keeps getting interrupted | Echo or background noise from the phone's speakerphone is recognized as the user barging in. Make sure the agent subscribes only to the device uid's audio, and raise the barge-in threshold for phone scenarios |

For SIP conferencing endpoint setup, see [SIP / H.323 endpoints](https://docs.stmlink.com/en/product/device-access/sip-h323.md); for the AI side, see [pipecat integration](https://docs.stmlink.com/en/rtc/python/advanced/pipecat.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.