Skip to content

Plugin API

Core plugin lifecycle: BasePlugin, the manifest contract, optional interfaces (discovery, notifier, detection).

camera_ui_sdk.plugin

APIListener module-attribute

APIListener = Callable[[], None] | Callable[[], Awaitable[None]]

Listener for plugin lifecycle events. May be a plain callable or a coroutine function — the runtime awaits the latter.

PythonVersion module-attribute

PythonVersion = Literal['3.11', '3.12']

Python interpreter major.minor version a Python plugin requires. The host ensures a matching interpreter exists in its venv pool before launching the plugin; Node and Go plugins ignore this field.

OAuthStatus module-attribute

OAuthStatus = Literal['disconnected', 'awaiting_user', 'polling', 'connected', 'error']

Lifecycle phase of an OAuth provider connection, carried in OAuthState.status.

API_EVENT

Bases: Enum

Lifecycle events emitted on the PluginAPI EventEmitter.

Plugins subscribe with api.on(API_EVENT.X, handler) to react to host-driven phase changes.

FINISH_LAUNCHING class-attribute instance-attribute

FINISH_LAUNCHING = 'finishLaunching'

Emitted exactly once after the plugin has been constructed, all assigned cameras have been wired up, and configureCameras() has returned. Use this to start background work that must wait until the camera set is stable (timers, model warm-up, outbound connections).

SHUTDOWN class-attribute instance-attribute

SHUTDOWN = 'shutdown'

Emitted when the host is tearing the plugin down (graceful stop, reload or process exit). Listeners must release resources synchronously enough to finish before the host kills the process — open files, sockets, timers, child processes.

PluginAPI

Bases: Protocol

The PluginAPI is injected into the plugin at runtime and exposes the system services the plugin is allowed to talk to. It also acts as an EventEmitter for plugin lifecycle events (see :class:API_EVENT).

The API is passed to the plugin constructor and should be stored for later use.

Example
class MyPlugin(BasePlugin):
    def __init__(self, logger, api, storage):
        super().__init__(logger, api, storage)
        # Access FFmpeg path
        ffmpeg = await api.coreManager.getFFmpegPath()

coreManager property

coreManager: CoreManager

System-level operations such as the FFmpeg path and the server addresses used for media URLs (HTTP/RTSP).

deviceManager property

deviceManager: DeviceManager

Owns the camera devices assigned to this plugin and publishes camera-state changes.

downloadManager property

downloadManager: DownloadManager

Mints token-protected download URLs for files the plugin wants to expose to the UI (e.g. clip exports, snapshots).

notificationManager property

notificationManager: NotificationManager

Publishes notifications into the host so they fan out to every installed Notifier-plugin and the in-app UI. Requires :attr:PluginCapability.PublishNotifications in the plugin contract.

storagePath property

storagePath: str

Absolute path to the plugin's writable storage directory. Created and cleaned up by the host. Use it for caches, models, sqlite/bolt files.

on

on(event: API_EVENT, f: APIListener) -> Any

Subscribe to a lifecycle event.

Parameters:

Name Type Description Default
event API_EVENT

Lifecycle event to subscribe to.

required
f APIListener

Event listener (sync callable or coroutine function).

required

Returns:

Type Description
Any

Self for chaining.

once

once(event: API_EVENT, f: APIListener) -> Any

Subscribe to a lifecycle event for one delivery only.

Parameters:

Name Type Description Default
event API_EVENT

Lifecycle event to subscribe to.

required
f APIListener

Event listener (sync callable or coroutine function).

required

Returns:

Type Description
Any

Self for chaining.

off

off(event: API_EVENT, f: APIListener) -> None

Remove a previously registered listener (alias of :meth:removeListener).

Parameters:

Name Type Description Default
event API_EVENT

Lifecycle event the listener was registered for.

required
f APIListener

Listener to remove.

required

removeListener

removeListener(event: API_EVENT, f: APIListener) -> None

Remove a previously registered listener.

Parameters:

Name Type Description Default
event API_EVENT

Lifecycle event the listener was registered for.

required
f APIListener

Listener to remove.

required

removeAllListeners

removeAllListeners(event: API_EVENT | None = None) -> None

Remove every listener for event, or every listener entirely if no event is given.

Parameters:

Name Type Description Default
event API_EVENT | None

Lifecycle event whose listeners should be removed, or None to clear all listeners.

None

