Skip to main content

UniFi Access

Commercial access-control for Ubiquiti's UniFi Access platform — UA-Hub, UA-Ultra, and Access running on UDM-Pro / UNVR consoles via UniFi OS. Uses the local Developer REST API on the controller with a bearer API token. One GEM device row represents the Access controller; each zone represents a door.

Prerequisites

  • UniFi Access 2.x or newer with the Developer API enabled. The API is exposed on the controller (Access hub, UA-Ultra, or UniFi OS console).
  • API token. Mint one under Settings → Advanced → API Token. The value is shown once — save it before navigating away. This token authorises every request.
  • Network reach.
    • Standalone Access hub / UA-Ultra: TCP/12445 reachable from GEM to the controller (HTTPS with a self-signed cert).
    • Access on UniFi OS (UDM-Pro, UNVR): TCP/443 reachable, API URL prefixed with /proxy/access/.
  • Doors already enrolled. GEM does not do door onboarding — enrol doors via the UniFi Access mobile / web UI first.

Setup

1. Mint the API token

  1. Open the UniFi Access web UI.
  2. Go to Settings → Advanced → API Token.
  3. Click Create API Token. Name it something like gem-controller.
  4. Copy the token. This is the only time it will be shown.

2. Add the device in GEM

Under Admin → Devices → Add:

FieldValue
Driverunifi_access
Namee.g. access_ctrl
Attribute ipController IP or hostname
Attribute tokenBearer token from step 1 (auto-encrypted)

If Access is running on UniFi OS (UDM-Pro / UNVR) rather than a standalone Access hub:

AttributeValue
port443
api_base/proxy/access/api/v1/developer

Save. The driver will hit /doors to verify the token, then start the two polling loops.

3. Discover doors

Run the get_doors command from the Command Console. Each returned entry looks like:

{
"id": "2f2f42d0-1234-4a5b-9c8d-abcdef012345",
"name": "Front Lobby",
"floor_id": "…",
"door_lock_relay_status": "lock",
"door_position_status": "closed",
"is_bind_hub": true
}

The id field is what you use as the zone address.

4. Create zones — one per door

Under Admin → Zones → Add, for each door:

FieldValue
DeviceThe Access device from step 2
Subsystemaccess_control (or door — either works with the state map)
AddressDoor UUID from get_doors

Save. The next poll cycle (default 60 s) will populate state, door_position, door_name, and online.

Attributes

Required (device)

NameTypeDescription
ipstringLAN IP or hostname of the Access controller.
tokenstring (secure)Bearer API token minted under Access → Settings → Advanced → API Token.

Optional (device)

NameTypeDefaultDescription
portint12445TCP port. Use 443 for UniFi OS proxied access.
api_basestring/api/v1/developerAPI path prefix. Use /proxy/access/api/v1/developer for UniFi OS.
status_intervalint60000Milliseconds between door-state polls.
event_poll_intervalint30000Milliseconds between event-log polls. 0 disables event polling.
unlock_secondsint5Fallback unlock duration when a zone-level override is not set. Bounded 1-60. Note: Access enforces its own hold time regardless — this is a hint that surfaces on the response payload.
released_state_msint5000How long zone.state stays at released after an unlock before the next poll snaps it back.

Optional (zone)

NameTypeDescription
unlock_secondsintPer-door override of the device-level default.

Populated by the driver

The polling loops write these attributes onto each door zone:

NameSourceNotes
statedoor_lock_relay_statuslocked / unlocked / released (transient).
door_positiondoor_position_statusopen / closed / unknown. Requires a door-position sensor to be wired to the reader / hub.
door_namenameThe friendly name from Access.
onlineis_bind_hubtrue if the door is bound to a reachable Access hub.
last_event/system/logsMost recent event type (badge_read / door_forced / …).
last_event_at/system/logsISO timestamp of the event.
last_event_user/system/logsName of the user attached to the event, if any.

Commands

CommandArgsNotes
unlockaddress, duration (optional)Buzz-open the door (one-shot). Duration is a hint — Access enforces its own hold time.
lock / reset_lock_ruleaddressReset the lock rule to the normal schedule. Locks the door if a keep_unlock was active.
keep_unlockaddressHold the door unlocked until reset. Use for propping doors open.
keep_lockaddressHold the door locked, ignoring valid credentials. Lock-down.
get_doorsEnumerate all doors. Cache is refreshed with the response.
get_statusaddressRead a single door.
get_eventssince (optional epoch ms)Read the system log.
get_usersList Access users.
get_visitorsList active visitors.
refreshForce a door status poll now.
raw_requestpath, method, bodyEscape hatch — pass-through to any /api/v1/developer/* endpoint.

Zone address format

Every zone address is a UniFi Access door UUID (36-character, hyphenated). Get them via get_doors.

Known limitations

  • Unlock duration is Access-controlled. The Developer API's unlock endpoint is a one-shot buzz whose hold time is set by the door schedule in Access, not by anything passed on the API call. To hold a door open longer, use keep_unlock and later lock (reset).
  • Identity provisioning is out of scope. Creating users, issuing credentials, and assigning schedules is not exposed. Manage those in the Access web / mobile UI.
  • Event log is polled, not streamed. The Developer API exposes a WebSocket for events, but this driver polls /system/logs with a since_ms watermark instead. Set event_poll_interval=0 to skip event polling if you only care about door state.
  • UniFi Identity Enterprise / SSO is not consumed. The driver uses the local API token, not the tenant OAuth.
  • UDM-Pro deployments put the API behind /proxy/access/api/v1/developer. Set port=443 and api_base=/proxy/access/api/v1/developer — the reference implementation was authored against the standalone hub URL first.

Troubleshooting

http 401 — check that the API token is valid. The token was revoked, expired, or copied incorrectly. Mint a new one in the Access UI and paste it into the token attribute.

http 404 on connect. The api_base path is wrong. For a standalone Access hub, /api/v1/developer is correct on port 12445. For UniFi OS deployments, use /proxy/access/api/v1/developer on port 443.

Zone state stays unknown after unlock. The door doesn't have a position sensor wired to the hub. door_lock_relay_status will still reflect lock / unlock; only door_position requires the sensor.

Events stop populating. Check the token still has read scope. If you disabled Developer API access on the controller after adding it here, the polling loop logs a 401 warning each cycle. Set event_poll_interval=0 while token access is out.

get_users returns an empty array but the Access UI shows users. Some tenants scope users by site — the Developer API's /users returns the currently-active site by default. Use raw_request with a site-scoped path if you need cross-site enumeration.

Follow-ups

  • Consume the Developer API WebSocket (/subscribe/events) for push-based events instead of polling.
  • Expose credential issuance / user provisioning as a separate management surface.
  • Surface schedule information on zones (currently only state / door_position are polled).
  • Sample-and-cache door photos where cameras are paired to doors.