UniFi Protect
Ubiquiti UniFi Protect controllers (Cloud Key Gen2+, Dream Machine Pro, UNVR, UDM-SE) and the cameras / floodlights / chimes managed by them, integrated via the local Protect Integrations REST API on UniFi OS using a per-controller API key.
The driver subscribes to the console's real-time event stream (/subscribe/events, WebSocket) for motion, smart detections, and doorbell rings, with the camera-state poll as fallback while the stream is down. It fetches JPEG snapshots and exposes a small set of write commands (recording mode, status LED, relay outputs). Lights and chimes are listed but not yet controllable.
The console also works as an access control device: a doorbell ring fires call rules, request-to-exit buttons on a Protect relay fire rex rules, relay outputs release strikes and gates, and relay inputs and UP-Sense door sensors report door position. See Access control.
Prerequisites
- A UniFi OS console (Cloud Key Gen2+, Dream Machine Pro, UNVR, UDM-SE) running UniFi Protect 3.x or newer.
- Console firmware new enough to expose the Integrations API at
/proxy/protect/integration/v1/(UniFi OS 3.2+). The driver falls back to the/proxy/protect/integrations/v1/spelling that earlier builds used if the documented path doesn't answer. - For access control: Protect 6.x or newer, plus the hardware — a doorbell, a UniFi relay / SuperLink device for strikes and REX buttons, UP-Sense sensors for door position.
- An API key minted under Settings → Control Plane → Integrations → Create API Key. The key is shown only once at creation — copy it before navigating away.
- TCP/443 reachable from the GEM controller to the console (LAN IP or hostname).
Setup
- In the UniFi OS web UI, open Settings → Control Plane → Integrations, click Create API Key, and copy the key.
- In GEM admin, add a device with the UniFi Protect (NVR + Cameras) driver, placed under a Security or Cameras subsystem. One GEM device represents the whole console — you do not add a separate device per camera.
- Set the device attributes:
ip— LAN IP or hostname of the UniFi OS console.api_key— the key you copied in step 1 (stored encrypted).
- Save and reload. On boot the driver reaches the console to confirm the key, then enumerates the cameras, lights, and chimes it manages.
- Run the
get_camerascommand to print every discovered camera with its id (a long alphanumeric string) and friendly name. - For each camera you want to track in GEM, add a zone under the same subsystem, bind it to this device, and set its address to that camera id. The driver writes per-camera state onto these zones.
GEM does not create zones automatically from discovery. A camera only surfaces state once a zone whose address is its id exists and is bound to the console device; cameras without a matching zone are enumerated but tracked silently. Use get_cameras to copy the exact id — the long alphanumeric string, never the friendly name.
Attribute reference
Device
| Attribute | Required | Description |
|---|---|---|
ip | yes | LAN IP / hostname of the UniFi OS console. |
api_key | yes | Console API key. Stored encrypted; sent in the X-API-KEY header. |
port | no | HTTPS port. Defaults to 443; a blank, non-numeric or out-of-range value also uses 443 (with one warning in the log). |
status_interval | no | Camera-state poll interval in milliseconds. Defaults to 5000. Values below 1000 poll every 1000; 0, negative or unreadable values use 5000. |
enable_events | no | Subscribe to the real-time event stream. Defaults to true; set false (or 0, no, off) to fall back to polling only. |
rex_inputs | no | Relay inputs wired to request-to-exit buttons, as relayId:channel separated by commas (ids from get_relays). Only these fire rex rules. |
rex_trigger | no | Which circuit change is a REX press: closed (default — a normally-open button) or open. |
Zone
| Attribute | Description |
|---|---|
state | Camera zones: motion while the camera reports motion, clear otherwise. Relay output zones: on / off. Relay input and door sensor zones: open / closed. Driver-managed, real-time when the event stream is up. |
last_motion | Unix-millisecond timestamp of the last reported motion event. Driver-managed. |
ring | Doorbell ring flag; pulses 1 → 0 (cleared by the event's end frame, or after 3 s if none arrives). Driver-managed. |
last_ring | Unix-millisecond timestamp of the last doorbell ring. Driver-managed. |
person, vehicle, animal, package, license_plate, … | Smart-detection flags, 1 while the detection is active and 0 when it ends. One attribute per detection type the camera reports (names are the Protect types converted to snake_case). A flag auto-clears after 120 s if the end-of-event frame is lost. |
Zone address is required. Its form picks what the zone tracks (an address typed in a different letter case from the console's id still matches, as long as only one zone matches it):
| Address | Zone tracks |
|---|---|
<cameraId> | A camera or doorbell (id from get_cameras — NOT the friendly name). |
<sensorId> | An UP-Sense door / window sensor (id from get_sensors). |
<relayId>:<output> | A relay output, e.g. 66d025b301ebc903e80003ea:0. |
<relayId>:input:<channel> | A relay input, e.g. 66d025b301ebc903e80003ea:input:1. |
Smart-detection flags only appear for detection types that are enabled on the camera in the Protect UI (Settings → the camera → Smart Detections). Trigger automations on the flag's change to 1 — e.g. person 1 on the driveway camera arms the floodlight macro.
Commands
| Command | Args | Description |
|---|---|---|
get_info | — | Returns controller meta (Protect application version, etc.). |
get_cameras | — | Lists all cameras and refreshes the discovered-camera list GEM keeps for this console. |
get_lights | — | Lists Protect-managed flood lights. |
get_chimes | — | Lists Protect-managed chimes. |
snapshot | address | Grabs a still from the camera at that zone address and saves it under the server's snapshots folder. Returns the served path /snapshots/<device-id>-<address>.jpg, usable directly in a UI image widget or a notification. |
set_recording_mode | address, mode | Mode is one of always, never, detections, schedule. |
set_status_light | address, enabled | Turns the camera status LED on (enabled = true, 1, on or yes) or off (anything else). |
get_relays | — | Lists Protect relay devices with their output and input channels. |
get_sensors | — | Lists Protect sensors with their open state. |
activate_relay_output | address, state, pulse_duration | Drives a relay output. address is relayId:output. state is on or off; leave it out to toggle. With state on, pulse_duration (ms) turns the output back off — a strike or gate pulse. |
snapshot with no address photographs the doorbell that rang in the last 15 seconds. This is how a call rule's access-log entry gets a picture without a fixed camera.
Addresses are trimmed before they are sent. An address left as an unfilled placeholder ([@zone.address]) is refused with an error instead of being sent to the console. snapshot returns an error, and saves nothing, when the console answers with something other than an image (an empty body, or a login or error page).
Event stream
The driver holds a WebSocket open to /proxy/protect/integration/v1/subscribe/events (same X-API-KEY auth as the REST calls) and applies event frames as they arrive:
- motion — zone
stateflips tomotionon the start frame and back toclearon the end frame;last_motiongets the start timestamp. - smartDetectZone / smartDetectLine / smartAudioDetect — each detected type sets its per-type flag to
1, cleared to0by the end frame. - ring — writes
last_ringand pulsesring, and firescallaccess rules on this device. - relayInputChanged — writes
open/closedon arelayId:input:nzone, and firesrexrules for inputs listed inrex_inputs. - sensorOpened / sensorClosed — writes
open/closedon the sensor's zone.
Relay output state isn't carried on the stream. While any relayId:output zone exists, the status_interval poll also reads the relay list.
The socket is health-checked with a 30 s ping/pong heartbeat; a silent socket is terminated and redialed with capped backoff (5 s doubling to 60 s). The backoff resets once the console sends something on the new socket, so a console that accepts the connection and drops it straight away keeps backing off. While the stream is down the status_interval poll reconciles motion state exactly as older builds did, so the only thing lost during an outage is latency.
If the device is reloaded or disabled while a ring or smart-detection flag is still 1, the driver sets it back to 0, so the next ring or detection still changes the value.
Connection and polling
- Console unreachable at startup. If the console does not answer at all when GEM connects (powered off, network down), the driver tries again after 30 s, doubling up to every 5 minutes, until it answers. A console that answers with
401or404(wrong key, old firmware) is not retried, because that needs a configuration change and a reload. - Poll back-off. After 3 failed camera polls in a row, the poll slows down, doubling up to once a minute (never faster than
status_interval). It goes back to normal on the first good poll. The failure is logged once asunifi protect status poll failed, with a summary line every 15 minutes while it continues. - A
meta/inforeply that isn't JSON (for example a login page served by a proxy) counts as not connected.
Access control
The console can be the access device on an access control rule, the same way a 2N intercom is. What it supports:
| Rule type | Fired by | Notes |
|---|---|---|
call | A doorbell ring | The rule's log entry records the camera id that rang. |
rex | A relay input listed in rex_inputs | Fires on the rex_trigger edge. The log records relayId:channel. |
pin / rfid | — | Not supported. See the limitations below. |
A typical front door:
- Wire the strike to a relay output and add a zone addressed
relayId:0. - Wire the REX button to relay input 0 and set
rex_inputstorelayId:0. - Wire the door contact (normally closed) to input 1 and add a zone addressed
relayId:input:1. - Create a
rexrule on the console device. Its action isactivate_relay_outputon the strike zone withstateonandpulse_duration5000. Set a held-open threshold on the rule to watch the contact zone. - Create a
callrule for the doorbell to get a log entry and a snapshot, and to run a chime or notification macro.
An input that isn't in rex_inputs never fires a rex rule. This keeps a door contact on the same relay from unlocking its own door when the door opens.
One device, every door. A rule is attached to the console device, not to a single door. A call rule fires for a ring on any doorbell the console manages, and a rex rule for any input in rex_inputs. Where two doors need different actions, drive each one from a trigger on its own zone (ring, or the input zone's state).
Snapshots. Set camera_zone_id on the console device to always photograph one camera. Leave it empty and a call rule photographs the doorbell that rang. A rex rule has no camera of its own, so it only gets a photo when camera_zone_id is set.
Door position. Relay input zones read circuitClosed as closed. With a normally-closed magnetic contact that means the door is closed. Wire the contact that way, or held-open and forced-door detection will read the door backwards.
Limitations
-
No PIN / card / fingerprint rules. Protect reports NFC card and fingerprint scans, but only as the UniFi Identity user the console matched. It never sends the card number, so the scan can't be checked against a GEM user's credentials. Credentials are enrolled and checked in UniFi. GEM can't push users to the console either, because the Protect API's user endpoints are read-only.
-
Lights and chimes are read-only. They show up in discovery but per-zone control of flood lights / chime tones is not yet wired through.
-
No live video. Snapshots only. RTSPS stream URLs are exposed by the API but not surfaced as a command in this build.
-
No camera-side configuration writes beyond recording mode and status LED. Privacy zones, motion sensitivity, and detection types are reachable through the API but not exposed yet.
Troubleshooting
/meta/inforeturns 404 — the console firmware predates the Integrations API. Upgrade UniFi OS to 3.2 or newer.activate_relay_outputreturnsstatus 503— the relay is offline or out of range of its link station. Check it in the Protect UI.- A
rexrule never fires — check thatrex_inputsnames the relay id exactly asget_relaysprints it, with the right channel, and thatrex_triggermatches the button's wiring. /meta/inforeturns 401 — the API key is wrong, expired, or was deleted. Mint a new one and update theapi_keyattribute.- Snapshots return
status 400for some cameras — a few low-power battery cameras only return snapshots while their stream is active. Wake the camera in the Protect UI first. set_recording_mode schedulereturns 400 — the camera has no schedule configured on the controller. Save a schedule in the Protect UI before sending this mode.- All requests time out — confirm 443/tcp from the GEM host to the console. If you front the console behind a reverse proxy, the proxy must forward
/proxy/protect/integration/v1/and pass through theX-API-KEYheader verbatim (including the WebSocket upgrade for/subscribe/events). - Motion stopped triggering in real time — grep server logs for
unifi protect event socket.heartbeat timeoutmeans the network path died silently (the driver terminates and reconnects on its own);code: 1006is an abnormal network drop;code: 1000is a console-initiated close (reboot / firmware update);reconnectingfollowed by a(repeated N times in Xm)summary means the console is unreachable or the firmware predates the subscribe endpoints — motion still works via the poll in the meantime. Repeated dial errors and closes of sockets that never opened are also collapsed this way; theconnected ... after N reconnect attempt(s)line says how many tries it took. - Smart-detection flags never appear — the detection types must be enabled per camera in the Protect UI, and the camera must support them (AI-series or G4+/G5 models).
Related documentation
- Devices — adding the UniFi OS console as a single device.
- Zones — creating one zone per camera, addressed by the camera id.
- Subsystems — the Security / Cameras subsystem the console and its camera zones live under.
- Commands — running
snapshot,set_recording_mode, andset_status_lightfrom the admin UI, macros, or scenes. - Triggers — firing automations off a camera zone's
statechanging tomotion, a smart-detection flag likepersongoing to1, or a doorbellring. - ONVIF — the vendor-neutral camera path to use when you need RTSP/ONVIF video streams rather than the Protect Integrations API.