PluginCapability

Bases: str, Enum

Permission a plugin requests so it can call a host-provided system feature. Each capability gates one outgoing SDK call — calls without the matching capability are rejected by the host.

PublishNotifications class-attribute instance-attribute

PublishNotifications = 'publishNotifications'

Grants the plugin permission to call api.notificationManager.publish. Without this capability the host silently drops published notifications and logs an error.

PluginContract

Bases: TypedDict

Manifest contract a plugin declares so the host knows what it does and what it needs at load time. Validated by helper.py before the plugin is started.

name instance-attribute

name: str

Stable, unique identifier for the plugin instance — used as the registry key, log prefix and the storage namespace.

role instance-attribute

role: PluginRole

Role of the plugin (see :class:PluginRole).

provides instance-attribute

provides: list[SensorType]

Sensor types the plugin produces. Empty for hubs and pure camera-controllers; required for sensor providers.

consumes instance-attribute

consumes: list[SensorType]

Sensor types the plugin reads from other plugins (e.g. a face plugin consumes camera video frames).

interfaces instance-attribute

interfaces: list[PluginInterface]

Capability flags the plugin implements (see :class:PluginInterface).

capabilities instance-attribute

capabilities: NotRequired[list[PluginCapability]]

Permissions the plugin requests to call host system features (see :class:PluginCapability). The host enforces these — calls without a matching capability are rejected.

pythonVersion instance-attribute

pythonVersion: NotRequired[PythonVersion]

Required Python interpreter version for Python plugins. Ignored by Node / Go plugins.

dependencies instance-attribute

dependencies: NotRequired[list[str]]

Extra package dependencies installed into the plugin's runtime (Go module paths for Go plugins; PyPI / npm names for Python and Node plugins).

PluginInfo

Bases: TypedDict

Lightweight handle identifying an installed plugin — used in RPC payloads and managers to refer to the plugin without shipping its full state.

id instance-attribute

id: str

Unique runtime ID assigned by the host (stable across restarts).

name instance-attribute

name: str

Plugin package name (matches PluginContract.name).

contract instance-attribute

contract: PluginContract

Full contract the plugin was loaded with.

PluginInterface

Bases: str, Enum

Capability flags a plugin advertises in its contract.

The host uses these to decide which RPC handlers to wire up and which UI affordances to show.

MotionDetection class-attribute instance-attribute

MotionDetection = 'MotionDetection'

Implements MotionDetectionInterface (video-based motion detection).

ObjectDetection class-attribute instance-attribute

ObjectDetection = 'ObjectDetection'

Implements ObjectDetectionInterface (e.g. person, vehicle, animal).

AudioDetection class-attribute instance-attribute

AudioDetection = 'AudioDetection'

Implements AudioDetectionInterface (event/keyword audio detection).

FaceDetection class-attribute instance-attribute

FaceDetection = 'FaceDetection'

Implements FaceDetectionInterface (face localisation + embeddings). The NVR owns matching against enrolled faces; the plugin only emits detections + embeddings.

LicensePlateDetection class-attribute instance-attribute

LicensePlateDetection = 'LicensePlateDetection'

Implements LicensePlateDetectionInterface (plate localisation + OCR).

ClassifierDetection class-attribute instance-attribute

ClassifierDetection = 'ClassifierDetection'

Implements ClassifierDetectionInterface (generic image classification emitting attribute/label pairs).

ClipDetection class-attribute instance-attribute

ClipDetection = 'ClipDetection'

Implements ClipDetectionInterface (CLIP image and text embeddings used for semantic search).

DiscoveryProvider class-attribute instance-attribute

DiscoveryProvider = 'DiscoveryProvider'

Implements DiscoveryProvider — plugin can scan the network for new cameras and adopt them. Only valid for camera-controlling roles.

NVR class-attribute instance-attribute

NVR = 'NVR'

Implements NVRInterface — persists events and recordings, and serves them back to the UI / mobile clients. Exactly one plugin per host fills this role at runtime.

Notifier class-attribute instance-attribute

Notifier = 'Notifier'

Implements NotifierInterface (get_devices, send_notification, ...). Lets the central NotificationManager dispatch notifications to this plugin regardless of role — see camera_ui_sdk/plugin/notifier.py.

OAuthCapable class-attribute instance-attribute

OAuthCapable = 'OAuthCapable'

Implements the OAuthCapable base interface (getOAuthMetadata, getOAuthState, disconnect) plus at least one flow sub-interface — see camera_ui_sdk/plugin/oauth.py.

OAuthDeviceFlow class-attribute instance-attribute

OAuthDeviceFlow = 'OAuthDeviceFlow'

Implements OAuthDeviceFlowCapable (RFC 8628 Device Authorization Grant).

OAuthAuthCodeFlow class-attribute instance-attribute

OAuthAuthCodeFlow = 'OAuthAuthCodeFlow'

Implements OAuthAuthCodeFlowCapable (Authorization Code Flow + PKCE).

OAuthClientCredentials class-attribute instance-attribute

OAuthClientCredentials = 'OAuthClientCredentials'

Implements OAuthClientCredentialsCapable (user-supplied client_id + client_secret).

PluginRole

Bases: str, Enum

Role a plugin plays in the system. The role decides which lifecycle hooks the host invokes and which contract validations apply.

Hub class-attribute instance-attribute

Hub = 'hub'

System-wide aggregator that attaches to cameras owned by other plugins to provide a cross-camera service (e.g. bridging cameras and sensors into a smart-home platform, or recording and notifications). A hub creates no cameras of its own and provides no sensors (provides must be empty); it attaches to cameras via the hub assignment and typically reads camera and sensor state through consumes.

SensorProvider class-attribute instance-attribute

SensorProvider = 'sensorProvider'

Adds sensors to existing cameras without owning the camera itself. Typical use: a detection plugin that consumes another plugin's video frames and emits motion / object / face detections back into the system.

CameraController class-attribute instance-attribute

CameraController = 'cameraController'

Manages cameras and their media streams (ONVIF, RTSP, generic IP, ...). The plugin is responsible for stream URLs, PTZ, snapshots, and the lifecycle hooks in BasePlugin. It does not produce sensors for foreign cameras.

CameraAndSensorProvider class-attribute instance-attribute

CameraAndSensorProvider = 'cameraAndSensorProvider'

Combined role: plugin both manages cameras and exposes sensors (its own cameras and, when consumes is set, also foreign cameras). Used by integrations that ship a complete camera + detection stack.

AudioDetectionInterface

Bases: Protocol

Interface implemented by plugins that perform audio event or keyword detection.

testAudioDetection async

testAudioDetection(audio_data: bytes, metadata: AudioMetadata, config: dict[str, Any]) -> AudioDetectionPluginResponse | None

Run detection on an audio buffer captured by the UI test panel; metadata carries the input MIME type (mpeg/wav/ogg).

audioDetectionSettings async

audioDetectionSettings() -> list[JsonSchema] | None

Return the JSON schema used to render the audio-detection settings form in the UI. Return None for no schema.

AudioDetectionPluginResponse

Bases: TypedDict

Response from an audio detection test.

AudioMetadata

Bases: TypedDict

Audio metadata for detection test requests.

BasePlugin

BasePlugin(logger: LoggerService, api: PluginAPI, storage: DeviceStorage[StorageT])

Bases: ABC, Generic[StorageT]

Base class every plugin extends.

It wires up the three dependencies the host injects (logger, PluginAPI, DeviceStorage) and declares the lifecycle methods the host calls on the plugin.

Lifecycle order: the host calls :meth:configureCameras once at startup with every camera already assigned to this plugin, then calls :meth:onCameraAdded / :meth:onCameraReleased as the user adds or removes cameras at runtime.

The generic type parameter StorageT types storage.values so plugin code gets autocompletion for its own settings shape.

Example
class MyStorageValues(TypedDict):
    model_path: str
    threshold: float


class MyPlugin(BasePlugin[MyStorageValues]):
    async def configureCameras(self, cameras: list[CameraDevice]) -> None:
        # storage.values is typed as MyStorageValues
        model_path = self.storage.values.get("model_path")  # str
        for camera in cameras:
            await self.onCameraAdded(camera)

    async def onCameraAdded(self, camera: CameraDevice) -> None:
        # Initialize camera controller
        pass

    async def onCameraReleased(self, camera_id: str) -> None:
        # Cleanup camera controller
        pass

storage_schema property

storage_schema: list[JsonSchema]

Override to register a JSON schema for the plugin-level settings form rendered in the UI. Default: no schema.

configureCameras abstractmethod async

configureCameras(cameras: list[CameraDevice]) -> None

Called once on startup with every camera already assigned to this plugin. The plugin should attach handlers, open vendor sessions, and warm up models. Raising aborts plugin startup.

Parameters:

Name Type Description Default
cameras list[CameraDevice]

Cameras already assigned to this plugin.

required

onCameraAdded abstractmethod async

onCameraAdded(camera: CameraDevice) -> None

Called whenever a camera is assigned to this plugin at runtime — after a discovery adoption (:meth:DiscoveryProvider.onAdoptCamera) or after the user re-assigns an existing camera in the UI. The plugin should set up the same per-camera state as in :meth:configureCameras.

Parameters:

Name Type Description Default
camera CameraDevice

The camera device that was added.

required

onCameraReleased abstractmethod async

onCameraReleased(cameraId: str) -> None

Called when a camera is unassigned from this plugin or deleted from the system. The plugin must release per-camera resources (sessions, timers, decoders) before returning.

Parameters:

Name Type Description Default
cameraId str

ID of the camera that was released.

required

ClassifierDetectionInterface

Bases: Protocol

Interface implemented by plugins that run a generic image classifier and emit attribute/label pairs (e.g. weather, scene, activity).

testClassifierDetection async

testClassifierDetection(image_data: bytes, metadata: ImageMetadata, config: dict[str, Any]) -> ClassifierDetectionPluginResponse | None

Run classification on a single image captured by the UI test panel and return the result for preview rendering.

detectClassifications async

detectClassifications(frame: VideoFrameData, config: dict[str, Any] | None = None) -> ClassifierDetectionPluginResponse | None

Run classification on a pre-decoded video frame.

classifierDetectionSettings async

classifierDetectionSettings() -> list[JsonSchema] | None

Return the JSON schema for the classifier-detection settings form in the UI. Return None for no schema.

ClassifierDetectionPluginResponse

Bases: TypedDict

Response from a classifier detection test.

ClipDetectionInterface

Bases: Protocol

Interface implemented by plugins that generate CLIP image and text embeddings used for semantic search over recorded events.

testClipEmbedding async

testClipEmbedding(image_data: bytes, metadata: ImageMetadata, config: dict[str, Any]) -> ClipDetectionPluginResponse | None

Run the CLIP image branch on a single image captured by the UI test panel.

detectClipEmbedding async

detectClipEmbedding(frame: VideoFrameData, config: dict[str, Any] | None = None) -> ClipDetectionPluginResponse | None

Run the CLIP image branch on a pre-decoded video frame.

getTextEmbedding async

getTextEmbedding(text: str) -> ClipTextEmbeddingResult

Run the CLIP text branch and return a single embedding vector usable for semantic-search queries against previously stored image embeddings.

clipSettings async

clipSettings() -> list[JsonSchema] | None

Return the JSON schema for the CLIP settings form in the UI. Return None for no schema.

ClipDetectionPluginResponse

Bases: TypedDict

Response from a CLIP embedding test.

ClipTextEmbeddingResult

Bases: TypedDict

Result of text-to-embedding conversion — a single embedding vector plus the model name used to produce it, so downstream code can refuse to mix embeddings from different models.

DiscoveryProvider

Bases: Protocol

Implemented by plugins that can scan the network for new cameras and adopt them. Only plugins with a camera-controlling role (CameraController or CameraAndSensorProvider) are queried for discovery.

onDiscoverCameras async

onDiscoverCameras() -> list[DiscoveredCamera]

Scan the network and return the cameras the plugin can offer for adoption. Called by the host on demand (UI rescan button) or on a polling schedule.

Returns:

Type Description
list[DiscoveredCamera]

Cameras currently discoverable by this plugin.

onGetCameraSettings async

onGetCameraSettings(camera: DiscoveredCamera) -> list[JsonSchemaWithoutCallbacks]

Return a JSON schema describing the form fields (credentials, transport options, ...) the user must fill in to adopt this specific discovered camera.

Parameters:

Name Type Description Default
camera DiscoveredCamera

The discovered camera the user is about to adopt.

required

Returns:

Type Description
list[JsonSchemaWithoutCallbacks]

Schema for the adoption form.

onAdoptCamera async

onAdoptCamera(camera: DiscoveredCamera, cameraSettings: dict[str, object]) -> CameraConfig

Probe the device with the user-provided settings and return the camera configuration the host should persist. The host then creates the camera and invokes :meth:BasePlugin.onCameraAdded on the plugin.

Parameters:

Name Type Description Default
camera DiscoveredCamera

The discovered camera being adopted.

required
cameraSettings dict[str, object]

Values entered into the adoption form.

required

Returns:

Type Description
CameraConfig

Final camera configuration for the host to persist.

FaceDetectionInterface

Bases: Protocol

Interface implemented by plugins that locate faces and emit per-face embeddings. The NVR owns matching against enrolled faces; the plugin only emits raw detections + embeddings.

testFaceDetection async

testFaceDetection(image_data: bytes, metadata: ImageMetadata, config: dict[str, Any]) -> FaceDetectionPluginResponse | None

Run face detection on a single image captured by the UI test panel and return the result for preview rendering.

detectFaces async

detectFaces(frame: VideoFrameData, config: dict[str, Any] | None = None) -> FaceDetectionPluginResponse | None

Run face detection on a pre-decoded video frame.

faceDetectionSettings async

faceDetectionSettings() -> list[JsonSchema] | None

Return the JSON schema for the face-detection settings form in the UI. Return None for no schema.

FaceDetectionPluginResponse

Bases: TypedDict

Response from a face detection test.

ImageMetadata

Bases: TypedDict

Image metadata for detection test requests.

LicensePlateDetectionInterface

Bases: Protocol

Interface implemented by plugins that locate license plates and run OCR on them.

testLicensePlateDetection async

testLicensePlateDetection(image_data: bytes, metadata: ImageMetadata, config: dict[str, Any]) -> LicensePlateDetectionPluginResponse | None

Run detection on a single image captured by the UI test panel and return the result for preview rendering.

detectLicensePlates async

detectLicensePlates(frame: VideoFrameData, config: dict[str, Any] | None = None) -> LicensePlateDetectionPluginResponse | None

Run detection on a pre-decoded video frame.

licensePlateDetectionSettings async

licensePlateDetectionSettings() -> list[JsonSchema] | None

Return the JSON schema for the license-plate-detection settings form in the UI. Return None for no schema.

LicensePlateDetectionPluginResponse

Bases: TypedDict

Response from a license plate detection test.

MotionDetectionInterface

Bases: Protocol

Interface implemented by plugins that perform video-based motion detection. The host invokes :meth:testMotionDetection from the UI test panel and :meth:detectMotion from automation / benchmark pipelines.

testMotionDetection async

testMotionDetection(video_data: bytes, config: dict[str, Any]) -> MotionDetectionPluginResponse | None

Run detection on a raw video buffer captured by the UI test panel and return the result for preview rendering.

detectMotion async

detectMotion(frames: list[VideoFrameData], config: dict[str, Any] | None = None) -> MotionDetectionPluginResponse | None

Run detection on already-decoded :class:VideoFrameData. Called from automation / benchmark pipelines that supply pre-decoded frames directly to avoid re-encoding.

motionDetectionSettings async

motionDetectionSettings() -> list[JsonSchema] | None

Return the JSON schema used to render the motion-detection settings form in the UI. Return None for no schema.

MotionDetectionPluginResponse

Bases: TypedDict

Response from a motion detection test.

ObjectDetectionInterface

Bases: Protocol

Interface implemented by plugins that perform object detection (person, vehicle, animal, ...).

testObjectDetection async

testObjectDetection(image_data: bytes, metadata: ImageMetadata, config: dict[str, Any]) -> ObjectDetectionPluginResponse | None

Run detection on a single image captured by the UI test panel; metadata carries the image dimensions.

detectObjects async

detectObjects(frame: VideoFrameData, config: dict[str, Any] | None = None) -> ObjectDetectionPluginResponse | None

Run detection on a pre-decoded video frame. Called from automation / benchmark pipelines.

objectDetectionSettings async

objectDetectionSettings() -> list[JsonSchema] | None

Return the JSON schema used to render the object-detection settings form in the UI. Return None for no schema.

ObjectDetectionPluginResponse

Bases: TypedDict

Response from an object detection test.

Notification

Bases: TypedDict

Payload published via api.notificationManager.publish or routed by the host. Plugins fill the user-visible fields; the host stamps the message id, timestamp and source identifier on receive — plugins do not set those.

title instance-attribute

title: str

Headline shown by every notifier.

subtitle instance-attribute

subtitle: NotRequired[str]

Optional second bold line between title and body. Honoured natively on iOS (APNs alert.subtitle); other notifiers may fold it into the body or ignore it.

body instance-attribute

body: NotRequired[str]

Optional secondary text.

severity instance-attribute

severity: NotRequired[Severity]

Drives DND / Critical-Alerts behaviour and Quiet-Hours bypass. Defaults to :attr:Severity.Info if omitted.

tag instance-attribute

tag: NotRequired[str]

Collapse-key for dedup at both manager and notifier level (e.g. motion:cam-1 — multiple events with the same tag inside the throttle window collapse into one notification on the device).

thumbnail instance-attribute

thumbnail: NotRequired[bytes]

Optional inline JPEG attached to the notification.

imageUrl instance-attribute

imageUrl: NotRequired[str]

Publicly-fetchable URL to a rich image (e.g. a detection snapshot). Notifier-agnostic: FCM/APNs and other notifiers fetch it to render the image. Preferred over inline thumbnail bytes when a URL is available; empty renders text-only.

deepLink: NotRequired[str]

Router-relative path consumed by mobile / web tap-handlers (e.g. /cameras/cam-1?startTs=...). No host, no scheme.

data instance-attribute

data: NotRequired[dict[str, str]]

Plugin-specific context (cameraId, eventId, plugin-defined keys). String values keep the wire format predictable across notifier implementations.

adminOnly instance-attribute

adminOnly: NotRequired[bool]

Restricts delivery to users with the master or admin role. Use it for operational alerts that concern whoever runs the instance — camera offline, disk full, plugin failures — so they don't reach guests the instance is merely shared with. Defaults to False (every user of the instance receives it, subject to their own notification settings).

NotifierDevice

Bases: TypedDict

A push-target managed by a notifier plugin (one phone, one chat, ...).

Devices are owned by the plugin that registered them; the manager queries plugins for their device list rather than maintaining a shared registry.

NotifierInterface

Bases: Protocol

Implemented by plugins that deliver notifications.

The NotificationManager invokes these methods over RPC. Plugins own their device storage — the manager never persists devices itself.

get_devices async

get_devices(owner_user_ids: list[str]) -> list[NotifierDevice]

Return every device this notifier knows about for the given users. Each device carries its ownerUserId so the caller can map results back. May return [] when the notifier is unavailable (e.g. license invalid). Called frequently — keep cheap.

get_device async

get_device(device_id: str) -> NotifierDevice | None

Return a single device by id, or None if not found.

send_notification async

send_notification(device_ids: list[str], n: Notification) -> None

Deliver a notification to the given devices in one call. Errors are logged; the manager never aborts a fan-out because one notifier failed.

register_device async

register_device(owner_user_id: str, input: dict[str, Any]) -> NotifierDevice

Create a new device on this notifier. input is plugin-specific JSON whose schema the notifier defines; the NotificationManager forwards it opaquely.

revoke_device async

revoke_device(device_id: str) -> None

Permanently remove a device. Called when the user revokes the device through their notifier-specific UI.

update_device async

update_device(device_id: str, patch: dict[str, Any]) -> NotifierDevice | None

Mutate a subset of fields on an existing device. patch is plugin-agnostic (name, active); plugins ignore unknown keys. Returns the updated device, or None if the id isn't ours so the manager can probe the next plugin.

notificationSettings async

notificationSettings() -> list[JsonSchema] | None

Return the JSON schema used to render the notifier's settings form in the UI. Return None for no schema.

Severity

Bases: str, Enum

Classifies how urgent a Notification is.

Notifiers map this to platform-specific delivery characteristics; the host bypasses user-configured Quiet Hours for Critical.

Info class-attribute instance-attribute

Info = 'info'

Standard notification — default delivery (sound + banner).

Warn class-attribute instance-attribute

Warn = 'warn'

Heightened attention; notifiers may use a different sound/colour.

Error class-attribute instance-attribute

Error = 'error'

Failure or action-required notification.

Critical class-attribute instance-attribute

Critical = 'critical'

Highest-priority delivery on supporting notifiers; bypasses user-configured Quiet Hours on the host.

OAuthAuthCodeFlowCapable

Bases: OAuthCapable, Protocol

Implemented by plugins that use the Authorization Code Flow with PKCE. The host opens the auth URL and forwards the IdP redirect's code+state to completeAuthCodeFlow.

startAuthCodeFlow async

startAuthCodeFlow(scope: list[str]) -> OAuthState

Build the authorization URL for the given scopes; return the awaiting-user state (authUrl set).

completeAuthCodeFlow async

completeAuthCodeFlow(code: str, state: str) -> OAuthState

Exchange the IdP-returned code for tokens after validating state.

cancelAuthCodeFlow async

cancelAuthCodeFlow() -> None

Abort an in-progress authorization-code flow.

OAuthCapable

Bases: Protocol

Base interface every OAuth-capable plugin implements, alongside at least one flow sub-interface. IdP-agnostic — the plugin brings its own endpoint config and knows nothing about the host's internals.

getOAuthMetadata async

getOAuthMetadata() -> OAuthMetadata

Return IdP display info, scope descriptions and the implemented flow sub-interfaces.

getOAuthState async

getOAuthState() -> OAuthState

Return a snapshot of the current lifecycle state; the host polls this to mirror progress.

disconnect async

disconnect() -> None

Revoke the current grant at the IdP and clear stored tokens.

OAuthClientCredentialsCapable

Bases: OAuthCapable, Protocol

Implemented by plugins that authenticate with a user-supplied client_id + client_secret (no user redirect). The plugin validates by fetching a token immediately.

configureClientCredentials async

configureClientCredentials(client_id: str, client_secret: str) -> OAuthState

Store the supplied credentials and fetch an initial token to validate them.

OAuthDeviceFlowCapable

Bases: OAuthCapable, Protocol

Implemented by plugins whose IdP supports the RFC 8628 Device Authorization Grant. The plugin polls the IdP internally; the host only polls getOAuthState to mirror progress.

startDeviceFlow async

startDeviceFlow(scope: list[str]) -> OAuthState

Request a device code for the given scopes and begin polling; return the awaiting-user state.

cancelDeviceFlow async

cancelDeviceFlow() -> None

Abort an in-progress device flow.

OAuthMetadata

Bases: TypedDict

Informational data the host renders in the connect dialog.

idpDisplayName instance-attribute

idpDisplayName: str

Human name of the identity provider, e.g. cameraui.com, Spotify.

scopeDescriptions instance-attribute

scopeDescriptions: dict[str, str]

Maps each scope to a human-readable description.

supportedFlows instance-attribute

supportedFlows: list[PluginInterface]

Flow sub-interfaces the plugin implements, so the host knows which affordance to render.

OAuthProviderConfig

Bases: TypedDict

Points the plugin's OAuth manager at an identity provider.

preset instance-attribute

preset: NotRequired[str]

Built-in IdP endpoint set, e.g. cameraui.com. When unset, the explicit endpoints are used.

deviceAuthUrl instance-attribute

deviceAuthUrl: NotRequired[str]

Device-authorization endpoint (used when preset is unset).

authUrl instance-attribute

authUrl: NotRequired[str]

Authorization endpoint (used when preset is unset).

tokenUrl instance-attribute

tokenUrl: NotRequired[str]

Token endpoint (used when preset is unset).

revokeUrl instance-attribute

revokeUrl: NotRequired[str]

Revocation endpoint (used when preset is unset).

OAuthProviderDeclaration

Bases: TypedDict

One provider a plugin integrates with. A single-provider plugin declares exactly one.

id instance-attribute

id: str

Plugin-local provider identifier (storage-key dimension for multi-provider plugins).

provider instance-attribute

provider: OAuthProviderConfig

IdP endpoint configuration.

clientId instance-attribute

clientId: str

OAuth client id the plugin authenticates as.

scopes instance-attribute

scopes: list[str]

Scopes requested for this provider.

required instance-attribute

required: NotRequired[bool]

Whether the provider is mandatory for the plugin to function.

description instance-attribute

description: NotRequired[str]

One-line UI hint shown alongside the connect button.

OAuthState

Bases: TypedDict

Snapshot of a provider connection's lifecycle. Lives in the plugin and is the source of truth for both the host UI and downstream plugin code that needs a token. The host polls it via getOAuthState while a flow runs.

status instance-attribute

status: OAuthStatus

Current lifecycle phase.

userCode instance-attribute

userCode: NotRequired[str]

Device-flow user code shown to the user (set while awaiting_user).

verificationUri instance-attribute

verificationUri: NotRequired[str]

Device-flow verification URI the user opens (set while awaiting_user).

verificationUriComplete instance-attribute

