---
title: AgentStream: what to use when
slug: exotel-agentstream/agentstream-what-to-use-when
createdAt: 2026-08-12T09:40:08.901Z
---

Short chooser for connecting Voice AI over [Exotel AgentStream.](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-api)

There are **two control models**:

| **Model**                           | **How call logic is defined**                                                                                                                                    | **Typical entry**                                                               |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **Flow (App Bazaar)**               | Dashboard applets (Voicebot, Stream, Connect, Passthru, Gather, Greeting)                                                                                        | Exophone → Appbazaar flow, Voicebot applet or Passthru/ Connect Applet with Url |
| **ExoML (Programmable Voice APIs)** | Your code reacts to **gRPC leg events** and issues **leg actions** (start\_stream, start\_say, start\_play, hangup, bridge, …). **No App Bazaar flow required.** | Number attached to gRPC endpoint (inbound), or POST /legs (outbound)            |

Your bot WebSocket (AgentStream) is the same either way. Choose the **telephony control plane**, not a different bot protocol.

## Quick chooser

| **You need…**                                                                                                                                                 | **Use**                                                                                                                       |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Outbound: answer → bot only                                                                                                                                   | <u>[Connect Voice AI API](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-api)</u>                                |
| Outbound: answer → bot  with custom routing logics via applets: Voicebot applet and then / IVR / greeting / DTMF / agent handoff via connect applet in Exotel | <u>[Connect Voice AI with Flow API](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-with-flow-api)</u>            |
| Outbound or inbound: full runtime control in code, per-call stream URL, parallel greeting while bot connects                                                  | <u>[ExoML / Programmable Voice APIs](https://docs.exotel.com/exotel-agentstream/programmable-voice-apis-with-agentstream)</u> |
| Inbound: dashboard flow with Voicebot applet                                                                                                                  | Assign virtual number (Exophone) to an **App Bazaar flow** that includes **Voicebot applet**                                  |
| Inbound: no flow — events to your app, you drive the call                                                                                                     | Attach virtual number to a **gRPC endpoint** (ExoML / Programmable Voice)                                                     |



## Outbound

### 1. Connect Voice AI API — simplest outbound

**Docs:** <u>[Connect Voice AI API](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-api)</u>

**What happens:** Your API dials the customer. On answer, Exotel opens bidirectional WSS to StreamUrl.

**Use when:**

- Campaigns, surveys, reminders — bot is the whole experience
- No IVR, no compliance applet, no agent transfer in Exotel
- Fixed bot URL is fine in the request (StreamUrl + StreamType=bidirectional)

**Not for:** Menus, disclosure before bot, bot→human, or per-leg programmable actions.

:::BlockQuote
Your API → dial customer → answer → AgentStream WSS ↔ bot
:::

### 2. Connect Voice AI with Flow API — outbound + App Bazaar journey

**Docs:** <u>[Connect Voice AI with Flow API](https://docs.exotel.com/exotel-agentstream/connect-voice-ai-with-flow-api)</u>

**What happens:** Same connect call, but Url points at an App Bazaar flow. After answer, applets run. When the flow hits **Voicebot** / **Stream**, Exotel opens WSS. Stream URL is configured **in the applet**, not in the API body.

**Use when:**

- Compliance greeting → bot
- IVR / DTMF → bot
- Bot → Connect (human) or Passthru (your system)
- Unidirectional Stream for assist / transcription

**Not for:** Pure “answer → bot” (use Connect Voice AI), or code-owned per-leg control (use ExoML).

:::BlockQuote
Your API → dial customer → answer → App Bazaar flow → Voicebot/Stream → WSS ↔ bot
:::

**Common flow patterns:** Greeting → Voicebot · Gather → Voicebot · Voicebot → Connect · Voicebot → Passthru

### 3. ExoML (Programmable Legs) — outbound in code

**Docs:** <u>[Programmable Voice APIs with AgentStream](https://docs.exotel.com/exotel-agentstream/programmable-voice-apis-with-agentstream)</u> · <u>[ExoML introduction](https://docs.exotel.com/voice-apis/exoml-introduction)</u>

**What happens:** You POST /legs to dial the customer, receive events on your **gRPC** leg\_event\_endpoint, then issue leg actions yourself (e.g. start\_stream, optional start\_say / start\_play).

**Use when:**

- Stream URL / routing decided per call at runtime
- Parallel filler while bot connects (sales / collections “instant feel”)
- You want application-owned call logic, not a dashboard flow

| **Scenario**                        | **Pattern**                                                                                                      |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Bot-first / tech support            | leg\_answered → start\_stream                                                                                    |
| Sales / collections / CC simulation | leg\_answered → start\_stream **+** short start\_say / start\_play → on stream\_started → stop\_say / stop\_play |

:::BlockQuote
Your API → POST /legs → gRPC events (connecting / ringing / answered)   → your app: start\_stream (+ say/play) → AgentStream WSS ↔ bot
:::

## Inbound

Inbound has **two separate paths**. Do not mix them with the same mental model.

### A. Flow-based inbound (App Bazaar + Voicebot applet)

**What happens:** Customer dials your Exophone. The number is linked to an **App Bazaar flow**. Applets execute in the dashboard-defined order. **Voicebot** opens bidirectional AgentStream to your bot.

**Use when:**

- You want visual / applet-based journeys (Voicebot, greeting, IVR, bot, agent handoff)
- Same patterns as Connect-with-Flow outbound, but the customer dials in

Customer → Exophone → App Bazaar flow → Voicebot → WSS ↔ bot

**Setup:** Procure Exophone → assign **Voice URL / flow** → include Voicebot (and optional Connect / Passthru / Gather).

### B. ExoML programmable inbound (no App Bazaar flow)

**What happens:** Customer dials your Exophone. The number is attached to a **gRPC endpoint** (Programmable Voice / Legs), **not** to an App Bazaar flow. Exotel pushes leg events to your gRPC service. **Your application is the flow** — you answer/control the leg with **leg actions** (start\_stream, say, play, gather, bridge, hangup, …).

**Use when:**

- Call logic must live entirely in code
- Per-call dynamic stream URL, A/B routing, CRM-driven branching
- Same ExoML action model as outbound Legs (inbound or outbound legs)



:::BlockQuote
Customer → Exophone (attached to gRPC) → gRPC leg events → your app   → leg actions (start\_stream, say/play, …) → AgentStream WSS ↔ bot
:::



**Setup:** Implement gRPC event endpoint → register it → attach Exophone to that gRPC endpoint → on inbound events, issue leg actions.

ExoML inbound does **not** require a Voicebot applet or App Bazaar flow. The programmable “flow” is the sequence of leg actions your service sends in response to gRPC events.

## Side-by-side

|                                   | **Connect Voice AI** | **Connect + Flow**     | **ExoML (Legs)**                                   | **Inbound Flow + Voicebot** | **Inbound ExoML**           |
| --------------------------------- | -------------------- | ---------------------- | -------------------------------------------------- | --------------------------- | --------------------------- |
| Direction                         | Outbound             | Outbound               | Outbound **and** inbound                           | Inbound                     | Inbound                     |
| Control plane                     | Single connect API   | App Bazaar applets     | gRPC events + leg actions                          | App Bazaar applets          | gRPC events + leg actions   |
| App Bazaar flow                   | **No**               | Yes                    | **No**                                             | Yes                         | **No**                      |
| Where WSS URL is set              | API StreamUrl        | Voicebot/Stream applet | start\_stream action                               | Voicebot/Stream applet      | start\_stream action        |
| Parallel greeting while bot warms | Bot-owned            | Flow Play applet       | start\_say / start\_play + stop on stream\_started | Bot-owned                   | Same ExoML say/play pattern |
| Best for                          | Simple outbound bot  | Multi-step outbound    | Code-first, dynamic control                        | Dashboard inbound journeys  | Code-first inbound          |



## Decision tree

:::BlockQuote
Who starts the call?
│
├─ Customer dials (inbound)
│   ├─ Want dashboard applets (Voicebot,Passthru, Connect)?
│   │    └─ Exophone → App Engine flow → Voicebot
│   └─ Want logic in your app (no flow)?
│        └─ Exophone → gRPC endpoint → leg actions (ExoML)
│
└─ You dial (outbound)
&#x20;   ├─ Bot only, fixed StreamUrl?
&#x20;   │    └─ Connect Voice AI API
&#x20;   ├─ Need Exotel flow (Voicebot applet/Passthru / agent handoff)?
&#x20;   │    └─ Connect Voice AI with Flow API
&#x20;   └─ Need gRPC + leg actions / dynamic stream / parallel say-play?
&#x20;        └─ ExoML Programmable Legs API
:::

## Same bot, four front doors

| **Entry**        | **Who dials**         | **Control**        | **Who configures WSS** |
| ---------------- | --------------------- | ------------------ | ---------------------- |
| Connect Voice AI | Your API              | Connect API        | StreamUrl in API       |
| Connect + Flow   | Your API              | App Bazaar         | Voicebot/Stream applet |
| ExoML outbound   | Your API (POST /legs) | gRPC + leg actions | start\_stream body     |
| Inbound Flow     | Customer              | App Bazaar         | Voicebot/Stream applet |
| ExoML Inbound    | Customer              | gRPC + leg actions | start\_stream body     |



## References

- <u>[Connect Voice AI API](docId\:oWtjaGRpfWe8dJHjIQSlI)</u>
- <u>[Connect Voice AI with Flow API](docId\:YKd82KkkOVAVq4vrngHpe)</u>
- <u>[Programmable Voice APIs with AgentStream](docId\:iP28BOjsUUU9kz1Pu1Miv)</u>
- <u>[ExoML — Introduction](docId\:Ju_t9ACidAdHKkOAfYdWp)</u>
- <u>[AgentStream developer guide](https://developer.exotel.com/docs/agentstream/developer-guide)</u>
- <u>[Implementing Your gRPC Endpoint for Real-Time Call Events](docId\:ah1nMJ4tFRVxBU6FHHzEB)</u>

