Skip to main content

voip_ms/
lib.rs

1//! Async client for the [VoIP.ms](https://voip.ms) REST API.
2//!
3//! # Quick start
4//!
5//! ```no_run
6//! use voip_ms::{Client, GetBalanceParams, GetBalanceResponse};
7//!
8//! # async fn run() -> voip_ms::Result<()> {
9//! let client = Client::new("you@example.com", "your-api-password");
10//! let balance: GetBalanceResponse = client
11//!     .get_balance(&GetBalanceParams { advanced: Some(true) })
12//!     .await?;
13//! println!("{balance:#?}");
14//! # Ok(()) }
15//! ```
16//!
17//! # Design
18//!
19//! Every VoIP.ms API method gets a typed `*Params` request struct (with all
20//! fields wrapped in [`Option`] and skipped when `None`) and a method on
21//! [`Client`]. The default method deserializes into a generated `*Response`
22//! struct; each generated method also has a `*_raw` variant that returns
23//! [`serde_json::Value`]. The
24//! crate ships a generated `*Response` struct per method (e.g.
25//! `GetBalanceResponse`, `GetDIDsInfoResponse`) inferred from the official
26//! API documentation's example output, so default calls can deserialize
27//! into a known shape without callers writing their own structs.
28//!
29//! # Authentication
30//!
31//! VoIP.ms uses an `api_username` (your account email) and an `api_password`
32//! that is **distinct** from your portal password — generate it under the
33//! "SOAP and REST/JSON API" page in the customer portal and enable API access
34//! there.
35//!
36//! ## IP allow-listing
37//!
38//! By default **no IP address** may consume the VoIP.ms API. Under
39//! "Main Menu" → "SOAP & REST/JSON API" in the portal, add the IP address(es)
40//! you'll call from and save. The portal accepts individual addresses, CIDR
41//! ranges, wildcard forms (`192.168.1.*`), and DNS names. The sole exception
42//! is `getIP` ([`Client::get_ip`]), which works without an allow-listed IP so
43//! you can discover the address to add.
44//!
45//! # Wire format
46//!
47//! All calls are HTTP `GET` against the REST endpoint ([`DEFAULT_BASE_URL`],
48//! `…/api/v1/rest.php`) with parameters in the query string. That endpoint
49//! returns the `{ "status": ... }` JSON envelope directly, which this crate
50//! deserializes -- a status other than `success` surfaces as [`Error::Api`],
51//! except an empty-collection status ([`ApiStatus::is_empty`], e.g. `no_sms`),
52//! which the typed methods return as an empty response (the `*_raw` methods
53//! still surface it verbatim). (The generic `…/api/v1/` endpoint instead
54//! defaults to `text/html` and needs an explicit `content_type=json`; this
55//! crate does not use it.)
56
57mod client;
58mod error;
59mod generated;
60mod responses;
61mod types;
62
63pub use client::{Client, ClientBuilder, DEFAULT_BASE_URL};
64pub use error::{Error, Result};
65pub use generated::*;
66pub use types::{Routing, RoutingParseError, Seconds, WaitTime};
67
68// Dependencies whose types appear in this crate's public API. Re-exported so
69// callers can name those types (and `match` on [`Error::Http`]) without adding
70// a separate, independently-versioned dependency of their own.
71pub use {chrono, reqwest, rust_decimal, serde_json};