Skip to content

asterisk-rs

crates.iodocs.rsCIMSRVLicense

Async Rust client for Asterisk PBX. Originate calls, handle events, control channels, bridges, queues, and recordings across all three Asterisk interfaces.

  • AMI -- monitor and control Asterisk over TCP. Typed events, actions, automatic reconnection, MD5 auth.
  • AGI -- run dialplan logic from your Rust service. FastAGI server with typed async commands.
  • ARI -- full call control via REST + WebSocket. Resource handles, typed events with metadata.

Quick Example

use asterisk_rs::ami::{AmiClient,AmiEvent};#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{
tracing_subscriber::fmt::init();let client = AmiClient::builder().host("10.0.0.1").credentials("admin","secret").build().await?;// subscribe to hangup events onlyletmut hangups = client.subscribe_filtered(|e| {
e.event_name() == "Hangup"});whileletSome(event) = hangups.recv().await{ifletAmiEvent::Hangup{ channel, cause, cause_txt, .. } = event {
tracing::info!(%channel, %cause, %cause_txt,"channel hung up");}}Ok(())}

Install

Use the umbrella crate to pull in whichever protocols you need:

[dependencies]
asterisk-rs = "0.8"

Or add individual protocol crates directly:

[dependencies]
asterisk-rs-ami = "0.8"# AMI onlyasterisk-rs-agi = "0.8"# AGI onlyasterisk-rs-ari = "0.8"# ARI only

Feature Selection

The umbrella crate enables all protocols by default. To select only what you need:

[dependencies]
asterisk-rs = { version = "0.8", default-features = false, features = ["ami"] }
# or: features = ["agi"]# or: features = ["ari"]# or: features = ["ami", "ari"]

Available features: ami, agi, ari. The pbx abstraction requires ami.

Protocols

ProtocolDefault PortTransportUse Case
AMI5038TCPMonitoring, call control, system management
AGI4573TCPDialplan logic, IVR, call routing
ARI8088HTTP + WSStasis applications, full media control

Capabilities

  • Typed actions, events, and commands for the modeled protocol surface; Asterisk 22 is the repository's live-proven boundary
  • Filtered event subscriptions -- receive only what you need
  • Event-collecting actions -- send_collecting() gathers multi-event responses (Status, QueueStatus, etc.)
  • Automatic reconnection with exponential backoff, jitter, and re-authentication
  • Call tracker -- correlates AMI events into CompletedCall records (channel, duration, cause, full event log)
  • PBX abstraction -- Pbx::dial() wraps originate + OriginateResponse correlation into one async call
  • Pending resources -- ARI PendingChannel/PendingBridge pre-subscribe before REST to eliminate event races
  • Transport modes -- ARI supports HTTP (request/response) or WebSocket (bidirectional streaming)
  • Outbound WebSocket server -- AriServer accepts Asterisk 22+ outbound WS connections
  • Media channel -- low-level audio I/O over WebSocket for external media applications
  • Resource handles for ARI (ChannelHandle, BridgeHandle, PlaybackHandle, RecordingHandle)
  • Domain types for hangup causes, channel states, device states, dial statuses, and more
  • ARI event metadata (application, timestamp, asterisk_id) on every event
  • AMI command output capture for Response: Follows
  • URL-safe query encoding, HTTP timeouts, WebSocket lifecycle management
  • #[non_exhaustive] enums -- new variants won't break your code
  • Structured logging via tracing

More Examples

AMI: call tracker

use asterisk_rs::ami::AmiClient;#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{
tracing_subscriber::fmt::init();let client = AmiClient::builder().host("127.0.0.1").credentials("admin","secret").build().await?;let(tracker,mut rx) = client.call_tracker();whileletSome(call) = rx.recv().await{
tracing::info!(
channel = %call.channel,
duration = ?call.duration,
cause = %call.cause_txt,"call completed");}
tracker.shutdown();Ok(())}

AGI: IVR handler

use asterisk_rs::agi::{AgiChannel,AgiHandler,AgiRequest,AgiServer};structIvrHandler;implAgiHandlerforIvrHandler{asyncfnhandle(&self,_request:AgiRequest,mutchannel:AgiChannel)
-> asterisk_rs::agi::error::Result<()>{
channel.answer().await?;
channel.stream_file("welcome","#").await?;let response = channel.get_data("press-ext",5000,4).await?;
tracing::info!(digits = response.result,"caller input");
channel.hangup(None).await?;Ok(())}}#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{
tracing_subscriber::fmt::init();let(server, _shutdown) = AgiServer::builder().bind("0.0.0.0:4573").allow_external_bind(true).handler(IvrHandler).max_connections(100).build().await?;
server.run().await?;Ok(())}

ARI: pending channel

use asterisk_rs::ari::config::AriConfigBuilder;use asterisk_rs::ari::{AriClient,AriEvent,PendingChannel,TransportMode};use asterisk_rs::ari::resources::channel::OriginateParams;#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{
tracing_subscriber::fmt::init();let config = AriConfigBuilder::new("my-app").host("127.0.0.1").port(8088).username("asterisk").password("asterisk").build()?;let client = AriClient::connect(config).await?;// pre-subscribe before originate so no events are missedlet pending = client.channel();let params = OriginateParams{endpoint:"PJSIP/100".into(),app:Some("my-app".into()),
..Default::default()};let(handle,mut events) = pending.originate(params).await?;whileletSome(msg) = events.recv().await{match msg.event{AriEvent::StasisStart{ .. } => {
handle.answer().await?;
handle.play("sound:hello-world").await?;
handle.hangup(None).await?;}AriEvent::ChannelDestroyed{ cause_txt, .. } => {
tracing::info!(%cause_txt,"channel destroyed");break;}
_ => {}}}Ok(())}

PBX: dial and wait

use asterisk_rs::ami::AmiClient;use asterisk_rs::pbx::{DialOptions,Pbx};use std::time::Duration;#[tokio::main]asyncfnmain() -> Result<(),Box<dyn std::error::Error>>{
tracing_subscriber::fmt::init();let client = AmiClient::builder().host("127.0.0.1").credentials("admin","secret").build().await?;letmut pbx = Pbx::new(client);let call = pbx.dial("PJSIP/100","200",Some(DialOptions::new().caller_id("Rust PBX <100>").timeout_ms(30000),),).await?;
call.wait_for_answer(Duration::from_secs(30)).await?;
tracing::info!("call answered");
call.hangup().await?;ifletSome(completed) = pbx.next_completed_call().await{
tracing::info!(duration = ?completed.duration, cause = %completed.cause_txt,"call record");}Ok(())}

Documentation

Representative builder, domain-type, and command signatures from these guides are compiled by just docs-snippets. Exhaustive public API inventories belong to rustdoc.

MSRV

1.86 or newer.

License

Licensed under either of

at your option.

About

Async Rust client for Asterisk AMI, AGI, and ARI

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages