Skip to main content

aiounifiaccess

Async Python client for the UniFi Access API.

  • Full REST API coverage — all endpoints from API v4.0.10
  • Real-time events — WebSocket listener with auto-reconnect and typed event models
  • Webhook receiver — built-in HTTP server for webhook events with signature verification
  • Idempotent webhook registration — ensure_endpoint() and setup_webhook() handle subscription management
  • Pydantic v2 models — fully typed request/response objects
  • Async-native — built on aiohttp for composability with other async libraries

Installation

pip install aiounifiaccess

Quick Start

import asyncio
import os
from aiounifiaccess import UniFiAccessClient

async def main():
    async with UniFiAccessClient(
        host=os.environ["UNIFI_ACCESS_HOST"],
        api_token=os.environ["UNIFI_ACCESS_TOKEN"],
    ) as client:
        # List all users
        users, pagination = await client.users.list()
        for user in users:
            print(f"{user.first_name} {user.last_name} ({user.status})")

        # Get a specific door
        door = await client.doors.get("door-id-here")
        print(f"{door.name}: {door.door_lock_relay_status}")

        # Remote unlock
        await client.doors.unlock("door-id-here", actor_name="API Script")

asyncio.run(main())

Real-Time Events

UniFi Access delivers events through two channels:

Channel Delivery Event types
WebSocket Persistent connection Doorbell rings, remote unlocks, device/location state updates
Webhook Controller POSTs to your endpoint Door unlocks (NFC/PIN/fingerprint), DPS status, schedules, visitors

Notably, credential-based door unlock events (access.door.unlock) are webhook-only — they are not delivered over the WebSocket. To receive all event types, you need both channels.

The WebSocket also delivers undocumented status events not listed in the official API reference. These are typed and parsed by the library:

Event Model Description
access.data.device.update DeviceUpdateEvent Full device state with DPS, lock relays, power, wiring
access.data.v2.device.update DeviceUpdateV2Event Lightweight device change notification with changed-field metadata
access.data.v2.location.update LocationUpdateV2Event Building/floor/door location state changes (most frequent)
access.data.location.update LocationUpdateEvent Full location state
access.data.setting.update SettingUpdateEvent Controller settings changes

The DeviceUpdateEvent is particularly useful — its configs list contains key/value pairs for DPS states, lock relay states, and power readings. For example, detecting "door left open":

from aiounifiaccess import DeviceUpdateEvent

@client.on(DeviceUpdateEvent)
async def handle_device_update(event: DeviceUpdateEvent):
    for cfg in event.data.configs:
        if cfg.tag == "hub_action" and cfg.key.endswith("_dps"):
            port = cfg.key.replace("input_", "").replace("_dps", "")
            state = "open" if cfg.value == "on" else "closed"
            print(f"Port {port} DPS: {state}")

WebSocket Only

If you only need doorbell and remote unlock events:

import asyncio
import os
from aiounifiaccess import UniFiAccessClient, RemoteViewEvent

async def main():
    async with UniFiAccessClient(
        host=os.environ["UNIFI_ACCESS_HOST"],
        api_token=os.environ["UNIFI_ACCESS_TOKEN"],
    ) as client:

        @client.on(RemoteViewEvent)
        async def handle_doorbell(event: RemoteViewEvent):
            print(f"Doorbell ring at {event.data.door_name}")

        await client.listen()

asyncio.run(main())

To receive all events including credential-based door unlocks, use setup_webhook(). This registers a webhook subscription on the UniFi controller, starts a local HTTP receiver, and runs both channels concurrently:

import asyncio
import os
from aiounifiaccess import (
    UniFiAccessClient,
    DoorUnlockEvent,
    DoorPositionEvent,
    RemoteViewEvent,
)

async def main():
    async with UniFiAccessClient(
        host=os.environ["UNIFI_ACCESS_HOST"],
        api_token=os.environ["UNIFI_ACCESS_TOKEN"],
    ) as client:

        # Register webhook on the controller and start local receiver.
        # The controller will POST events to this URL.
        await client.setup_webhook("https://myserver:8080/webhook")

        @client.on(DoorUnlockEvent)
        async def handle_unlock(event: DoorUnlockEvent):
            actor = event.data.actor.name
            door = event.data.location.name
            method = event.data.object.authentication_type
            print(f"{actor} unlocked {door} via {method}")

        @client.on(DoorPositionEvent)
        async def handle_dps(event: DoorPositionEvent):
            print(f"{event.data.location.name}: {event.data.object.status}")

        @client.on(RemoteViewEvent)
        async def handle_doorbell(event: RemoteViewEvent):
            print(f"Doorbell ring at {event.data.door_name}")

        # Runs both WebSocket and webhook receiver until stopped
        await client.listen()

asyncio.run(main())

setup_webhook() is idempotent — if a webhook subscription already exists for the same URL and event set, it reuses it. If the URL matches but the events differ, it updates the existing subscription. By default it subscribes to all known webhook event types.

Subscribing to Specific Events

Use WebhookEventType to subscribe to only the events you need:

from aiounifiaccess import WebhookEventType

await client.setup_webhook(
    "https://myserver:8080/webhook",
    events=[
        WebhookEventType.DOOR_UNLOCK,
        WebhookEventType.DEVICE_DPS_STATUS,
    ],
)

