Skip to content

Network Management

The Network Management module is the core module - no dependencies - that gives you a unified interface for switch port management, VLAN operations, wireless networks, topology visualization, firmware lifecycle, and controller config backup across all connected network controllers.

The reference adapter is Omada (TP-Link) - production, gold-standard, tested against real hardware. UniFi is beta. Every configuration write is staged by default and requires an explicit apply step before touching a live device. This is not optional during initial setup - see Write safety and staging.

AdapterTierNotes
Omada (TP-Link)Production / gold standardwrite-capable with staging gate; full VLAN/WiFi/port/AP/hotspot/802.1X surface; deepest adapter in the codebase
UniFi (Ubiquiti)BetaRead paths + dual-gated writes; not yet field-tested against real Ubiquiti hardware

For firewall/router adapters - OPNsense, pfSense, MikroTik, OpenWRT - see Firewall.

Every configuration write routes through the staged dual-gate:

  1. Read-only gate - ADAPTER_READ_ONLY (and OMADA_READ_ONLY) both default to true on new installs (safe by default). While either is true, the Omada client layer raises OmadaApiError on any mutating HTTP verb, and the standard network endpoint router (_push_vlan_to_controller, _push_wifi_to_controller) catches that error silently - the database change commits, but the controller is not written. No row is queued in adapter_pending_changes; the push is simply skipped with a warning log.
  2. Apply gate - the formal staged-change pipeline (adapter_pending_changes / AdapterStagingService) is used by the shared gateway-vpn apply dispatcher (POST /api/v1/gateway-vpn/changes/{change_id}/apply with {"force": true}) and by all firewall/gateway adapter endpoint families - OPNsense, pfSense, MikroTik, OpenWrt, Proxmox, UniFi, and Omada write endpoints all stage through AdapterStagingService and apply via this dispatcher. The standard VLAN/WiFi/port paths in the network module do not use this pipeline - they push directly and live to the controller.

When ADAPTER_READ_ONLY is disabled (false), standard VLAN/WiFi writes push directly and live to the controller. There is no pending-changes queue for these paths; the controller sync or next scheduled poll reconciles any drift.

The Omada raw passthrough (/api/v1/gateway-raw/{controller_id}/call) also enforces this gate and blocks sensitive paths regardless of force - /admin, /users, /roles, /cmd/backup, /maintenance/, /setting/system/sslcert, and others - so the escape hatch cannot bypass the staging audit trail.

ControllerCreate accepts inline credentials (username / password). There is no credential_id field and no separate credential-store step for controller registration.

Required fields: name, controller_type, host, port, site_id. Optional: username, password, use_ssl, verify_ssl, sync_enabled, sync_interval_seconds, config, site_mappings.

Omada:

Terminal window
curl -s -X POST https://<freesdn>/api/v1/controllers/ \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>" \
-H "Content-Type: application/json" \
-d '{
"name": "omada-1",
"controller_type": "omada",
"host": "https://omada.lan",
"port": 8043,
"site_id": "<site_uuid>",
"username": "admin",
"password": "<pass>"
}'

UniFi:

Terminal window
curl -s -X POST https://<freesdn>/api/v1/controllers/ \
-H "Cookie: freesdn_access=<token>" \
-H "X-CSRF-Token: <csrf>" \
-H "Content-Type: application/json" \
-d '{
"name": "unifi-1",
"controller_type": "unifi",
"host": "https://unifi.lan",
"port": 8443,
"site_id": "<site_uuid>",
"username": "admin",
"password": "<pass>"
}'
Terminal window
# Test connectivity against stored credentials
curl -s -X POST https://<freesdn>/api/v1/controllers/<controller_id>/test \
-H "Cookie: freesdn_access=<token>" -H "X-CSRF-Token: <csrf>"
# Pull inventory into FreeSDN
curl -s -X POST https://<freesdn>/api/v1/controllers/<controller_id>/sync \
-H "Cookie: freesdn_access=<token>" -H "X-CSRF-Token: <csrf>"

3. Probe remote sites (multi-site controllers)

Section titled “3. Probe remote sites (multi-site controllers)”

Omada and UniFi controllers can manage multiple sites. Map their remote sites to FreeSDN sites before you start syncing:

Terminal window
# List remote sites visible on the controller
GET /api/v1/controllers/<controller_id>/remote-sites
# Map remote-site IDs to FreeSDN site UUIDs
PUT /api/v1/controllers/<controller_id>/site-mappings

