Authentication
API key, credentials, hybrid, and cloud auth modes explained
Unifly supports four authentication modes. The right choice depends on whether you need live WebSocket streams, and whether you reach the controller directly or through Ubiquiti’s cloud.
🔗Which Mode Do I Need?
flowchart TD
START["What do you need?"] --> Q0{"Direct connection
to the controller?"}
Q0 -->|"No, via Ubiquiti cloud"| CLOUD["Cloud Mode"]
Q0 -->|"Yes"| Q1{"Live event streaming
(events watch or TUI)?"}
Q1 -->|"No"| APIKEY["API Key Mode"]
Q1 -->|"Yes"| HYBRID["Hybrid Mode"]
Q1 -->|"No API key
available"| SESSION["Username/Password Mode"]
style APIKEY fill:#50fa7b,color:#0a0a0f
style HYBRID fill:#80ffea,color:#0a0a0f
style SESSION fill:#f1fa8c,color:#0a0a0f
style CLOUD fill:#ff6ac1,color:#0a0a0fAPI Key mode is the right default on UniFi OS controllers. A single Integration API key reaches both the Integration API and the Session API HTTP endpoints, covering almost every CLI command without a username or password. Only switch to Hybrid if you need live WebSocket features (events watch, tui).
🔗API Key
Generate a key on your controller under Settings > Integrations. On UniFi OS, the same key also authenticates the Session API HTTP endpoints (/proxy/network/api/* and /proxy/network/v2/api/*), so API key mode covers CRUD, device commands, stats, DHCP reservations, admin operations, and events list.
unifly config init # Select "API Key" during setup
unifly --api-key <KEY> devices list # Or pass directly
| Pros | Limitations |
|---|---|
| Simplest setup — no password juggling | No live WebSocket events (requires cookie) |
| Authenticates both Integration + Session | events watch and TUI live refresh unavailable |
| No CSRF bookkeeping | Classic standalone controllers may differ |
| Stable, no token expiry |
Best for: CI/CD pipelines, scripted provisioning, daily CLI automation, most everyday workflows.
🔗Username / Password
Session-based auth with cookie and CSRF token handling. Use this when you lack an Integration API key, when you need live WebSocket events, or when your controller is a classic standalone that does not accept API keys on the Session HTTP surface.
unifly config init # Select "Username/Password" during setup
| Pros | Limitations |
|---|---|
| Live WebSocket events + TUI live | Sessions expire periodically |
| Full Session API access | No access to modern Integration endpoints |
| Admin management | DNS, ACL, traffic lists unavailable |
Best for: Monitoring-focused setups where you primarily care about live event streaming.
🔗Hybrid Mode
API key for HTTP (both Integration and Session) plus username/password for the live WebSocket cookie session. The setup wizard offers this when you provide both.
unifly config init # Select "Hybrid" during setup
| Capability | How it’s reached |
|---|---|
| Integration CRUD (networks, WiFi, firewall, etc.) | Integration API via X-API-KEY |
| Session HTTP (stats, device commands, admin) | Session API via X-API-KEY |
Live event streaming (events watch, TUI) | Session WebSocket via cookie session |
| Client/device field enrichment | Session HTTP (merged into Integration records) |
How it works: unifly uses the API key for every HTTP request and the cookie session only to establish the live WebSocket. Everything else an API key can reach is reached with the API key.
To verify Hybrid is working, run unifly events watch — if events stream, the WebSocket cookie session is active.
🔗Cloud Mode
Cloud mode reaches your controllers through Ubiquiti’s Site Manager instead of a direct network connection. It needs two things: a Site Manager API key (created under your Ubiquiti account, not on the controller) and a console ID (host_id) naming which console to talk to.
unifly config cloud-setup # Guided setup: validates the key,
# picks a console and site, writes the profile
Two distinct surfaces run on this key:
- Fleet queries (
unifly cloud hosts|sites|devices|isp|sdwan) talk to the Site Manager fleet API directly and need nohost_idat all. - Connector-routed commands: with
auth_mode = "cloud", regular Integration-backed commands (networks list,wifi create,firewall policies list, …) tunnel through the cloud connector to the console named byhost_id. When the API key sees exactly one console (or exactly one you own), unifly resolveshost_idautomatically; otherwise set it in the profile or pass--host-id.
Session-backed commands (events watch, stats, device commands, admin) are not reachable through the connector and still require direct controller access. The full walkthrough lives on the Cloud & Site Manager page.
🔗Credential Storage
Credentials go in your OS keyring by default:
| OS | Backend |
|---|---|
| macOS | Keychain |
| Linux | Secret Service (GNOME Keyring, KWallet) |
| Windows | Windows Credential Manager |
The config.toml file stores non-sensitive settings like controller URLs and site names. The setup wizard offers keyring storage by default, but also provides a plaintext config fallback for environments where the keyring isn’t available (headless servers, WSL, CI).
Secrets resolve in a fixed order: an environment variable named by api_key_env (or UNIFI_PASSWORD for passwords), then an explicit api_key = "..." / password = "..." value in config.toml, then the keyring. Explicit config beats the keyring, so editing a plaintext value in config.toml is never silently overridden by a stale keyring entry from an earlier config init.
The TUI settings and setup screens follow the same rules. A credential held by the keyring shows as “stored in keyring” and needs no retyping, and saving leaves the entry alone. A newly typed credential goes to the keyring, falling back to plaintext in config.toml (with a notice) only when no keyring is usable. A keyring that is there but refuses the secret stops the save instead, with a message naming the field: config.toml is left unchanged, and any keyring entry the same save had already written is put back as it was. A credential already kept in plaintext in config.toml stays there.
To update a stored password:
unifly config set-password # Updates the active profile
unifly config set-password --profile office # Updates a specific profile
🔗Environment Variables
For CI/CD and scripting, pass credentials via environment:
export UNIFI_API_KEY="your-api-key-here"
export UNIFI_URL="https://192.168.1.1"
unifly devices list
graph LR
A["CLI Flags"] -->|highest| RESULT["Final Value"]
B["Environment Variables"] -->|medium| RESULT
C["Config File"] -->|lowest| RESULT
style A fill:#ff6ac1,color:#0a0a0f
style B fill:#f1fa8c,color:#0a0a0f
style C fill:#80ffea,color:#0a0a0fCLI flags override environment variables, which override config file values.
🔗MFA / TOTP
If your controller requires two-factor authentication, give unifly a command that prints the current code. unifly runs it again every time it has to log in, so a long-running TUI renews its session when the cookie expires (about an hour in):
[profiles.home]
totp_command = ["op", "item", "get", "UniFi", "--otp"]
The command is an argv array: the program, then its arguments. It runs directly, without a shell, with no stdin and 10 seconds to finish, and its trimmed output is the code. A non-zero exit, a timeout, or empty output counts as no code and is logged without the output. On Unix the command runs in its own process group. If it times out, or exits while something it started still holds its output open, everything left in that group is stopped too. A background process whose output is redirected away, as a password manager’s cache daemon does, is left running. On Windows only the command itself is stopped, and a helper that leaves a background process holding its output open can leave one idle thread in unifly until that process exits; the login itself is never held up.
If the password manager is locked when a re-login runs, the command may raise an unlock prompt (Touch ID or a system password dialog). An attempt that gets no code is retried after 30 seconds, so while the session stays expired, expect a prompt about every 30 seconds until you unlock it.
A one-off code works too, for a single command:
UNIFI_TOTP=$(op read "op://Personal/UniFi/one-time password?attribute=otp") \
unifly devices list
# Or name the variable in your profile:
# [profiles.home]
# totp_env = "UNIFI_TOTP"
A one-off code (--totp, UNIFI_TOTP, or totp_env) serves a single login. When a profile has both, the one-off code is used for the first login and totp_command for every later one. A session with only a one-off code cannot renew itself, so the TUI loses session-backed features (events, health, client details) once its cookie expires.
totp_env and totp_command must be edited directly in config.toml. Neither is supported by unifly config set yet.
🔗Next Steps
- Configuration: full profile reference, environment variables, and precedence rules
- CLI Commands: what you can do with each auth mode
- Troubleshooting: common auth errors and fixes