UnifiClient

Hex.pm Docs License

Note: this library has worked well for my purposes, but is immature, and may have bugs. Please post an issue if you find any.

An Elixir client for UniFi Network and UniFi Protect — the two applications on a UniFi OS console — and for Ubiquiti's Cloud Site Manager.

One authenticated client serves both local applications: Network and Protect share a console and a login, so a session opened for one works for the other.

Network sites, devices (including PoE), clients, wireless and LAN configuration, firewall rules and port forwards, traffic statistics, live events over WebSocket
Protect cameras and snapshots, recorded events with thumbnails and heatmaps, MP4 export of recorded video, live updates over Protect's binary WebSocket
Cloud hosts, sites, devices and clients through the Site Manager API, with an API key rather than a console login

Talks to UDM Pro and other UniFi OS consoles, self-hosted controllers (type: :controller — Network only; Protect is UniFi OS), and api.ui.com.

Installation

def deps do
  [
    {:unifi_client, "~> 0.2"}
  ]
end

Requires Elixir 1.20 or newer — the version this library is built and tested on.

What you need

Network

{:ok, client} =
  UnifiClient.Client.new(
    host: "192.168.1.1",
    username: "admin",
    password: "secret",
    verify_ssl: false
  )

{:ok, client} = UnifiClient.Auth.login(client)

{:ok, sites} = UnifiClient.API.Sites.list(client)
{:ok, devices} = UnifiClient.API.Devices.list(client, "default")
{:ok, active} = UnifiClient.API.Clients.list_active(client, "default")

:ok = UnifiClient.Auth.logout(client)

A session that the console has expired is renewed and the call retried, once, without the caller seeing it — and concurrent callers renew once between them rather than each starting a login.

UNIFI_HOST=udmpro.lan UNIFI_USER=admin UNIFI_PASS=secret elixir examples/device_list.exs

UniFi Protect

Protect runs on the same UniFi OS console as Network and uses the same login, so one authenticated client serves both. (Protect is not available on a self-hosted type: :controller.)

{:ok, client} = UnifiClient.Client.new(host: "unvr.local", username: "admin", password: "secret")
{:ok, client} = UnifiClient.Auth.login(client)

{:ok, cameras} = UnifiClient.Protect.Cameras.list(client)
porch = Enum.find(cameras, &(&1["name"] == "Porch"))

# live snapshot as JPEG bytes, or straight to a file
{:ok, jpeg} = UnifiClient.Protect.Cameras.snapshot(client, porch["id"], w: 640)
{:ok, "porch.jpg"} = UnifiClient.Protect.Cameras.snapshot(client, porch["id"], dest: "porch.jpg")

Recorded video is exported as MP4, streamed straight to disk. The console renders the clip on demand, so the call waits roughly as long as the clip is long (the timeout is derived from the window; pass timeout: to override):

start = ~U[2026-09-17 08:00:00Z]
finish = DateTime.add(start, 60, :second)
{:ok, "porch.mp4"} = UnifiClient.Protect.Video.export(client, porch["id"], start, finish, "porch.mp4")

# recent motion events and their thumbnails
{:ok, events} = UnifiClient.Protect.Events.list(client, start: start, types: ["motion"])
{:ok, jpeg} = UnifiClient.Protect.Events.thumbnail(client, hd(events)["id"])

Live updates arrive over Protect's binary WebSocket as {:unifi_protect_event, %{action: action, data: data}} messages; the subscription resumes from lastUpdateId across reconnects so nothing repeats:

{:ok, _ws} = UnifiClient.Protect.WebSocket.start_link(client: client, subscriber: self())

receive do
  {:unifi_protect_event, %{action: %{"modelKey" => "camera", "id" => id}, data: %{"isMotionDetected" => true}}} ->
    IO.puts("motion on #{id}")
end

Cloud

The Site Manager API reaches consoles through Ubiquiti rather than over your LAN, and authenticates with an API key from unifi.ui.com:

{:ok, cloud} = UnifiClient.Cloud.Client.new(api_key: System.fetch_env!("UNIFI_API_KEY"))
{:ok, hosts} = UnifiClient.Cloud.Sites.list_hosts(cloud)

Examples

Twelve runnable scripts, one per public area — examples/, indexed in examples/README.md with what each shows and which credential it needs. Each is a single file you can copy out and run:

UNIFI_HOST=udmpro.lan UNIFI_USER=admin UNIFI_PASS=secret elixir examples/protect_cameras.exs

Every script takes -h/--help and prints its usage without touching the network, runs from any directory, and prints the reason and exits 1 when a console cannot be reached or a login is refused.

Documentation

The API reference is on HexDocs. spec.md is the library’s specification — what each module is for, which console endpoints it speaks to, and the rules the implementation follows.

Status

Network and Protect are both implemented and covered by the suite; spec.md marks what each module supports. The console's own API is undocumented and varies by firmware, so responses are returned as the console sends them — maps with string keys — rather than being remodelled into structs.

License

MIT — see LICENSE.