The switch detail view (/switches/:deviceId/:tab) has twelve tabs driven by the URL.

MethodPathPermissionPurpose
GET/api/v1/switches/network:readPaginated list with aggregated port/PoE stats
GET/api/v1/switches/{id}network:readDetailed single-switch info
POST/api/v1/switches/{id}/refreshnetwork:writePull live state from controller into DB
MethodPathPermissionPurpose
GET/api/v1/switches/{id}/portsnetwork:readList all ports (SwitchPortOut)
GET/api/v1/switches/{id}/ports/{n}network:readSingle port detail
PATCH/api/v1/switches/{id}/ports/{n}network:writeFull port config (see fields below)
POST/api/v1/switches/{id}/ports/{n}/togglenetwork:writeEnable / disable port
POST/api/v1/switches/{id}/ports/{n}/poenetwork:writeEnable / disable PoE
POST/api/v1/switches/{id}/ports/{n}/poe/cyclenetwork:writePower-cycle PoE (disable → wait → enable)
POST/api/v1/switches/{id}/ports/bulknetwork:writeBulk-update multiple ports

The PATCH port body accepts: name, enabled, speed, duplex, mtu, flow_control, vlan_config (native/tagged), poe_config, and stp_config (mode, guard, BPDU protection). It also accepts security_config for 802.1X mode (auto / force_auth / force_unauth / disable), MAC limit, and violation action.

The interactive U (untagged) / T (tagged) / empty matrix lets you assign VLAN membership to every port in one operation.

MethodPathPermissionPurpose
GET/api/v1/switches/{id}/vlansnetwork:readVLANs on this switch’s controller
PUT/api/v1/switches/{id}/vlans/port-assignmentsnetwork:writeBulk-assign VLAN memberships to ports

Large switches (48+ ports × 20+ VLANs) render the matrix with TanStack Virtual to avoid DOM overhead.

MethodPathPermissionPurpose
GET/api/v1/switches/{id}/lagsnetwork:readList LAG groups
POST/api/v1/switches/{id}/lagsnetwork:writeCreate LAG
PUT/api/v1/switches/{id}/lags/{lag_id}network:writeUpdate LAG (member ports, LACP/static)
DELETE/api/v1/switches/{id}/lags/{lag_id}network:writeDelete LAG
MethodPathPermissionPurpose
GET/api/v1/switches/{id}/mirrornetwork:readPort mirror config
PUT/api/v1/switches/{id}/mirrornetwork:writeSet mirror session, source, and destination
MethodPathPermissionPurpose
GET / PUT/api/v1/switches/{id}/stpnetwork:read / network:writeSTP/RSTP global config (mode, priority, hello, max-age)
GET / POST/api/v1/switches/{id}/aclnetwork:read / network:writeACL rules
PUT / DELETE/api/v1/switches/{id}/acl/{rule_id}network:writeUpdate / delete ACL rule
GET / PUT/api/v1/switches/{id}/igmpnetwork:read / network:writeIGMP snooping config
POST/api/v1/switches/{id}/ports/{n}/isolationnetwork:writePort isolation toggle
MethodPathPurpose
POST.../ports/{n}/flow-control802.3x flow control
POST.../ports/{n}/speedLink speed / duplex
POST.../ports/{n}/loopback-detectLoopback detection
POST.../ports/{n}/bandwidthBandwidth / rate limiting
POST.../ports/{n}/storm-controlStorm-control thresholds
POST.../ports/{n}/lldpEnable / disable LLDP-MED

All require network:write.

Requires the network.advanced_poe feature flag (default OFF).

MethodPathPurpose
GET/api/v1/switches/{id}/poe-schedulesList PoE schedules
POST/api/v1/switches/{id}/poe-schedulesCreate schedule
PUT/api/v1/switches/{id}/poe-schedules/{schedule_id}Update schedule
DELETE/api/v1/switches/{id}/poe-schedules/{schedule_id}Delete schedule
MethodPathPurpose
POST/api/v1/switches/{id}/diagnostics/cable-testCable diagnostic on a port
POST/api/v1/switches/{id}/diagnostics/pingPing from device to target
POST/api/v1/switches/{id}/diagnostics/tracerouteTraceroute from device

There are two distinct kinds of profiles - be precise about which you mean:

