Getting Started
Add asterisk-rs to your project:
[dependencies]
asterisk-rs = "0.8"
Or pick individual protocols:
[dependencies]
asterisk-rs = { version = "0.8", default-features = false, features = ["ami"] }
Or use crates directly:
[dependencies]
asterisk-rs-ami = "0.8"
Protocols
| Protocol | Port | Transport | Crate |
|---|---|---|---|
| AMI | 5038 | TCP | asterisk-rs-ami |
| AGI | 4573 | TCP (FastAGI) | asterisk-rs-agi |
| ARI | 8088 | HTTP + WebSocket | asterisk-rs-ari |
Domain Types
Common Asterisk constants are available as typed enums in asterisk_rs_core::types:
hangup causes, channel states, device states, dial statuses, and more.
See Domain Types for the full list.
Requirements
- Rust 1.86 or newer
- tokio runtime
- a C/C++ compiler for the default non-FIPS AWS-LC-backed Rustls provider; CMake, Go, and bindgen are not required for this configuration
- A running Asterisk instance for integration
This workspace does not expose or test AWS-LC FIPS mode. A downstream FIPS configuration requires CMake and Go, and may also require bindgen plus libclang on targets without pre-generated FIPS bindings; treat that as an unsupported integration until it has its own target-specific CI proof.
Domain types
Shared Asterisk constants are modeled in
asterisk_rs_core::types.
Rustdoc is the canonical inventory of types, variants, numeric conversions, and string parsing.
Use these types when a protocol boundary has a closed or meaningfully classified value set:
HangupCausefor Q.850/Q.931 cause codes;ChannelState,DeviceState, andExtensionStatefor observed state;DialStatusandCdrDispositionfor call outcomes;PeerStatusandQueueStrategyfor peer and queue state;AgiStatusfor numeric AGI response status.
Unknown or combined wire values are preserved where the protocol requires forward compatibility. Consult each type’s rustdoc for its exact conversion behavior rather than matching a copied table.
AMI (Asterisk Manager Interface)
AMI is a TCP protocol on port 5038 for monitoring and controlling Asterisk. The client handles authentication, reconnection, and event dispatch automatically.
Quick Start
use asterisk_rs_ami::AmiClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = AmiClient::builder()
.host("10.0.0.1")
.credentials("admin", "secret")
.build()
.await?;
let resp = client.ping().await?;
println!("{resp:?}");
client.disconnect().await?;
Ok(())
}
Capabilities
- Typed actions and events for the modeled AMI surface; Asterisk 22 is the live-proven boundary
- MD5 challenge-response with configurable plaintext fallback; remote transport requires an operator-owned, versioned TLS proxy
- Automatic reconnection with re-authentication
- Filtered subscriptions – receive only events you care about
- Event-collecting actions –
send_collecting()gathers multi-event responses - Command output capture for
Response: Follows - Configurable timeouts, backoff, and event buffer size
See Connection & Authentication for setup details, Events for the event system, and API reference for links to the canonical rustdoc inventory.
Connection & Authentication
Builder
use asterisk_rs_ami::AmiClient;
use asterisk_rs_core::config::ReconnectPolicy;
use std::time::Duration;
let client = AmiClient::builder()
.host("10.0.0.1")
.port(5038)
.credentials("admin", "secret")
.timeout(Duration::from_secs(10))
.reconnect(ReconnectPolicy::exponential(
Duration::from_secs(1),
Duration::from_secs(30),
))
.event_capacity(2048)
.build()
.await?;
Authentication
The client tries MD5 challenge-response first. Plaintext fallback is enabled by
default for compatibility; set .require_challenge(true) outside an explicitly
managed TLS proxy boundary. Version 0.8 owns only the TCP AMI client, so a
non-loopback deployment must use a separately versioned and maintained TLS
proxy whose listener, certificate verification, access policy, and upgrades
are owned by the application operator.
Authentication happens automatically during build() and after every reconnect.
Reconnection
When the TCP connection drops, the background task reconnects with exponential
backoff and re-authenticates before setting the connection state to Connected.
Policies:
ReconnectPolicy::exponential(initial, max)— doubling delay with jitterReconnectPolicy::fixed(interval)— constant delayReconnectPolicy::none()— no retry.with_max_retries(n)— cap attempts
Connection State
Monitor connection health:
use asterisk_rs_core::config::ConnectionState;
let state = client.connection_state();
match state {
ConnectionState::Connected => { /* ready */ }
ConnectionState::Reconnecting => { /* waiting */ }
_ => { /* down */ }
}
Disconnect
client.disconnect().await?;
Sends a Logoff action before closing the TCP connection.
Events
AMI delivers real-time events as things happen in Asterisk. Events are parsed
into typed AmiEvent variants. See API reference for canonical rustdoc links.
Subscribing
let mut sub = client.subscribe();
while let Some(event) = sub.recv().await {
println!("{}: {}", event.event_name(), event.channel().unwrap_or("n/a"));
}
Filtered Subscriptions
Subscribe to specific event types without processing every event:
let mut hangups = client.subscribe_filtered(|e| {
e.event_name() == "Hangup"
});
while let Some(event) = hangups.recv().await {
if let AmiEvent::Hangup { channel, cause, cause_txt, .. } = event {
println!("hangup on {channel}: {cause} ({cause_txt})");
}
}
Event-Generating Actions
Actions like Status, CoreShowChannels, and QueueStatus return results
as a sequence of events. Use send_collecting to gather them:
use asterisk_rs_ami::action::StatusAction;
let result = client.send_collecting(&StatusAction { channel: None }).await?;
println!("got {} channel status events", result.events.len());
for event in &result.events {
println!(" {}", event.event_name());
}
Common Accessors
Every AmiEvent has:
event_name()— the raw event name stringchannel()— the associated channel name, if anyunique_id()— the unique channel identifier, if any
Unknown Events
Events not covered by typed variants arrive as AmiEvent::Unknown:
if let AmiEvent::Unknown { event_name, headers } = event {
println!("unhandled: {event_name}");
for (k, v) in &headers {
println!(" {k}: {v}");
}
}
AMI API reference
The public Rust API is documented in
asterisk_rs_ami. Rustdoc is the canonical
inventory of actions, events, builders, errors, and methods; this guide explains how those APIs fit
together without duplicating a generated symbol table.
Find the right API
AmiClientowns connection, authentication, subscriptions, action correlation, and shutdown.actioncontains typed AMI actions. Usesendfor one response andsend_collectingfor Asterisk actions that terminate with a completion event.AmiEventcontains modeled events and retains unknown events for forward-compatible handling.AmiResponseexposes parsed response fields and output fromResponse: Follows.
Start with connection and authentication, then use events for subscription and lag behavior. The crate examples show complete Tokio programs.
AGI (Asterisk Gateway Interface)
AGI allows external programs to control Asterisk dialplan execution. This crate implements a FastAGI TCP server that accepts connections from Asterisk and dispatches them to a handler.
Quick Start
use asterisk_rs_agi::{AgiServer, AgiHandler, AgiRequest, AgiChannel};
struct MyHandler;
impl AgiHandler for MyHandler {
async fn handle(&self, request: AgiRequest, mut channel: AgiChannel)
-> asterisk_rs_agi::error::Result<()>
{
channel.answer().await?;
channel.stream_file("hello-world", "").await?;
channel.hangup(None).await?;
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (server, _shutdown) = AgiServer::builder()
.bind("0.0.0.0:4573")
// expose only on an isolated/private FastAGI network
.allow_external_bind(true)
.handler(MyHandler)
.max_connections(100)
.build()
.await?;
server.run().await?;
Ok(())
}
FastAGI does not authenticate peers. External binds require an explicit opt-in and must be isolated with a private network, firewall allowlist, or authenticated TLS proxy. Prefer the default loopback bind when Asterisk runs on the same host.
Capabilities
- Every AGI command with typed async methods
- Handler trait using native async fn (RPITIT, no macro needed)
- Request environment parsing from Asterisk
- Configurable concurrency limits
- Optional command deadline, disabled by default for long-running operations
- Graceful shutdown via
ShutdownHandle
See FastAGI Server for server details and API reference for the canonical rustdoc command inventory.
FastAGI Server
Binding
The server binds a TCP listener and dispatches each connection to your handler.
Asterisk connects via the AGI() dialplan application:
exten => 100,1,AGI(agi://your-server:4573)
The builder defaults to 127.0.0.1:4573. FastAGI has no native peer authentication, so an external
bind is rejected unless allow_external_bind(true) is explicit. External listeners must be
isolated with a private network, firewall allowlist, or authenticated TLS proxy.
Handler Trait
pub trait AgiHandler: Send + Sync + 'static {
fn handle(&self, request: AgiRequest, channel: AgiChannel)
-> impl Future<Output = Result<()>> + Send;
}
The handler receives the AGI request (parsed environment variables from Asterisk) and a channel for sending commands back.
Request Environment
AgiRequest contains the agi_* variables sent by Asterisk at connection start:
channel name, caller ID, called extension, context, language, etc.
Channel Commands
AgiChannel provides typed async methods for every AGI command: answer, hangup,
stream_file, get_data, say_digits, record_file, database_get,
speech_create, and more. See API reference for canonical rustdoc links.
Command round trips have no deadline by default because WAIT FOR DIGIT -1, dial applications,
recording, and speech operations can wait indefinitely. Applications that need a bound can call
channel.set_command_timeout(Some(duration)); expiry poisons the channel because a late response
cannot be correlated safely. Pass None to disable the deadline again.
Concurrency
Limit concurrent connections with max_connections:
let (server, _shutdown) = AgiServer::builder()
.bind("0.0.0.0:4573")
.allow_external_bind(true)
.handler(MyHandler)
.max_connections(50)
.build()
.await?;
Graceful Shutdown
build() returns a ShutdownHandle that stops the accept loop:
let (server, shutdown) = AgiServer::builder()
.bind("0.0.0.0:4573")
.allow_external_bind(true)
.handler(MyHandler)
.build()
.await?;
// stop accepting after ctrl-c
tokio::spawn(async move {
tokio::signal::ctrl_c().await.ok();
shutdown.shutdown();
});
server.run().await?;
AGI API reference
The public Rust API is documented in
asterisk_rs_agi. Rustdoc is the canonical
inventory of channel commands, request accessors, server configuration, and errors.
Find the right API
AgiServeraccepts bounded FastAGI sessions and coordinates shutdown.AgiHandleris the application callback for one parsed request and channel session.AgiRequestprovides typed access to the FastAGI prelude.AgiChannelowns the command/response exchange. Its rustdoc lists every supported typed command and exact signature.
See FastAGI server for admission, bind, timeout, and shutdown behavior. The crate examples are the source of complete runnable server programs.
ARI (Asterisk REST Interface)
ARI provides full call control through a REST API combined with a WebSocket event stream for Stasis applications.
Cleartext HTTP/WebSocket is allowed by default only on loopback. Remote
cleartext requires .allow_insecure_remote(true); prefer .secure(true).
Private PKI deployments can add a PEM CA bundle with .private_ca_pem(...),
which augments platform trust for both HTTPS and WSS.
Quick Start
use asterisk_rs_ari::AriClient;
use asterisk_rs_ari::config::AriConfigBuilder;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = AriConfigBuilder::new("my-app")
.host("10.0.0.1")
.secure(true)
.username("asterisk")
.password("secret")
.build()?;
let client = AriClient::connect(config).await?;
let mut events = client.subscribe();
while let Some(msg) = events.recv().await {
println!("[{}] {:?}", msg.application, msg.event);
}
Ok(())
}
Capabilities
- REST/WebSocket clients for the modeled ARI surface, with Asterisk 22 as the live-proven boundary
- Typed events with metadata (application, timestamp, asterisk_id)
- Filtered subscriptions – receive only events you care about
- Resource handles for channels, bridges, playbacks, recordings
- System management – modules, logging, config, global variables
- URL-safe query encoding, HTTP timeouts, WebSocket lifecycle management
See Stasis Applications for the event model, Resources for the handle pattern, and API reference for links to the canonical rustdoc inventory.
Stasis Applications
ARI routes calls to your application via the Stasis() dialplan application:
exten => 100,1,Stasis(my-app,arg1,arg2)
Event Stream
Events arrive via WebSocket as AriMessage structs containing metadata
and a typed AriEvent:
use asterisk_rs_ari::event::{AriEvent, AriMessage};
let mut events = client.subscribe();
while let Some(msg) = events.recv().await {
println!("app={} time={}", msg.application, msg.timestamp);
match msg.event {
AriEvent::StasisStart { channel, args, .. } => {
println!("call from {} with args {:?}", channel.name, args);
}
AriEvent::StasisEnd { channel } => {
println!("call ended: {}", channel.name);
}
_ => {}
}
}
Filtered Subscriptions
let mut calls = client.subscribe_filtered(|msg| {
matches!(msg.event, AriEvent::StasisStart { .. } | AriEvent::StasisEnd { .. })
});
Event Metadata
Every AriMessage carries:
application— the Stasis app that received the eventtimestamp— ISO 8601 when the event was createdasterisk_id— unique Asterisk instance ID (for clusters)event— the typedAriEventpayload
See API reference for the canonical rustdoc event inventory.
Resources
ARI resources are managed through handle objects that bundle a resource ID with a client reference.
Handle Pattern
use asterisk_rs_ari::resources::channel::{ChannelHandle, originate, OriginateParams};
// originate a channel
let params = OriginateParams {
endpoint: "PJSIP/100".into(),
app: Some("my-app".into()),
..Default::default()
};
let channel = originate(&client, ¶ms).await?;
// wrap in a handle for operations
let handle = ChannelHandle::new(channel.id, client.clone());
handle.answer().await?;
handle.play("sound:hello-world").await?;
handle.hangup(None).await?;
Available Handles
| Handle | Resource | Key Operations |
|---|---|---|
ChannelHandle | Channel | answer, hangup, play, record, hold, mute, dtmf, dial, snoop |
BridgeHandle | Bridge | add/remove channel, play, record, moh, video source |
PlaybackHandle | Playback | control, stop |
RecordingHandle | Recording | stop, pause, mute |
Module Functions
Each resource module also provides free functions for list/get/create:
use asterisk_rs_ari::resources::channel;
use asterisk_rs_ari::resources::bridge;
let channels = channel::list(&client).await?;
let bridges = bridge::list(&client).await?;
Asterisk System
The asterisk resource module provides system management:
use asterisk_rs_ari::resources::asterisk;
let info = asterisk::info(&client, None).await?;
let pong = asterisk::ping(&client).await?;
asterisk::reload_module(&client, "res_pjsip.so").await?;
See API reference for the canonical rustdoc resource inventory.
ARI API reference
The public Rust API is documented in
asterisk_rs_ari. Rustdoc is the canonical
inventory of resources, handles, events, transports, media types, and server APIs.
Find the right API
AriClientcombines REST or unified WebSocket requests with the event stream.resourcesgroups operations by Asterisk resource. Handles bind an ID to a client for follow-up operations.pendingcontains subscribe-before-create flows for channels, bridges, and playbacks.AriEventcontains modeled Stasis events plus forward-compatible unknown-event retention.mediaownschan_websocketcontrol and audio exchange.serverowns Asterisk 22 outbound WebSocket sessions.
Use resources for the handle pattern and Stasis applications for event delivery. Exact operations and signatures belong only in rustdoc so source-module splits cannot silently stale this guide.