#api-client #unifi #client #ubiquiti #controller #api

unifi-client

Rust client library for UniFi Controller API

2 releases

new 0.3.2 Apr 26, 2025
0.3.1 Apr 26, 2025

#630 in Network programming

Download history 84/week @ 2025-04-20

90 downloads per month

MIT license

96KB
837 lines

unifi-client

Crates.io Documentation License CI

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.

Features

  • πŸ” 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

Installation

Run this command in your terminal to add the latest version of UniFiClient.

cargo add unifi-client

Quick Start

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
        .verify_ssl(false)  // Set to `true` if you have valid SSL
        .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(())
}

API Overview

Semantic API

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.

Authorizing a guest

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(())
}

Error Handling

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(())
}

Advanced Usage

Builder vs. Singleton: Choosing the Right Approach

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 in main) and use instance() anywhere else.
  • Multiple or Custom Instances: Use the builder to create each client independently.
  • If initialize() isn’t called, any singleton usage should gracefully return a configuration error.

Creating Multiple Clients

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(())
}

Custom HTTP Client

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))
        .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(())
}

Planned Features

  • Statistics and reporting

Compatibility

This library has been tested with:

  • UniFi Controller version 9.x

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT license.

Acknowledgements

  • This library is inspired by various UniFi API clients in other languages
  • Thanks to the Ubiquiti community for documenting the unofficial API

Dependencies

~7–18MB
~242K SLoC