Skip to main content

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). Lights and chimes are listed but not yet controllable.

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/integrations/v1/ (UniFi OS 3.2+).
  • 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

  1. In the UniFi OS web UI, open Settings → Control Plane → Integrations, click Create API Key, and copy the key.
  2. 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.
  3. 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).
  4. Save and reload. On boot the driver reaches the console to confirm the key, then enumerates the cameras, lights, and chimes it manages.
  5. Run the get_cameras command to print every discovered camera with its id (a long alphanumeric string) and friendly name.
  6. 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.
One zone per camera — created by you

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

AttributeRequiredDescription
ipyesLAN IP / hostname of the UniFi OS console.
api_keyyesConsole API key. Stored encrypted; sent in the X-API-KEY header.
portnoHTTPS port. Defaults to 443.
status_intervalnoCamera-state poll interval in milliseconds. Defaults to 5000.
enable_eventsnoSubscribe to the real-time event stream. Defaults to true; set false to fall back to polling only.

Zone

AttributeDescription
statemotion while a camera reports motion, clear otherwise. Driver-managed, real-time when the event stream is up.
last_motionUnix-millisecond timestamp of the last reported motion event. Driver-managed.
ringDoorbell ring flag; pulses 10 (cleared by the event's end frame, or after 3 s if none arrives). Driver-managed.
last_ringUnix-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 and must be the camera id from get_cameras (NOT the friendly name).

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

CommandArgsDescription
get_infoReturns controller meta (Protect application version, etc.).
get_camerasLists all cameras and refreshes the discovered-camera list GEM keeps for this console.
get_lightsLists Protect-managed flood lights.
get_chimesLists Protect-managed chimes.
snapshotaddressGrabs 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_modeaddress, modeMode is one of always, never, detections, schedule.
set_status_lightaddress, enabledToggles the camera status LED.

Event stream

The driver holds a WebSocket open to /proxy/protect/integrations/v1/subscribe/events (same X-API-KEY auth as the REST calls) and applies event frames as they arrive:

  • motion — zone state flips to motion on the start frame and back to clear on the end frame; last_motion gets the start timestamp.
  • smartDetectZone / smartDetectLine / smartAudioDetect — each detected type sets its per-type flag to 1, cleared to 0 by the end frame.
  • ring — writes last_ring and pulses ring.

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). 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.

Limitations

  • 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/info returns 404 — the console firmware predates the Integrations API. Upgrade UniFi OS to 3.2 or newer.
  • /meta/info returns 401 — the API key is wrong, expired, or was deleted. Mint a new one and update the api_key attribute.
  • Snapshots return status 400 for 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 schedule returns 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/integrations/v1/ and pass through the X-API-KEY header verbatim (including the WebSocket upgrade for /subscribe/events).
  • Motion stopped triggering in real time — grep server logs for unifi protect event socket. heartbeat timeout means the network path died silently (the driver terminates and reconnects on its own); code: 1006 is an abnormal network drop; code: 1000 is a console-initiated close (reboot / firmware update); repeated reconnecting ... attempt: N means the console is unreachable or the firmware predates the subscribe endpoints — motion still works via the poll in the meantime.
  • 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).
  • 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, and set_status_light from the admin UI, macros, or scenes.
  • Triggers — firing automations off a camera zone's state changing to motion, a smart-detection flag like person going to 1, or a doorbell ring.
  • ONVIF — the vendor-neutral camera path to use when you need RTSP/ONVIF video streams rather than the Protect Integrations API.