voip-ms
Async client for the voip.ms REST API.
A thin, idiomatic Rust wrapper around every method exposed by the voip.ms
REST endpoint (https://voip.ms/api/v1/rest.php). Each WSDL operation gets a
typed *Params request struct and a method on [Client]; responses come
back as serde_json::Value so callers can pick the fields they need or
[serde_json::from_value] into a struct of their own.
Installation
[]
= "0.1"
= { = "1", = ["macros", "rt-multi-thread"] }
By default the crate enables rustls with system root certificates. To use a
different TLS backend:
# Embed Mozilla's roots (good for scratch/distroless images):
= { = "0.1", = false, = ["rustls-tls-webpki-roots"] }
# Use the platform's native TLS stack:
= { = "0.1", = false, = ["native-tls"] }
Authentication
voip.ms uses two pieces of credential, both of which you control entirely:
api_username— your account email.api_password— a distinct password generated on the SOAP and REST/JSON API page in the voip.ms customer portal.
You must also allow-list the source IP address(es) you'll be calling from on
that same page. This crate does not load credentials from the environment,
files, or any other source — pass them when you construct the [Client].
Usage
use ;
async
Every API method follows the same pattern: construct a *Params struct
(every field is Option<T> and omitted from the request when None), then
call either:
client.some_method(...)for a rawserde_json::Value, orclient.some_method_typed::<T>(...)for typed deserialization.
use ;
# async
Customizing the HTTP client
Use [Client::builder] to plug in your own reqwest::Client — for proxies,
custom timeouts, retry middleware, or anything else you'd configure on
reqwest directly.
use Duration;
use Client;
let http = builder
.timeout
.build
.unwrap;
let client = builder
.http_client
.build
.unwrap;
Typed responses
The WSDL doesn't describe response shapes (all 222 operations declare the
same generic arrayResponse), so this crate intentionally hands back
serde_json::Value by default. To reduce boilerplate, every generated method
also has a typed variant named *_typed:
use ;
# async
For methods where you only want a nested field, use
[Client::call_typed_at] with a JSON pointer:
use Deserialize;
use ;
# async
The crate also includes starter partial response types that keep unknown
fields in extra, such as GetBalanceResponse and GetDidsInfoResponse.
Running the examples
The examples/ directory contains small runnable programs that
read credentials from VOIP_MS_USERNAME and VOIP_MS_PASSWORD:
VOIP_MS_USERNAME=you@example.com \
VOIP_MS_PASSWORD=your-api-password \
Available examples: get_balance, list_dids, send_sms.
Calling methods this crate hasn't been regenerated for
If voip.ms adds an API method that isn't yet in this crate, use
[Client::call] directly with a serde-serializable parameter set:
# async
Error model
All errors surface through [voip_ms::Error]. The three variants are:
Error::Http— the request failed at the transport or HTTP-status level.Error::Api(ApiStatus)— the response was a well-formed JSON envelope but thestatusfield was something other thansuccess(e.g.invalid_credentials,missing_method,api_not_enabled). The wire string is exposed verbatim — the set of values is per-method and not stable, so consult the voip.ms documentation for the methods you use.Error::InvalidResponse— the response was not the expected JSON envelope (e.g. missingstatusfield).
Regenerating the API surface
The 222 typed request structs and Client methods are generated from
tools/server.wsdl by the xtask workspace member
(xtask/src/main.rs). To pick up new methods after
voip.ms updates the WSDL:
# Download the latest WSDL from voip.ms:
# Then regenerate and verify:
See AGENTS.md for the design notes behind the generator.
Releasing
Publishing is automated via .github/workflows/release.yaml.
- Ensure
Cargo.tomlhas the target version (for the first release:0.1.0). - Move release notes from
Unreleasedinto a versioned section inCHANGELOG.md. - Push a tag in the form
vX.Y.Z.
On tag push, the workflow verifies the tag version matches Cargo.toml, runs
fmt/clippy/tests, performs cargo publish --dry-run, publishes to crates.io
using CRATES_IO_TOKEN, and creates a GitHub release.
License
Licensed under the MIT license.