verificationUriComplete: NotRequired[str]

Verification URI with the user code embedded — rendered as a QR code.

authUrl instance-attribute

authUrl: NotRequired[str]

Authorization-code-flow URL the browser must open (set while awaiting_user).

userEmail instance-attribute

userEmail: NotRequired[str]

Connected account email (set while connected).

connectedAt instance-attribute

connectedAt: NotRequired[int]

Unix timestamp the grant was established (set while connected).

scopesGranted instance-attribute

scopesGranted: NotRequired[list[str]]

Scopes granted by the IdP (set while connected).

errorCode instance-attribute

errorCode: NotRequired[str]

OAuth error code (set while error): access_denied | expired_token | server_error.

errorMessage instance-attribute

errorMessage: NotRequired[str]

Human-readable error detail (set while error).

can_create_cameras

can_create_cameras(contract: PluginContract) -> bool

Report whether the plugin can create cameras (role is CameraController or CameraAndSensorProvider). Used to gate camera-creating operations such as DiscoveryProvider adoption.

Parameters:

Name Type Description Default
contract PluginContract

Plugin contract to inspect.

required

Returns:

Type Description
bool

True if the plugin may create cameras.

Example
if can_create_cameras(contract):
    enable_adoption()

can_provide_sensors_to_any_cameras

can_provide_sensors_to_any_cameras(contract: PluginContract) -> bool

Report whether the plugin is allowed to add sensors to cameras owned by other plugins (true for SensorProvider and CameraAndSensorProvider). Hub and pure CameraController plugins only see their own cameras.

Parameters:

Name Type Description Default
contract PluginContract

Plugin contract to inspect.

required

Returns:

Type Description
bool

True if the plugin may attach sensors to any camera.

Example
if can_provide_sensors_to_any_cameras(contract):
    list_all_cameras()

get_contract_validation_errors

get_contract_validation_errors(contract: object) -> list[str]

Check the structural validity of an unknown contract object — required fields present, enum values inside the accepted sets — and return one human-readable error per problem found. Returns an empty list when the contract is valid.

Parameters:

Name Type Description Default
contract object

Untrusted candidate contract (e.g. parsed manifest JSON).

required

Returns:

Type Description
list[str]

Error messages, empty if the contract is valid.

Example
errors = get_contract_validation_errors(my_contract)
if errors:
    print(f"Invalid contract: {errors}")

has_capability

has_capability(contract: PluginContract, cap: PluginCapability) -> bool

Report whether the plugin requested the given capability.

Parameters:

Name Type Description Default
contract PluginContract

Plugin contract to inspect.

required
cap PluginCapability

Capability to check (e.g. :attr:PluginCapability.PublishNotifications).

required

Returns:

Type Description
bool

True if cap is listed in the contract's capabilities.

Example
if has_capability(contract, PluginCapability.PublishNotifications):
    allow_publish()

has_interface

has_interface(contract: PluginContract, iface: PluginInterface) -> bool

Report whether the plugin implements the given capability.

Parameters:

Name Type Description Default
contract PluginContract

Plugin contract to inspect.

required
iface PluginInterface

Interface to check (e.g. :attr:PluginInterface.DiscoveryProvider).

required

Returns:

Type Description
bool

True if iface is listed in the contract's interfaces.

Example
if has_interface(contract, PluginInterface.DiscoveryProvider):
    start_scan()

is_hub

is_hub(contract: PluginContract) -> bool

Report whether the plugin's role is Hub (a cross-camera aggregator such as a smart-home bridge or recorder, which owns no cameras of its own).

Parameters:

Name Type Description Default
contract PluginContract

Plugin contract to inspect.

required

Returns:

Type Description
bool

True if the role is :attr:PluginRole.Hub.

Example
if is_hub(contract):
    skip_local_discovery()

validate_contract_consistency

validate_contract_consistency(contract: PluginContract, plugin_name: str | None = None) -> None

Enforce role-specific consistency rules on top of the structural check (e.g. SensorProvider plugins must declare at least one provided sensor; Hub plugins cannot expose sensors). Raises on the first violation.

Parameters:

Name Type Description Default
contract PluginContract

Already-structurally-valid contract.

required
plugin_name str | None

Optional plugin name; used to prefix error messages.

None

Raises:

Type Description
ValueError

When the contract violates a role-specific rule.

Example
validate_contract_consistency(contract, "my-plugin")