UniFi Adapter — Operator Setup
The UniFi adapter pulls topology from a local UniFi Network controller and feeds it into the same discovery pipeline the rest of the edge node uses. After setup, the topology view shows your real network — gateway → switches → access points — plus every connected client (Amazon Echos, Reolink cameras, phones, IoT devices) with vendor labels, all hanging off the AP or switch they’re actually connected through.
This page covers Slices A + B: read-only discovery of UniFi-managed devices AND the WiFi/wired clients the controller has authenticated. Per-port throughput, LLDP, and the metrics channel land in a follow-up.
What you need
- An enrolled DPF Edge Node (
docker-compose.edge.ymloverlay running; trustState=trusted in Admin > Platform Development > Edge Nodes). - A UniFi Network controller reachable from the host the edge node runs on — UDM, UDM-Pro, UDR, UCG, or a hosted Network Application instance.
- UniFi Network 9.0+ (or UniFi OS 4.0+). Earlier versions don’t expose the API-key surface this adapter uses.
Step 1 — Generate an API key in the controller
- Open the UniFi controller UI.
- Go to Settings → Control Plane → Integrations. On older firmware this is Settings → System → API.
- Click Create New under API Keys. Name it something like
dpf-edge-node. - Copy the key once — it isn’t shown again. Treat it like a password.
The adapter calls GET /proxy/network/api/s/{site}/stat/device and GET /proxy/network/api/s/{site}/stat/sta, so the key needs read access to the Network application. Read-only keys are sufficient.
Step 2 — Add the connection in the portal
- Open Platform → Tools → Estate Discovery (
/platform/tools/discovery). - Click Add Connection (or Configure on the detected gateway tile if your edge node has already surfaced one).
- Fill in:
- Discovery Method:
Ubiquiti UniFi - Site:
default(or the slug from your UniFi UI for multi-site installs) - Controller URL: full URL to the controller. UDM/UDM-Pro:
https://<gateway-ip>. Hosted Network (https://unifi.ui.com) is not supported on this path — local LAN only. - API Key: paste the value from Step 1.
- Allow self-signed controller certificate: enable only for a trusted closed LAN when the UniFi appliance uses its factory/self-signed certificate.
- Discovery Method:
- Click Save & Test. The portal:
- Encrypts the key with AES-256-GCM (
CREDENTIAL_ENCRYPTION_KEY) and stores the ciphertext in theDiscoveryConnectiontable. - Calls the controller once to verify the key works.
- Flips status to
activeon success, or reportsauth_failed/unreachable/tls_errorwith the failure reason.
- Encrypts the key with AES-256-GCM (
That’s it. No file on disk, no bind mount, no container restart needed.
Step 3 — The edge node picks it up automatically
Every sweep tick (default 5 minutes) the edge node calls GET /api/v1/edge/adapters with its node token. The portal returns every active UniFi DiscoveryConnection row with its apiKey decrypted server-side. The edge node then polls each controller and submits the results through the existing /api/v1/edge/discovery-runs channel.
To verify the next sweep picked the connection up:
docker compose logs edge-node --tail 50
…look for Discovery run submitted: runKey=<uuid> items=<N> where N jumps up after you saved the connection.
Editing or rotating the key
The connection row on /platform/tools/discovery has per-row Re-test, Edit, and Delete buttons:
- Re-test — runs the one-shot probe against the controller and updates
lastTestStatus. - Edit — opens the form with controller URL + site pre-filled; leave the API key field blank to keep the existing ciphertext, paste a new value to rotate.
- Delete — removes the row (with a confirmation step). The edge node’s next sweep will see it’s gone and stop polling that controller.
What the adapter emits
UniFi-managed devices (Slice A — /stat/device)
UniFi type |
DPF itemType |
OSI layer |
|---|---|---|
usw (switch) |
switch |
2 |
uap (access point) |
access_point |
2 |
ugw / udm / udmpro / uxg (gateway) |
gateway |
3 |
| anything else | network_device |
2 |
For every UniFi device:
- An
ObservationItemkeyedunifi:<mac>with model, IP, firmware, state, etc. inrawData. - A
SAME_ASrelationship toarp:<ip>so the Authority’s normalization can collapse the two records into one canonical Configuration Item. - A
HOSTSrelationship from each device to its uplink parent (gateway → switch → AP).
Connected clients (Slice B — /stat/sta)
For every authenticated WiFi or wired client (your phones, laptops, Amazon Echos, Reolink cameras, smart switches, etc.):
- An
ObservationItemkeyedarp:<ip>— same key the local ARP collector uses, so observations dedupe on the Authority side regardless of which collector sees the device first. - Display name preference: operator-set name in the UniFi UI (“Mark’s iPhone”) → DHCP hostname →
<short-vendor> <ip>(e.g. “Amazon 192.168.0.49”) → genericLAN Host <ip>. rawData.vendor/vendorOui/vendorShortfrom the bundled IEEE OUI registry — same enrichment the local ARP collector applies.- For WiFi clients:
rawData.apMac,essid,channel,radio,signal,rssi,noise. Higher confidence (0.9) than local ARP entries (0.7) because UniFi has authenticated the client, not just learned a kernel neighbor entry. - For wired clients:
rawData.swMac,swPort. - A
MEMBER_OFrelationship from each client (arp:<ip>) to the UniFi-managed device it connects through (unifi:<ap-or-switch-mac>) — this is what makes the topology view draw “this Echo hangs off this AP” edges.
Independent failure handling
The two endpoints are fetched in parallel and their errors are isolated. If /stat/sta returns 503 but /stat/device succeeds, the device items still flow and only one warning is emitted. Same the other way. A single endpoint failure never blocks the other.
The topology view treats all of these the same way as any other discovered CI, so they show up in the Subnet View, the Network View, and the impact-blast-radius traversal automatically.
Troubleshooting
Status stuck at auth_failed after Save & Test. The portal couldn’t authenticate with the controller using the key you pasted.
- Check the key wasn’t truncated when copying — paste it into a plain-text scratch space first and verify the length.
- Re-generate in the UniFi UI; rotate via the Edit button.
- Try Re-test — sometimes the controller momentarily 401s during an upgrade.
Status unreachable. The portal container can’t reach the controller IP. Check the controller is on the same network the portal can route to, and that no firewall blocks the HTTPS port.
Status tls_error. Common on UDM-Pro home installs with self-signed certs. Edit the connection, enable Allow self-signed controller certificate for the trusted closed LAN, then click Save & Test again. Keep it disabled for hosted/public controllers and prefer installing a trusted certificate when available.
No new items appear after a sweep. Run docker compose logs edge-node --tail 100 | grep -i unifi. Common causes:
- The edge node’s
fetchAdapterscall failed — log lineadapters: fetch failed (...)tells you why (token mismatch, portal unreachable, etc.). - HTTP 404 on the UniFi side — the controller version doesn’t expose
/proxy/network/api/.... You’re likely on UniFi OS 3.x; upgrade or wait for the cookie-auth slice. controller response was not JSON— the controller served the login page. The API key isn’t being honored; rotate it.
What’s NOT in this slice
| Feature | Tracked by |
|---|---|
Per-port rx/tx throughput metrics |
The full spec at docs/superpowers/specs/2026-05-19-edge-node-network-telemetry-adapters-design.md — needs the metrics.network capability + the /api/v1/edge/metrics endpoint, neither of which exist yet. |
| LLDP-derived L2 wiring | Same spec, § 4.2 — separate collector. |
| WebSocket event-driven sweep on client/device join | Same spec, § 4.3 — both slices poll on the regular sweep cadence (5 min). |
| Cookie-auth for pre-9.0 controllers | If you need this, open an issue with your controller version and we’ll add the auth fallback. |
Multi-node routing (targetEdgeNodeId) |
BI-35de9ce8 follow-up. Today every trusted edge node polls every active UniFi controller. Single-node installs Just Work; multi-node installs see duplicate idempotent work until the column lands. |