{"id":50090686,"url":"https://github.com/ecliptical/voip-ms","last_synced_at":"2026-05-29T05:00:33.202Z","repository":{"id":359662407,"uuid":"1247012364","full_name":"ecliptical/voip-ms","owner":"ecliptical","description":"Async client for the voip.ms REST API","archived":false,"fork":false,"pushed_at":"2026-05-26T12:49:35.000Z","size":278,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T03:14:29.630Z","etag":null,"topics":["async","rust","sdk","sms","telephony","voip","voipms"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ecliptical.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-22T20:01:55.000Z","updated_at":"2026-05-26T12:49:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ecliptical/voip-ms","commit_stats":null,"previous_names":["ecliptical/voip-ms"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/ecliptical/voip-ms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecliptical%2Fvoip-ms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecliptical%2Fvoip-ms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecliptical%2Fvoip-ms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecliptical%2Fvoip-ms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ecliptical","download_url":"https://codeload.github.com/ecliptical/voip-ms/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ecliptical%2Fvoip-ms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33593400,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","rust","sdk","sms","telephony","voip","voipms"],"created_at":"2026-05-22T23:05:18.566Z","updated_at":"2026-05-28T04:00:50.437Z","avatar_url":"https://github.com/ecliptical.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# voip-ms\n\n[![Crates.io](https://img.shields.io/crates/v/voip-ms.svg)](https://crates.io/crates/voip-ms/)\n[![Docs.rs](https://docs.rs/voip-ms/badge.svg)](https://docs.rs/voip-ms/)\n[![CI](https://github.com/ecliptical/voip-ms/actions/workflows/rust-ci.yaml/badge.svg)](https://github.com/ecliptical/voip-ms/actions/workflows/rust-ci.yaml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nAsync client for the [voip.ms](https://voip.ms) REST API.\n\nA thin, idiomatic Rust wrapper around every method exposed by the voip.ms\nREST endpoint (`https://voip.ms/api/v1/rest.php`). Each WSDL operation gets a\ntyped `*Params` request struct and methods on [`Client`](https://docs.rs/voip-ms/latest/voip_ms/struct.Client.html). The default method\ndeserializes into a generated `*Response` struct, and each operation also has\na `*_raw` variant that returns `serde_json::Value`.\n\n## Installation\n\n```toml\n[dependencies]\nvoip-ms = \"0.1\"\ntokio = { version = \"1\", features = [\"macros\", \"rt-multi-thread\"] }\n```\n\nBy default the crate enables `rustls` with system root certificates. To use a\ndifferent TLS backend:\n\n```toml\n# Embed Mozilla's roots (good for scratch/distroless images):\nvoip-ms = { version = \"0.1\", default-features = false, features = [\"rustls-tls-webpki-roots\"] }\n\n# Use the platform's native TLS stack:\nvoip-ms = { version = \"0.1\", default-features = false, features = [\"native-tls\"] }\n```\n\n## Authentication\n\nvoip.ms uses two pieces of credential, both of which you control entirely:\n\n* `api_username` — your account email.\n* `api_password` — a **distinct** password generated on the\n  *SOAP and REST/JSON API* page in the voip.ms customer portal.\n\nYou must also allow-list the source IP address(es) you'll be calling from on\nthat same page. This crate does not load credentials from the environment,\nfiles, or any other source — pass them when you construct the [`Client`](https://docs.rs/voip-ms/latest/voip_ms/struct.Client.html).\n\n## Usage\n\n```rust\nuse voip_ms::{Client, GetBalanceParams};\n\n#[tokio::main]\nasync fn main() -\u003e voip_ms::Result\u003c()\u003e {\n    let client = Client::new(\"you@example.com\", \"your-api-password\");\n\n    let balance = client\n        .get_balance(\u0026GetBalanceParams { advanced: Some(true) })\n        .await?;\n    println!(\"{balance:#?}\");\n    Ok(())\n}\n```\n\nEvery API method follows the same pattern: construct a `*Params` struct\n(every field is `Option\u003cT\u003e` and omitted from the request when `None`), then\ncall either:\n\n* `client.some_method(...)` for typed deserialization into the generated\n    `SomeMethodResponse` struct, or\n* `client.some_method_raw(...)` for a raw `serde_json::Value` envelope.\n\n```rust\nuse voip_ms::{Client, SendSmsParams};\n\n#[tokio::main]\nasync fn main() -\u003e voip_ms::Result\u003c()\u003e {\n    let client = Client::new(\"you@example.com\", \"your-api-password\");\n\n    let resp = client\n    .send_sms_raw(\u0026SendSmsParams {\n        did: Some(\"5551234567\".into()),\n        dst: Some(\"5557654321\".into()),\n        message: Some(\"Hello from Rust\".into()),\n        ..Default::default()\n    })\n    .await?;\n\n    println!(\"{resp:#?}\");\n    Ok(())\n}\n```\n\n### Customizing the HTTP client\n\nUse [`Client::builder`](https://docs.rs/voip-ms/latest/voip_ms/struct.Client.html#method.builder) to plug in your own `reqwest::Client` — for proxies,\ncustom timeouts, retry middleware, or anything else you'd configure on\nreqwest directly.\n\n```rust\nuse std::time::Duration;\nuse voip_ms::Client;\n\nlet http = reqwest::Client::builder()\n    .timeout(Duration::from_secs(30))\n    .build()\n    .unwrap();\n\nlet client = Client::builder(\"you@example.com\", \"api-password\")\n    .http_client(http)\n    .build()\n    .unwrap();\n```\n\n### Typed responses\n\nThe WSDL doesn't describe response shapes (all 222 operations declare the\nsame generic `arrayResponse`), so this crate generates per-method `*Response`\nstructs inferred from the official HTML docs. Each unsuffixed method returns\nits generated `*Response` type, and `*_raw` is available as an escape hatch\nwhen you want the full JSON envelope:\n\n```rust\nuse voip_ms::{Client, GetBalanceParams, GetBalanceResponse};\n\n#[tokio::main]\nasync fn main() -\u003e voip_ms::Result\u003c()\u003e {\n    let client = Client::new(\"you@example.com\", \"your-api-password\");\n\n    let resp: GetBalanceResponse = client\n    .get_balance(\u0026GetBalanceParams { advanced: Some(true) })\n    .await?;\n    if let Some(balance) = resp.balance.as_ref() {\n        println!(\"{}\", balance.current_balance.unwrap_or_default());\n    }\n\n    Ok(())\n}\n```\n\nAll fields in the generated `*Response` structs are `Option\u003cT\u003e` so unknown\nomissions or future shape drift don't fail deserialization. If you need a\nshape the generated struct doesn't capture, use `*_raw` and deserialize\nmanually, or drop down to `call` / `call_at` with your own type.\n\nFor methods where you only want a nested field, use\n[`Client::call_at`](https://docs.rs/voip-ms/latest/voip_ms/struct.Client.html#method.call_at) with a JSON pointer:\n\n```rust\nuse serde::Deserialize;\nuse voip_ms::{Client, GetDIDsInfoParams};\n\n#[derive(Debug, Deserialize)]\nstruct Did {\n    did: String,\n}\n\n#[tokio::main]\nasync fn main() -\u003e voip_ms::Result\u003c()\u003e {\n    let client = Client::new(\"you@example.com\", \"your-api-password\");\n\n    let dids: Vec\u003cDid\u003e = client\n    .call_at(\"getDIDsInfo\", \u0026GetDIDsInfoParams::default(), \"/dids\")\n    .await?;\n\n    println!(\"DID count: {}\", dids.len());\n    Ok(())\n}\n```\n\n### Running the examples\n\nThe [`examples/`](examples/) directory contains small runnable programs that\nread credentials from `VOIP_MS_USERNAME` and `VOIP_MS_PASSWORD`:\n\n```bash\nVOIP_MS_USERNAME=you@example.com \\\nVOIP_MS_PASSWORD=your-api-password \\\n    cargo run --example get_balance\n```\n\n```bash\nVOIP_MS_USERNAME=you@example.com \\\nVOIP_MS_PASSWORD=your-api-password \\\n    cargo run --example list_dids\n```\n\n```bash\nVOIP_MS_USERNAME=you@example.com \\\nVOIP_MS_PASSWORD=your-api-password \\\nVOIP_MS_FROM_DID=5551234567 \\\nVOIP_MS_TO=5557654321 \\\nVOIP_MS_MESSAGE=\"Hello from Rust\" \\\n    cargo run --example send_sms\n```\n\n`send_sms` requires a DID with SMS enabled. You can pass the message body either\nthrough `VOIP_MS_MESSAGE` or as the first argument after `--`.\n\n### Calling methods this crate hasn't been regenerated for\n\nIf voip.ms adds an API method that isn't yet in this crate, use\n[`Client::call_raw`](https://docs.rs/voip-ms/latest/voip_ms/struct.Client.html#method.call_raw) directly with a `serde`-serializable parameter set:\n\n```rust\nuse voip_ms::Client;\n\n#[tokio::main]\nasync fn main() -\u003e voip_ms::Result\u003c()\u003e {\n    let client = Client::new(\"you@example.com\", \"your-api-password\");\n\n    let resp = client\n    .call_raw(\"someBrandNewMethod\", \u0026serde_json::json!({ \"id\": 42 }))\n    .await?;\n\n    println!(\"{resp:#?}\");\n    Ok(())\n}\n```\n\n## Error model\n\nAll errors surface through [`voip_ms::Error`](https://docs.rs/voip-ms/latest/voip_ms/enum.Error.html). The three variants are:\n\n* `Error::Http` — the request failed at the transport or HTTP-status level.\n* `Error::Api(ApiStatus)` — the response was a well-formed JSON envelope but\n  the `status` field was something other than `success` (e.g.\n  `invalid_credentials`, `missing_method`, `api_not_enabled`). The wire\n  string is exposed verbatim — the set of values is per-method and not\n  stable, so consult the voip.ms documentation for the methods you use.\n* `Error::InvalidResponse` — the response was not the expected JSON envelope\n  (e.g. missing `status` field).\n\n## Development and release\n\nContributor and maintainer workflows (regeneration, verification, and release)\nare documented in [DEVELOPMENT.md](DEVELOPMENT.md).\n\nSee [AGENTS.md](AGENTS.md) for design decisions and project-specific guidance.\n\n## License\n\nLicensed under the [MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecliptical%2Fvoip-ms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fecliptical%2Fvoip-ms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fecliptical%2Fvoip-ms/lists"}