KindPrefixNotes
DB-backed port profiles/api/v1/switches/profiles*Stored in FreeSDN DB; GET/POST/PUT/PATCH/DELETE
Controller-side port profiles/api/v1/switches/{id}/port-profiles*Pushed to the controller; GET/POST/PUT/DELETE

You can also apply a batch CLI configuration profile to multiple ports with POST /api/v1/switches/{id}/cli-profile/apply.

Visibility: MAC table, LLDP, events, clients

Section titled “Visibility: MAC table, LLDP, events, clients”
MethodPathPurpose
GET/api/v1/switches/{id}/mac-tableMAC address table
GET/api/v1/switches/{id}/lldpLLDP neighbors (from port metadata)
GET/api/v1/switches/{id}/eventsRecent controller events for this device
GET/api/v1/switches/{id}/alertsActive controller alerts
GET/api/v1/switches/{id}/clientsConnected clients

These endpoints read site-level config from the controller; DHCP snooping and QoS have corresponding PUT write paths:

GET /api/v1/switches/{id}/routes - static routes (read-only)
GET /api/v1/switches/{id}/dhcp - site DHCP config
GET|PUT /api/v1/switches/{id}/dhcp/snooping - DHCP snooping
GET|PUT /api/v1/switches/{id}/qos - site QoS

POST /api/v1/switches/{id}/oui-vlan/apply matches connected-client MAC OUIs to VLAN mappings and configures each client port’s native VLAN automatically. Body:

{
"mappings": [
{ "oui_prefix": "AA:BB:CC", "vlan_id": 20, "description": "Printers" }
]
}

GET /api/v1/switches/{id}/running-config pulls the running config for backup or diff comparison. The Config History tab in the switch detail view shows a diff viewer across snapshots.


VLANs are controller-scoped in the network.vlans table (Network model). This is the Layer 0 truth - what a specific controller currently knows. Site-wide desired state (gateway.gw_canonical_vlans) is a Layer 2 concept managed by the Firewall module.

MethodPathPermissionPurpose
GET/api/v1/network/vlansauthenticatedList VLANs; site_id?, skip, limit
GET/api/v1/network/vlans/{id}authenticatedGet one VLAN
POST/api/v1/network/vlansconfig:writeCreate VLAN (201)
PATCH/api/v1/network/vlans/{id}config:writeUpdate + best-effort controller push
DELETE/api/v1/network/vlans/{id}config:writeSoft-delete + push delete to controller

Create body fields: vlan_id (1-4094), name (1-128 chars), description?, site_id?, dhcp_enabled, dhcp_start, dhcp_end, gateway, subnet_mask.

When a site has multiple controllers, VLANs can drift out of sync. Use the alignment endpoints to detect and fix this:

Terminal window
# See alignment matrix - always safe, never mutates
GET /api/v1/network/vlans/alignment?site_id=<uuid>

Response includes a per-controller present/absent/differs flag and an alignment_score (0-100). The matrix only provides meaningful data with two or more controllers at the same site.

Terminal window
# Push a VLAN from a source controller to target controllers
POST /api/v1/network/vlans/distribute
{
"source_network_id": "<uuid>",
"target_controller_ids": ["<uuid>", "<uuid>"]
}

This is a direct write subject to the adapter read-only gate. If ADAPTER_READ_ONLY=true, the push to each target controller is blocked and no DB record is written for that target. Per-target success/fail results are returned; a partial failure does not roll back successful targets.

The VlansPage alignment tab shows the VLAN × controller matrix with per-cell “Copy” buttons that call distribute on a single target.


MethodPathPermissionPurpose
GET/api/v1/access-points/network:readPaginated AP list with radio/client summary
GET/api/v1/access-points/{id}network:readAP detail including live controller data
GET/api/v1/access-points/{id}/clientsnetwork:readClients connected to this AP
GET/api/v1/access-points/{id}/firmwarenetwork:readFirmware update status
PATCH/api/v1/access-points/{id}/namenetwork:writeUpdate display name (pushes to controller)
MethodPathPurpose
GET/api/v1/access-points/{id}/radiosRadio config for all bands
PATCH/api/v1/access-points/{id}/radios/{band}Update radio settings for band ∈ {2g, 5g, 5g2, 6g}

Radio settings include channel, TX power, channel width, DFS, and band steering.