Available event types:

Enum value Event string Description
DOOR_UNLOCK access.door.unlock All door unlock events (NFC, PIN, fingerprint, remote)
DEVICE_DPS_STATUS access.device.dps_status Door position sensor changes
DOORBELL_INCOMING access.doorbell.incoming Doorbell ring
DOORBELL_COMPLETED access.doorbell.completed Doorbell accepted/declined/cancelled
DOORBELL_INCOMING_REN access.doorbell.incoming.REN Request-to-Enter button
DEVICE_EMERGENCY_STATUS access.device.emergency_status Emergency mode changes
UNLOCK_SCHEDULE_ACTIVATE access.unlock_schedule.activate Unlock schedule activated
UNLOCK_SCHEDULE_DEACTIVATE access.unlock_schedule.deactivate Unlock schedule deactivated
TEMPORARY_UNLOCK_START access.temporary_unlock.start Temporary unlock started
TEMPORARY_UNLOCK_END access.temporary_unlock.end Temporary unlock ended
VISITOR_STATUS_CHANGED access.visitor.status.changed Visitor status changed

Manual Webhook Configuration

If you prefer to manage webhook registration separately (or already have one registered), pass the secret directly:

async with UniFiAccessClient(
    host=os.environ["UNIFI_ACCESS_HOST"],
    api_token=os.environ["UNIFI_ACCESS_TOKEN"],
    webhook_secret="your_webhook_secret",
    webhook_port=8080,
) as client:
    # ...handlers...
    await client.listen()

Or use the webhook manager API directly:

# Idempotent registration (create or reuse)
endpoint = await client.webhooks.ensure_endpoint(
    "https://myserver:8080/webhook",
    "my-app",
)
print(f"Secret: {endpoint.secret}")

# Or manual CRUD
endpoints = await client.webhooks.list_endpoints()
await client.webhooks.delete_endpoint(endpoint.id)

Standalone Webhook Receiver

The WebhookReceiver can be used independently of the full client:

import asyncio
from aiounifiaccess import WebhookReceiver, DoorUnlockEvent
from aiounifiaccess.events.handler import EventHandler

handler = EventHandler()

@handler.on(DoorUnlockEvent)
async def handle(event: DoorUnlockEvent):
    print(f"{event.data.actor.name} unlocked {event.data.location.name}")

async def main():
    receiver = WebhookReceiver("your_webhook_secret", port=8080)
    await receiver.listen(handler)

asyncio.run(main())

Webhook Signature Verification

For custom webhook handling outside the built-in receiver:

from aiounifiaccess import verify_webhook_signature

is_valid = verify_webhook_signature(
    secret="your_webhook_secret",
    signature_header=request.headers["Signature"],
    body=await request.read(),
)

Debug Logging

To see all raw incoming messages on either channel:

import logging

# Both channels
logging.getLogger("aiounifiaccess.events").setLevel(logging.DEBUG)

# Or individually
logging.getLogger("aiounifiaccess.events.listener").setLevel(logging.DEBUG)   # WebSocket
logging.getLogger("aiounifiaccess.events.receiver").setLevel(logging.DEBUG)   # Webhook

API Managers

Manager Attribute Endpoints
Users client.users 29
Visitors client.visitors 13
Access Policies client.access_policies 15
Credentials client.credentials 17
Doors client.doors 13
Devices client.devices 4
System Logs client.system_logs 4
Identity client.identity 6
Webhooks client.webhooks 5
Server client.server 2

Compatibility

Library Version API Reference Version
0.1.x 4.0.10

Requirements

  • Python 3.11+
  • aiohttp >= 3.9
  • pydantic >= 2.0

License

MIT

Metadata

Release files for aiounifiaccess 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiounifiaccess 0.2.3
File Size Uploaded Provenance Actions
aiounifiaccess-0.2.3.tar.gz 36.5 kB With provenance Details for aiounifiaccess-0.2.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for aiounifiaccess 0.2.3
File Interpreter ABI Platform Actions
aiounifiaccess-0.2.3-py3-none-any.whl Python 3 none any Details for aiounifiaccess-0.2.3-py3-none-any.whl

Total release size: 79.0 kB

Release files / aiounifiaccess-0.2.3.tar.gz

Download URL aiounifiaccess-0.2.3.tar.gz
Size 36.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a6a1b9cd1a81416a0c8510d686aae92d34e8558c53b1c73fd4a81108db36ba5f
BLAKE2b-256 checksum
How to use checksums
e32dee642b1999da56e62d2d77c4af2203e7bf22d66ac4b16bb47e8446d9baaa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 15, 2026.

Transparency log

Release files / aiounifiaccess-0.2.3-py3-none-any.whl

Download URL aiounifiaccess-0.2.3-py3-none-any.whl
Size 42.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f324bd923a0f0a960bbc6b59452cd134609242dbae6e59d70a0250d7ef2dfac8
BLAKE2b-256 checksum
How to use checksums
54fc252d69ef95faab896d3aaab36fd2b0716a37eca5c3180e47292f9894e235
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 15, 2026.

Transparency log

Security for aiounifiaccess 0.2.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for aiounifiaccess-0.2.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page