UniFiClient is a third party Ubiquiti UniFi API client, allowing you to easily build your own UniFi integrations in Rust. UniFiClient comes with two primary sets of APIs for communicating with UniFi controllers, a high level strongly typed semantic API, and a lower level HTTP API for extending behaviour.
Note: This crate is not officially associated with or endorsed by Ubiquiti Inc.
- 🔐 Secure authentication with UniFi controllers
- 🌐 Access via a convenient singleton or multiple independent clients
- 🛜 Complete guest management: authorize, list, and unauthorize guests.
- 🔄 Async API with Tokio runtime support
- 🛡️ Comprehensive error handling
- 🧪 Well-tested (unit and integration tests)
- 🧩 Extensible architecture, ready for additional API endpoints
Run this command in your terminal to add the latest version of UniFiClient.
cargo add unifi-client
The easiest way to use unifi-client is with the singleton pattern. This provides a single, globally accessible client instance.
use unifi_client::{UniFiClient, UniFiError};
#[tokio::main]
async fn main() -> Result<(), UniFiError> {
// 1. Initialize the singleton client (ONCE, at the start).
// Get your credentials securely (e.g., from environment variables).
let client = UniFiClient::builder()
.controller_url("https://your-unifi-controller:8443") // Replace!
.username("your_username") // Replace!
.password_from_env("UNIFI_PASSWORD") // Best practice
.site("default") // Or your site ID
.accept_invalid_certs(false) // Set to `true` if you don't have valid SSL certificates
.build()
.await?;
unifi_client::initialize(client);
// 2. Access the client anywhere using `unifi_client::instance()`:
let guests = unifi_client::instance().guests().list().send().await?;
println!("Guests: {:?}", guests);
Ok(())
}
The semantic API is a high level API that provides a type-safe way to
interact with the UniFi controller. It is built around a set of models
that map to the UniFi controller's API. Currently the following modules are
available.
guest
- Guest access management.
unifi-client
uses a builder pattern for constructing API requests.
All methods with multiple optional parameters are built as Builder structs, allowing you to easily specify parameters.
use unifi_client::{UniFiClient, UniFiError};
#[tokio::main]
async fn main() -> Result<(), UniFiError> {
let client = UniFiClient::builder()
.controller_url("https://your-controller:8443")
.username("your_username")
.password_from_env("UNIFI_PASSWORD")
.build()
.await?;
unifi_client::initialize(client);
let unifi_client = unifi_client::instance();
// Authorize a guest:
let new_guest = unifi_client.guests()
.authorize("00:11:22:33:44:55")
.duration_minutes(60)
.up(1024)
.down(2048)
.data_quota_megabytes(1024)
.send() // *MUST* call .send()
.await?;
println!("Authorized Guest: {:?}", new_guest);
// List guests (with optional filtering):
let guests = unifi_client.guests().list().within(24).send().await?;
// Unauthorize a guest:
unifi_client.guests().unauthorize("00:11:22:33:44:55").send().await?;
//Unathorize all guests
unifi_client.guests().unauthorize_all().send().await?;
Ok(())
}
All API methods return a Result<T, UniFiError>
.
use unifi_client::{UniFiClient, UniFiError};
#[tokio::main]
async fn main() -> Result<(), UniFiError> {
let client = UniFiClient::builder()
.controller_url("https://your-controller:8443")
.username("your_username")
.password_from_env("UNIFI_PASSWORD")
.build()
.await?;
unifi_client::initialize(client);
let result = unifi_client::instance().guests().list().send().await;
match result {
Ok(guests) => println!("Guests: {:?}", guests),
Err(UniFiError::ApiError(msg)) => eprintln!("API Error: {}", msg),
Err(UniFiError::AuthenticationError(msg)) => eprintln!("Auth Error: {}", msg),
Err(e) => eprintln!("Other Error: {:?}", e),
}
Ok(())
}
UniFiClient supports two patterns for client creation and access:
-
Builder (
UniFiClient::builder()
):
Create new, independent client instances with custom configurations.
Use when:- Connecting to multiple controllers in a single application.
- Writing tests (e.g., with wiremock) or needing isolated configurations.
- Spinning up short-lived clients for specific tasks.
-
Singleton (
initialize()
/instance()
):
Set up a global client for simplified, centralized access.
Use when:- Your app interacts with a single UniFi controller.
- You want to avoid passing client instances around.
- A uniform configuration is needed throughout your codebase.
Best Practices:
- Single Controller: Call
initialize()
early (typically inmain
) and useinstance()
anywhere else.- Note: If
initialize()
isn’t called,instance()
refers to an inert default client; any requests will return a configuration error.
- Note: If
- Multiple or Custom Instances: Use the builder to create each client independently.
default-client
(enabled by default):- Provides the global singleton (
initialize()
/instance()
), implemented with a thread-safeArc
andArcSwap
. - Starts with an inert default client (invalid URL, no credentials, cookie store enabled).
- Recommended for apps that interact with a single controller and prefer global access.
- Provides the global singleton (
Disable the global client if you want explicit dependency injection only:
[dependencies]
unifi-client = { version = "*", default-features = false }
When disabled, call UniFiClient::builder()
and pass the client handle through your application.
If you need to connect to multiple UniFi Controllers, or you need different client configurations
within the same application, create independent client instances using UniFiClient::builder()
without calling initialize()
:
use unifi_client::{UniFiClient, UniFiError};
#[tokio::main]
async fn main() -> Result<(), UniFiError> {
let client1 = UniFiClient::builder()
.controller_url("https://controller1.example.com:8443")
.username("user1")
.password("password1")
.build()
.await?;
let client2 = UniFiClient::builder()
.controller_url("https://controller2.example.com:8443")
.username("user2")
.password("password2")
.build()
.await?;
// Use client1 and client2 independently.
let guests1 = client1.guests().list().send().await?;
let guests2 = client2.guests().list().send().await?;
println!("Guests on controller 1: {:?}", guests1);
println!("Guests on controller 2: {:?}", guests2);
Ok(())
}
use unifi_client::{UniFiClient, UniFiClientBuilder};
use reqwest::Client as ReqwestClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let custom_http_client = ReqwestClient::builder()
.timeout(std::time::Duration::from_secs(60))
.redirect(Policy::none())
.cookie_store(true)
.build()?;
let client = UniFiClient::builder()
.controller_url("https://your-controller:8443")
.username("your_username")
.password_from_env("UNIFI_PASSWORD")
.http_client(custom_http_client)
.build()
.await?;
Ok(())
}
- Statistics and reporting
This library has been tested with:
- UniFi Controller version 9.x
- MSRV: Rust 1.74.0 (edition 2021)
We set rust-version = "1.74"
in Cargo.toml
. Downstream crates can remain on older editions (e.g., 2018/2021); they just need a toolchain new enough to compile this crate.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT license.
- This library is inspired by various UniFi API clients in other languages
- Thanks to the Ubiquiti community for documenting the unofficial API