MethodPathPermissionPurpose
GET/api/v1/network/wifiauthenticatedList SSIDs; site_id?, enabled?, paging
POST/api/v1/network/wificonfig:writeCreate SSID; requires site_id
PATCH/api/v1/network/wifi/{id}config:writeUpdate + controller push
DELETE/api/v1/network/wifi/{id}config:writeSoft-delete + controller push
POST/api/v1/network/wifi/{id}/toggleconfig:writeEnable / disable SSID; body {"enabled": true}
GET/api/v1/access-points/{id}/ssid-overridesnetwork:readPer-AP SSID enable/disable overrides
PUT/api/v1/access-points/{id}/ssid-overridesnetwork:writeSet per-AP SSID overrides

SSID create body: ssid (1-32 chars), security (default wpa2_personal; valid values: open, wep, wpa_personal, wpa2_personal, wpa3_personal, wpa_wpa2_personal, wpa2_wpa3_personal, wpa2_enterprise, wpa3_enterprise - all underscore-separated; sending an unrecognised value returns 422), password? (max 63 chars; WPA2 requires ≥ 8 chars as a protocol constraint, but the API schema does not enforce a minimum - short passwords are forwarded to the adapter unchanged), vlan_id? (1-4094), hidden, enabled, band (default both), client_isolation, band_steering, fast_roaming, rate_limit_enabled/up/down.

MethodPathPurpose
GET / PATCH/api/v1/access-points/{id}/lan-portLAN port VLAN tagging and PoE passthrough
PATCH/api/v1/access-points/{id}/meshEnable / disable mesh; body {"enabled": true}
PATCH/api/v1/access-points/{id}/ledLED mode: 0=off, 1=on, 2=site_settings
MethodPathPurpose
POST/api/v1/access-points/{id}/adoptAdopt a pending AP; sets status ADOPTING
POST/api/v1/access-points/{id}/forgetForget / remove AP; sets status UNKNOWN
POST/api/v1/access-points/{id}/upgradeTrigger firmware upgrade
POST/api/v1/access-points/{id}/rebootReboot AP
POST/api/v1/access-points/{id}/locateFlash LEDs to locate AP
GET/api/v1/access-points/{id}/rf-scanRF scan results

All AP write endpoints require network:write.


Controllers are the upstream systems FreeSDN reads from and pushes to. The controller detail page and these endpoints cover advanced wireless functions, hotspot, 802.1X, batch ops, firmware lifecycle, and config backup.

MethodPathPermissionPurpose
GET/api/v1/controllers/controller:readPaginated list
GET/api/v1/controllers/{id}controller:readSingle controller with stats
POST/api/v1/controllers/controller:createAdd controller (201)
PATCH/api/v1/controllers/{id}controller:updateUpdate controller
DELETE/api/v1/controllers/{id}controller:deleteDelete controller (204)
POST/api/v1/controllers/{id}/testcontroller:updateTest stored-credential connection
POST/api/v1/controllers/test-connectioncontroller:createTest connection pre-creation (raw creds)
POST/api/v1/controllers/{id}/synccontroller:updateSync inventory from controller
MethodPathPurpose
GET / PUT/api/v1/controllers/{id}/hotspotHotspot config
GET / PUT/api/v1/controllers/{id}/captive-portalCaptive portal config
GET/api/v1/controllers/{id}/hotspot/vouchersList vouchers
POST/api/v1/controllers/{id}/hotspot/vouchersCreate vouchers (201)
DELETE/api/v1/controllers/{id}/hotspot/vouchers/{voucher_id}Delete voucher

All require controller:update.

MethodPathPurpose
GET / PUT/api/v1/controllers/{id}/dot1x802.1X site-wide config
GET/api/v1/controllers/{id}/dot1x/eventsRecent authentication events
GET / PUT/api/v1/controllers/{id}/dot1x/radiusRADIUS auth + accounting servers (primary/secondary, ports, shared secrets)
GET/api/v1/controllers/{id}/dot1x/statsSuccess / failure counts
MethodPathPurpose
GET / PUT/api/v1/controllers/{id}/wifi/radio-settingsSite-wide channel plan, TX power, DFS, band steering
GET/api/v1/controllers/{id}/wifi/channel-utilizationPer-AP/band utilization (RF Health tab)
GET/api/v1/controllers/{id}/wifi/rogue-apsRogue-AP detection results
MethodPathPermissionPurpose
POST/api/v1/controllers/{id}/batch/rebootcontroller:update + site_admin roleReboot multiple devices; logged to freesdn.security.batch_op
POST/api/v1/controllers/{id}/batch/firmware-upgradecontroller:update + site_admin roleBulk firmware upgrade
POST/api/v1/controllers/{id}/batch/firmware-checkcontroller:updateCheck available updates fleet-wide
MethodPathPurpose
GET/api/v1/controllers/{id}/firmwareAvailable firmware versions
GET/api/v1/controllers/{id}/firmware/overviewCounts per version + update availability
GET/api/v1/controllers/{id}/firmware/historyUpgrade history log
MethodPathPurpose
GET/api/v1/controllers/{id}/healthController health
GET/api/v1/controllers/{id}/logsController system logs
POST/api/v1/controllers/{id}/backupTrigger controller config backup
GET/api/v1/controllers/{id}/topologyTopology data from the controller

