Skip to main content

opennvr-app-sdk

The SDK for building vision apps on OpenNVR, the self-hosted AI video platform. Cameras, Tier-0 detections, pluggable inference, an event bus, recordings, an operator alert inbox, users and per-camera permissions, and a catalog every deployment opens — all already running. You write the rule, the model or the workflow; this package is the only import you need.

Apache-2.0 — ship your app under any licence, closed included, with no fee to OpenNVR. Why that holds even though the platform core is AGPL: LICENSING.md.

pip install opennvr-app-sdk

An app in one function

from opennvr_app_sdk import App

app = App("loitering", name="Loitering", version="1.0.0", category="perimeter")
app.param("dwell_s", float, default=30.0)
app.metric("alerted", label="Alerts fired")

@app.on_detection("person", zone="driveway", dwell="$dwell_s")
def loitering(event):
    event.alert(f"Person loitering on {event.camera}", severity="high")
    app.store["alerted"] = app.store.get("alerted", 0) + 1

if __name__ == "__main__":
    raise SystemExit(app.run())

That app subscribes to the platform's detections, serves the registry contract (/health, /state, /manifest, config form, actions), registers itself in the App Catalog, is issued its own credential, and fires alerts that reach the operator inbox — none of which you wrote. The zone becomes an editor named driveway that the operator draws on a camera still; dwell_s becomes a field in the config form they can tune; alerted becomes a tile on the app's dashboard. The presence timer, the once-per-episode latch, the alert envelope and the config file are the SDK's.

Declaring a surface implements it: @app.action adds an operator button, @app.ui an embedded page, @app.on_license the gate for a paid app, @app.state / app.metric a dashboard, and inside a rule event.publish() reaches other apps while event.nvr is the platform.

See it fire before you touch Docker:

opennvr-app new loitering        # a runnable app + tests
opennvr-app dev                  # run it against a simulated camera
opennvr-app validate .           # what a reviewer would check
opennvr-app spec                 # its OpenAPI 3.1 document

Your app self-describes: the contract server it gets for free serves OpenAPI 3.1 at /openapi.json and AsyncAPI 3.0 at /asyncapi.json, both generated from your manifest — a declared action is a path with a typed body, a declared param is a schema. Point Swagger UI or a client generator at a running app and it works.

The reference

opennvr.org/sdk — every export, in tiers, generated from these docstrings. Start at the front door: six names, and most apps need nothing else.

One example per class

cookbook/ is a runnable file per class — how it is constructed or subclassed, and which APIs it uses. Every file is imported and exercised by the test suite, so an example that references a name the SDK no longer exports breaks in CI rather than misleading you months later. Start at 01_app_facade.py.

…or the class underneath it

App compiles to a Detector. When a rule outgrows the decorators, write the Detector directly — same process, same manifest, same alerts:

from opennvr_app_sdk import Alert, AppManifest, Detector, Param, app

class Loitering(Detector):
    manifest = AppManifest(
        id="loitering", name="Loitering", version="1.0.0", category="perimeter",
        requires_tasks=["object_detection"],
        params=[Param("dwell_s", float, default=30.0)],
    )

    def setup(self):
        self.present = self.keyed_state(ttl=10.0)   # forgets a camera 10 s after the last person

    def on_detections(self, camera_id, detections, event):
        people = [d for d in detections if d.get("label") == "person"]
        if not people:
            return
        rec = self.present.touch(camera_id)
        if rec.age >= self.cfg.dwell_s and not rec.alerted:
            rec.alerted = True
            yield Alert(title="Loitering", description=f"{len(people)} person(s) for {rec.age:.0f}s",
                        camera_id=camera_id, severity="medium")

if __name__ == "__main__":
    raise SystemExit(app(Loitering).run())

FrameApp drives its own inference, AlertSubscriber consumes the alert bus and DomainEventSubscriber consumes contracted domain events — same shape, different input.

The platform, from an app

from opennvr_app_sdk import OpenNVR            # or opennvr_app_sdk.aio.AsyncOpenNVR

nvr = OpenNVR()                                 # OPENNVR_URL + the app's key
for cam in nvr.cameras():                       # only cameras assigned to this app
    jpeg = nvr.snapshot(cam)
    det = nvr.ai.infer("yolov8", jpeg, task="object_detection", camera_id=cam.handle)
nvr.state.set("last_seen", {"cam1": 12.5})      # durable, survives restarts
clips = nvr.recordings("cam1").list(start="2026-09-05T00:00:00Z")
mine = nvr.alerts.inbox(unacked=True)

Where to go next

Compatibility: the app-registry contract is versioned (api_version) and tested in the OpenNVR repository on every change; min_sdk_version only moves when an older SDK would misbehave. Issues tagged sdk at open-nvr/open-nvr.

Metadata

Release files for opennvr-app-sdk 0.8.0

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

Source distribution (sdist)

Source distribution for opennvr-app-sdk 0.8.0
File Size Uploaded
opennvr_app_sdk-0.8.0.tar.gz 269.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for opennvr-app-sdk 0.8.0
File Interpreter ABI Platform
opennvr_app_sdk-0.8.0-py3-none-any.whl Python 3 none any Details

Total release size: 472.6 kB

Release files / opennvr_app_sdk-0.8.0.tar.gz

Download URL opennvr_app_sdk-0.8.0.tar.gz
Size 269.1 kB
Tags Source
SHA-256 checksum
How to use checksums
b787f49c4d59190add5bbcd1c25e25031c83a61e092ace3fb35787edc2bb8825
BLAKE2b-256 checksum
How to use checksums
1927e7d4aae78dced05670917f397b60a59b828ef1e969f9a5225fffeb6ebe19
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 14, 2026.

Transparency log

Release files / opennvr_app_sdk-0.8.0-py3-none-any.whl

Download URL opennvr_app_sdk-0.8.0-py3-none-any.whl
Size 203.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ab1e07e7ea0a9aac7bc43f95b38caf6fb58a67d0edd4826015de893066938aea
BLAKE2b-256 checksum
How to use checksums
bbf4f337b2f75f4a3d6aaafd312609e4dbfda55d9da4c1e832b4070ba1c3e6b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.8.0 This release

2 release files

0.7.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

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