Specs your app publishes¶
Every OpenNVR app self-describes in the two standards its consumers
already have tooling for. Both documents are generated from the app's
own AppManifest, so they cannot drift from the app: a declared
Action is a path, a declared Param is a schema.
opennvr-app spec # OpenAPI 3.1, JSON, stdout
opennvr-app spec --format asyncapi # the NATS surface
opennvr-app spec --yaml -o api.yaml # for a docs site or a client generator
curl http://my-app:9210/openapi.json # …or ask a running app
OpenAPI 3.1 — the HTTP surface¶
Served at GET /openapi.json on the app's contract port.
| Path | Appears when | Purpose |
|---|---|---|
GET /health |
always | Liveness plus pipeline vitals; drives the catalog's status dot. |
GET /manifest |
always | The declarative identity core stores at registration. |
GET /state |
always | Live standing state, documented by the manifest's state_schema. |
GET /ui |
has_ui + ui_mode="internal" |
The embedded dashboard, rendered sandboxed. |
POST /actions/{name} |
one per declared Action |
Operator verbs. User-JWT only, via core's proxy. |
POST /entitlement/verify |
entitlement="license_key" |
Your licence check. |
Param typing: Python types map to their JSON equivalents; the catalog's
UI types map to the shape they carry on the wire plus an x-opennvr-ui
hint, so a generic consumer still sees something usable.
geometry.polygon becomes an array of normalized [x, y] vertices;
per_camera=True becomes an object keyed by camera id.
AsyncAPI 3.0 — the bus surface¶
Served at GET /asyncapi.json. Three groups of channels, all derived
from the manifest:
- what the app receives —
subscribes, typed by its subject tree, because aDetectoronopennvr.inference.>, aDomainEventSubscriberonopennvr.events.plate.recognized.v1.>and anAlertSubscriberonopennvr.alerts.>carry three different envelopes; - what it sends —
opennvr.alerts.app.<id>.{camera_id}, with the declared alert types and severities; - what it requests — one channel per
requires_scopesentry, since a scope is how an app asks for PII-bearing domain events, and that belongs in the spec rather than in prose.
The rest of the platform¶
Core, KAI-C and every AI adapter are FastAPI and publish OpenAPI 3.1 at
/openapi.json, with Swagger UI at /docs. The full map, including
versioning rules and the deliberate non-goals, is
API_STANDARDS.md.
opennvr_app_sdk.openapi
¶
Machine-readable specs for an app's own surfaces.
OpenNVR's server-side APIs are FastAPI, so they already publish
OpenAPI 3.1 at /openapi.json (core, KAI-C, every AI adapter)
with Swagger UI at /docs. The one surface that had no spec was the
one app authors actually implement: the app contract server in
:mod:~.contract, which is a stdlib http.server and so generates
nothing.
This module closes that. It derives both specs from the app's own
:class:~.manifest.AppManifest, which means they cannot drift from the
app — a declared action IS a path, a declared param IS a schema, and an
app that declares no licence gate has no /entitlement/verify path:
- :func:
contract_openapi— OpenAPI 3.1 for the HTTP surface the app serves (/health,/manifest,/state,/ui,/actions/{name},/entitlement/verify). - :func:
contract_asyncapi— AsyncAPI 3.0 for the NATS surface the app consumes and produces (the inference broadcast it subscribes to, the alert subjects it publishes on, the contracted domain events itsrequires_scopesgrant).
Both are served by the contract server itself at GET /openapi.json
and GET /asyncapi.json, and both are printable from the CLI::
opennvr-app spec # OpenAPI, to stdout
opennvr-app spec --format asyncapi # AsyncAPI
opennvr-app spec -o openapi.json # to a file
Which means every OpenNVR app self-describes in the two standards its consumers already have tooling for: an operator can point Swagger UI or an SDK generator at a running app, and a bus consumer can generate a typed client from the AsyncAPI document.