SuperAPI AI Directory

unifi-operator

General & other

View repository

Updated 10-16 03:39

About this skill

UniFi Operator

Operate a UniFi OS console (UDM, UDM-Pro/SE, UDR, UCG, Cloud Key Gen2+, or self-hosted Network/Protect) over HTTP. Two product apps, each with a stable official API and a comprehensive private API:

  • Network — gateways, switches, APs, clients, VLANs, WLANs, firewall, routing, port-forwards, DPI, guest/hotspot, device adoption & lifecycle.
  • Protect — cameras, recordings, snapshots, smart-detection events, lights, sensors, chimes, sirens, viewers, alarm manager, realtime updates.

All four surfaces live on one console at port 443 behind a self-signed TLS cert (every curl uses -k). Cloud Site Manager (api.ui.com) is out of scope — this skill is local-console only.

API surface map

Surface Base path Auth Stability Coverage
Network — official /proxy/network/integration/v1 X-API-KEY header (stateless) GA, stable ~60% — sites, devices, clients, vouchers, networks, WLANs, firewall, ACL, DNS
Network — private /proxy/network/api/s/<site> Cookie TOKEN + x-csrf-token Unsupported, shifts across versions ~100% — everything the web UI does (stats, rest/* CRUD, cmd/* actions)
Protect — official /proxy/protect/integration/v1 X-API-KEY header (stateless) GA, stable (Protect 5.3+) ~70% — cameras, snapshot, RTSPS, PTZ, lights/sensors/chimes, arm profiles, WS
Protect — private /proxy/protect/api Cookie TOKEN + X-CSRF-Token Unsupported, shifts across versions ~100% — /bootstrap, video export, events/thumbnails, realtime delta WebSocket

Pick the official surface first (stateless API key, survives upgrades). Drop to the private surface for anything official omits: firewall/WLAN CRUD on older firmware, client block/unblock, device adopt/locate/speedtest, full Protect bootstrap, recorded-video export, and the realtime updates socket.

References

File When to read
references/auth-and-conventions.md API-key creation, UniFi OS login + CSRF derivation, legacy :8443, response envelopes, pagination, the official filter DSL, error tables, reusable shell helpers
references/network-integration-api.md Official Network v1 — full endpoint catalog, device/client/port actions, voucher schema, firewall/WLAN/ACL/DNS CRUD
references/network-private-api.md Private Network — stat/* monitoring, rest/* CRUD, every cmd/* action + payload, reports, settings keys, backups
references/protect-integration-api.md Official Protect v1 — cameras/PATCH, snapshot, RTSPS, PTZ, lights/sensors/chimes/sirens, arm profiles, subscribe WebSocket, alarm webhook
references/protect-private-api.md Private Protect — /bootstrap, camera control, video export, events/thumbnails/heatmap, the binary realtime WebSocket frame protocol

Environment & auth quick-start

HOST="192.168.1.1"          # console IP or hostname
CURL=(curl -ksS)            # -k self-signed, -s quiet, -S show errors

Official APIs — API key (stateless)

One console-wide key (created in Settings → Control Plane → Integrations in either Network or Protect; inherits the creating admin's permissions). Same key works for both Network and Protect on that console.

KEY="paste-api-key"
H=(-H "X-API-KEY: $KEY" -H "Accept: application/json")

"${CURL[@]}" "${H[@]}" "https://$HOST/proxy/network/integration/v1/info"      # Network version
"${CURL[@]}" "${H[@]}" "https://$HOST/proxy/protect/integration/v1/meta/info" # Protect version

Private APIs — login + CSRF (cookie session)

Login once; reuse the cookie jar. Mutating calls (POST/PUT/PATCH/DELETE) require the CSRF token returned by login. A local admin account is required — Ubiquiti cloud SSO logins and MFA-enabled accounts do not work for scripted login (see auth reference).

CSRF=$("${CURL[@]}" -c /tmp/uni.jar -D - -o /dev/null \
  -X POST "https://$HOST/api/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"secret"}' \
  | awk 'tolower($1)=="x-csrf-token:"{print $2}' | tr -d '\r')

S=("${CURL[@]}" -b /tmp/uni.jar)                          # authenticated GET
M=("${CURL[@]}" -b /tmp/uni.jar -H "x-csrf-token: $CSRF") # authenticated mutation

"${S[@]}" "https://$HOST/proxy/network/api/self/sites"    # list sites -> use .name as <site>
"${S[@]}" "https://$HOST/proxy/protect/api/bootstrap"     # entire Protect state

<site> in private Network paths is the internal slug (often default), from self/sites[].name — not the display label. Response envelope is {"meta":{"rc":"ok"},"data":[...]}; rc:"error" carries meta.msg.

Capability → endpoint matrix

Network

Capability Surface · Endpoint
List connected clients official GET .../clients · private GET stat/sta
Client detail / history private GET stat/user/<mac>, POST stat/session
Authorize / unauthorize guest official POST .../clients/{id}/actions {AUTHORIZE_GUEST_ACCESS}
Block / unblock a client private POST cmd/stamgr {block-sta|unblock-sta, mac}
Reconnect (kick) a client private POST cmd/stamgr {kick-sta, mac}
Forget a client private POST cmd/stamgr {forget-sta, macs:[...]}
List devices (gw/sw/ap) official GET .../devices · private GET stat/device
Device stats snapshot official GET .../devices/{id}/statistics/latest
Restart a device official POST .../devices/{id}/actions {RESTART} · private cmd/devmgr {restart, mac}
PoE power-cycle a port official POST .../ports/{idx}/actions {POWER_CYCLE} · private cmd/devmgr {power-cycle, mac, port_idx}
Adopt / forget device official POST/DELETE .../devices · private cmd/devmgr {adopt}
Locate (blink LED) private cmd/devmgr {set-locate|unset-locate, mac}
Upgrade firmware private POST cmd/devmgr/upgrade {mac}
Run speedtest / spectrum scan private cmd/devmgr {speedtest} / {spectrum-scan, mac}
Site health / sysinfo private GET stat/health, stat/sysinfo, stat/dashboard
Events / alarms / IPS private GET stat/event, list/alarm, stat/ips/event
Usage reports (5m/hr/day) private POST stat/report/<interval>.<scope>
DPI by app/category private POST stat/sitedpi, GET stat/dpi
Networks / VLANs official CRUD .../networks · private rest/networkconf
WLANs / SSIDs official CRUD .../wifi/broadcasts · private rest/wlanconf
Firewall zones/policies official CRUD .../firewall/zones, .../firewall/policies (+/ordering)
Firewall groups/rules (legacy) private rest/firewallgroup, rest/firewallrule
Port forwarding private rest/portforward; toggle via PUT {enabled}
Static routes private rest/routing
Hotspot vouchers official GET/POST/DELETE .../hotspot/vouchers · private cmd/hotspot {create-voucher}
Per-client fixed IP / name private PUT rest/user/<id>
Site / admin management private cmd/sitemgr {add-site, invite-admin, move-device, ...}
Backups private cmd/backup {backup, list-backups}, GET /dl/backup/...

Protect

Capability Surface · Endpoint
Entire system state private GET /bootstrap (cameras, nvr, lights, sensors, users, lastUpdateId)
List cameras official GET /cameras · private from /bootstrap.cameras[]
Live snapshot (JPEG) official GET /cameras/{id}/snapshot?highQuality=true · private ?ts=<ms>&force=true
Historical snapshot private GET /cameras/{id}/recording-snapshot?ts=<ms>
Export recorded MP4 private GET /video/export?camera={id}&start=<ms>&end=<ms>&channel=0
RTSPS stream URL official GET/POST/DELETE /cameras/{id}/rtsps-stream
Rename / OSD / LED / HDR / video mode official PATCH /cameras/{id} · private PATCH /cameras/{id}
Recording mode private PATCH /cameras/{id} {recordingSettings:{mode}}
Smart-detect object types official/private PATCH /cameras/{id} {smartDetectSettings:{objectTypes}}
Doorbell LCD message official PATCH /cameras/{id} {lcdMessage}
PTZ preset / patrol official POST /cameras/{id}/ptz/goto/{slot}, /ptz/patrol/start/{slot}
Two-way talkback official POST /cameras/{id}/talkback-session
List / filter events private GET /events?start=&end=&types=&smartDetectTypes=
Event thumbnail / heatmap private GET /events/{id}/thumbnail, /animated-thumbnail, /heatmap
Lights / sensors / chimes official+private GET/PATCH /lights|sensors|chimes/{id}
Trigger chime / siren private POST /chimes/{id}/play-speaker · official POST /sirens/{id}/play
Arm / disarm alarms official POST /arm-profiles/enable|disable, PATCH /arm-profiles/settings
Trigger alarm via webhook official POST /alarm-manager/webhook/{id}
Realtime device/event stream official WS /subscribe/devices, /subscribe/events (JSON) · private WS /ws/updates (binary delta)
Reboot camera / NVR private POST /cameras/{id}/reboot, POST /nvr/reboot

Essential recipes

# --- Network: official ---
NB="https://$HOST/proxy/network/integration/v1"
SITE=$("${CURL[@]}" "${H[@]}" "$NB/sites" | jq -r '.data[0].id')   # most consoles: one site

# Find a noisy guest and read its detail
"${CURL[@]}" "${H[@]}" --get "$NB/sites/$SITE/clients" \
  --data-urlencode "filter=name.like('guest*')" --data-urlencode "limit=50" | jq

# PoE power-cycle port 5 of a switch (deviceId from /devices)
"${CURL[@]}" "${H[@]}" -H "Content-Type: application/json" -X POST \
  "$NB/sites/$SITE/devices/$DEVICE/interfaces/ports/5/actions" -d '{"action":"POWER_CYCLE"}'

# Issue 10 lobby vouchers, 24h, 1 GB cap
"${CURL[@]}" "${H[@]}" -H "Content-Type: application/json" -X POST \
  "$NB/sites/$SITE/hotspot/vouchers" \
  -d '{"name":"Lobby","count":10,"timeLimitMinutes":1440,"dataUsageLimitMBytes":1024}'

# --- Network: private (block a client the official API can't) ---
NP="https://$HOST/proxy/network/api/s/default"
"${M[@]}" -H "Content-Type: application/json" -X POST \
  "$NP/cmd/stamgr" -d '{"cmd":"block-sta","mac":"aa:bb:cc:dd:ee:ff"}'

# --- Protect: snapshot every camera ---
PB="https://$HOST/proxy/protect/integration/v1"
for id in $("${CURL[@]}" "${H[@]}" "$PB/cameras" | jq -r '.[].id'); do
  "${CURL[@]}" "${H[@]}" "$PB/cameras/$id/snapshot?highQuality=true" -o "cam-$id.jpg"
done

# --- Protect: export last 10 minutes of recorded video (private) ---
NOW=$(($(date +%s)*1000)); AGO=$((NOW-600000))
"${S[@]}" "https://$HOST/proxy/protect/api/video/export?camera=$CAM&start=$AGO&end=$NOW&channel=0" -o clip.mp4

Red flags

Pattern Risk
Omitting -k on curl TLS verify fails on the console's self-signed cert
Mutating private endpoint without x-csrf-token 401; the call silently does nothing
Using the UI display name as <site> api.err.NoSiteContext; the slug (default) is required
POST rest/<resource>/<id> to edit Creates a duplicate; PUT updates, POST creates
Polling GET /bootstrap on a timer Heavy full-state payload; the /ws/updates socket delivers deltas instead
Logging in with a cloud-SSO or MFA account Scripted /api/auth/login fails; a local non-MFA admin is required
Assuming private paths are stable rest/routing, rest/tag and others return 500/Invalid on some firmware (see references)
Hardcoding siteId across consoles Official siteId is a per-console UUID, not the default slug
Treating official API as complete No client block/unblock, no Protect /bootstrap, no video export — drop to private