The topology graph is a React Flow canvas at /topology. It has three distinct API surfaces - know which one the frontend is calling:

RouterPath prefixNotes
Module routerGET /api/v1/network/topologyNodes + links, site_id?
Endpoint routerGET /api/v1/network/topologyAlso nodes + links (UI fallback)
Topology router/api/v1/topology/*Frontend TopologyPage uses this one; includes saved layouts
MethodPathPermissionPurpose
GET/api/v1/topology/graphdevice:readFull topology graph (TopologyGraphResponse)
GET/api/v1/topology/layout/{site_id}device:readSaved node positions for a site (nullable)
PUT/api/v1/topology/layout/{site_id}device:writeSave / update node positions
POST/api/v1/topology/auto-layout/{site_id}device:writeAuto-arrange layout
DELETE/api/v1/topology/layout/{site_id}device:writeDelete saved layout (204)
MethodPathPermissionPurpose
POST/api/v1/network/discovery/startdevice:updateStart device discovery Celery task; site_id?, subnet? (202)
GET/api/v1/network/discovery/statusdevice:readPoll Celery progress; task_id required
POST/api/v1/network/topology/refreshdevice:updateDispatch discover_all_devices Celery task (202)

MethodPathPermissionPurpose
GET/api/v1/network/clientsauthenticatedList clients; site_id?, paging
GET/api/v1/network/clients/{id}authenticatedSingle client
GET/api/v1/network/clients/stats/summarydevice:readSummary counts; site_id?
POST/api/v1/network/clients/{id}/blockdevice:updateBlock client; sets blocked=True + calls adapter.block_client(mac) best-effort
POST/api/v1/network/clients/{id}/unblockdevice:updateUnblock client; clears flag + calls adapter.unblock_client(mac)

The actions router provides a small set of imperative device operations:

MethodPathPermissionPurpose
POST/api/v1/actions/poe/cycledevice:actionCycle PoE on a switch port; body {device_id, port (1-48), duration (1-30s)}
POST/api/v1/actions/wifi/ssid/togglecontroller:actionEnable / disable SSID; body {controller_id, ssid_name, enabled}
POST/api/v1/actions/rebootdevice:adminReboot a device; body {device_id}

POST /api/v1/actions/poe/cycle also publishes an action.poe_cycle event to the event bus.


Different routers use different permission strings. This is a real implementation seam, not a documentation error.

RouterRead permissionWrite permissionAdmin/delete
Module api.py (/network/*)device:readdevice:updatedevice:admin
Endpoint network.py - VLAN/WiFi/port-VLANauthenticatedconfig:writeconfig:write
Endpoint network.py - port PoE/patch, client blockauthenticateddevice:update-
switches.pynetwork:readnetwork:write-
access_points.pynetwork:readnetwork:write-
controllers.pycontroller:readcontroller:updatecontroller:delete; batch ops also require site_admin role
actions.py - PoE cycle / SSID toggle-device:action / controller:actiondevice:admin (reboot)
topology.pydevice:readdevice:write-

The module manifest declares network.view, network.manage, network.vlan.manage, network.wifi.manage, network.poe.control, and network.firmware.upgrade as its public permission names, which map onto the role hierarchy.


These routers mount directly at /api/v1/ and provide typed or raw access to Omada-specific API surfaces. They are Omada-only and not vendor-neutral FreeSDN features.

Path prefixSource fileCoverage
/api/v1/gateway-wifi/*adapter_omada_wifi.pyWLAN groups, SSIDs, surveillance-VLAN, walled-garden, voucher templates, WIDS/WIPS events
/api/v1/gateway-switch-advanced/*adapter_omada_switch_advanced.pySwitch configs, mirror sessions, per-port jumbo frame
/api/v1/gateway-hotspot/*adapter_omada_hotspot.pyOperators, SMS gateway, portal form fields, free-auth policies
/api/v1/gateway-firmware/*adapter_omada_firmware.pyFirmware lists, checks
/api/v1/gateway-bulk/*adapter_omada_bulk.pyBulk read + bulk action
/api/v1/gateway-diagnostics/*adapter_omada_diagnostics.pyGateway speed-test, sessions
/api/v1/gateway-raw/{controller_id}/calladapter_omada_raw.pyPower-user raw passthrough (see below)

The raw passthrough requires controller:write. Write methods are blocked when ADAPTER_READ_ONLY is true unless force=true is passed in the body. A hardcoded blocklist prevents bypassing the staging audit trail regardless of force:

/admin /users /roles /cmd/backup /maintenance/ /setting/system/sslcert …

RoutePageNotes
/networkNetworkDashboardPageOverview widgets
/topologyTopologyPage (enterprise)React Flow canvas; uses /topology/* API
/switchesSwitchesPagePaginated list; select → detail
/switches/:deviceId/:tabSwitchesPage detailTabs: overview, ports, vlans, lags, profiles, network, config, config-history, logs, clients, diagnostics, advanced
/access-pointsAccessPointsPagePaginated list; select → detail
/access-points/:deviceId/:tabAccessPointsPage detailTabs: overview, radios, ssids, clients, config, rf-health, rogue-aps, radio-settings
/vlans or /network/vlansVlansPageTabs: vlans (CRUD), alignment (VLAN × controller matrix)
/wifi or /network/wifiWifiNetworksPageSSID list + create/edit
/network/clientsNetworkClientsPageClient table with block/unblock
/poePoEPagePoE budget + scheduling

The Network module participates in the Fabric universal app-interconnect:

  • Operation network.client.list - read connected clients (org-scoped, optional site_id and is_online filter, up to 200 results); permission network.view. Use as a mid-chain step in Fabric Connections.
  • Events emitted - network.vlan.created, network.vlan.updated, network.vlan.deleted, network.wifi.created, network.wifi.updated, network.wifi.deleted (NATIVE tier). Wire these as triggers in the Fabric Connections builder.

SettingDefaultNotes
discovery_interval300 sConfigures the interval used when discovery is manually triggered; background auto-polling is not currently active (tasks are commented out)
topology_refresh600 sConfigures the refresh interval used when topology refresh is manually triggered; automatic periodic rebuild is not currently active (tasks are commented out)
traffic_retention_days30Days of port traffic data kept in TimescaleDB
auto_backup_enabledtrueCurrently not wired to any background task. Use POST /api/v1/controllers/{id}/backup to trigger backups manually.
backup_retention_count5Number of config snapshots to retain per device
poe_budget_warning_threshold80 %PoE budget warning level
Feature flagDefaultNotes
network.advanced_poeOFFEnables PoE scheduling and budget endpoint
network.topology_autoONAuto topology discovery
network.traffic_analyticsONPer-port traffic metrics to TimescaleDB
network.bulk_firmwareOFFMulti-device firmware upgrade

  • Two port-write paths with different behavior. /api/v1/switches/{id}/ports/{n} pushes to the controller via the adapter. /api/v1/network/devices/{id}/ports/{n} is DB-only. If a port change does not appear on hardware, confirm which path your client is calling.

  • Rogue-AP 501. If the controller adapter does not implement rogue-AP detection, the endpoint returns 501. This is the expected behavior, not a server error.

  • VLAN distribute is not transactional across controllers. If distributing to three controllers and the second fails, the first succeeded and the third may or may not have run. Check per-target results in the response.

  • UniFi is beta. It has not been field-tested against real Ubiquiti hardware. Use Omada for production deployments that require write capability.

  • Batch ops are not staged. batch/reboot and batch/firmware-upgrade hit live devices directly (subject to the read-only env flag). They require site_admin and are audit-logged, but they do not go through Pending Changes.

  • Module background tasks do not auto-run. Discovery and topology refresh run only when triggered via the API or a Celery task. The health_check reports their state but they are never spawned automatically by the module lifecycle.


  • Firewall - gateway orchestration, VPN, NAT, IDS/IPS, and the gw_canonical_vlans Layer 2 desired-state model
  • Configuration Reference - ADAPTER_READ_ONLY, discovery intervals, and all environment variables
  • Fabric - wire network events into cross-module automations
  • Supported Vendors - honest adapter maturity matrix