Framework-wide status: which VIS instances are connected, which streams are running, and which detection models are available on them. These endpoints are read-only.
- Reference
- REST API
- Check connectivity to an OpenAI-compatible VLM endpoint
Wowza Video Intelligence Framework API (2.0.0)
This reference documents every endpoint in the Wowza Video Intelligence Framework (VIF) REST API, including the parameters each one takes and the request and response bodies it works with. VIF adds real-time AI video analysis, such as object detection and scene recognition, to streams running on Wowza Streaming Engine, and this API is served by the Video Intelligence Controller (VIC), a module that runs alongside the Engine's own REST interface, on port 8087 by default.
Almost everything you do with this API comes down to one idea: a config, the group of settings that tells VIF how to analyze a stream (which detector to run, what to do with the results, and so on). You'll run into a config in three places, and each one uses it a little differently. A stream group config applies a config to every stream whose name matches a pattern you choose, such as cam.*. An override applies a config to one specific stream, by its exact name instead of a pattern. A running stream reports the config it's actually using right now, alongside its live connection state and performance. Wherever you see one of these documents in the reference below, look for a config member: that's where the settings live. The rest of the document, the match rule, the stream's identity, its live state, just says how that config gets used, and isn't part of the config itself.
Configs also build on each other in layers: a stream's final settings come from a default config, then a matching stream group config, then a per-stream override, then any changes made directly to the running stream. Each layer can fill in whatever the one below it left unset. The Config schema, wherever it appears in this reference, explains exactly how that merge works, including the two members, detector and listeners, that don't merge field by field like the rest.
Endpoints are grouped below by what they work with: durable configuration you save to disk, the live streams currently running, one-off video analysis jobs, connectivity checks, and framework-wide status. Each group's description explains what you'll find in it.
A few things are worth knowing before you make your first request. Every error this API returns is application/problem+json (RFC 7807), so error.title, error.status, and error.detail tell you what went wrong; the exceptions are failures the API never gets to handle, like a URL the Engine can't route or an Engine-level authentication or license failure, which come back in the Engine's own {success, code, message} format instead. Every document you can edit under /persist comes with an ETag header; send that value back as If-Match when you write, and the API answers 428 if you forget it and 412 if it's gone stale, so you never overwrite a change you haven't seen yet. PATCH requests are a JSON Merge Patch (RFC 7386), not a full replacement: send only the fields you want to change, and anything you leave out keeps its current value. Sending a field as null removes it instead, letting it inherit from whatever's underneath, except on a running stream, where null isn't accepted for config or config.active, because a running instance needs values to actually run with; use POST .../reset there if you want to discard your changes instead. The verbs you'll use are GET, POST, PATCH, and DELETE: POST creates a document (409 if one already exists by that name) or triggers an action, like resetting a stream or resuming a job; PATCH edits a document that already exists (404 if it doesn't); DELETE removes one. This API has no PUT. Wherever an endpoint isn't JSON, uploading or downloading a video file, for example, its description says so explicitly; everything else you send or receive is JSON.
Note: This is version 2 of the VIF REST API. Version 1 is deprecated, and we strongly recommend moving any integration that writes configuration to v2. Saving through v1 rewrites your configuration files in an older format: a per-stream override saved that way loses what made it an override, and comes back instead as a stream group config matched to that one stream's literal name. Reading through v1 stays safe at any time; just make sure every integration that writes configuration moves to v2, and that you don't mix the two on one installation.
Engine REST credentials (engineBasic). Requests are additionally subject to the Engine license entitlement (402 without it) and Engine RBAC: the "basic" role is read-only, and the Engine's REST layer answers 403 to every non-GET request from such a user, with the Engine's own JSON error format: {"success":false, "code":403, "message":"You do not have permission to access this resource"}. Both are enforced only while the Engine mounts this API with authentication (server property VideoIntelligenceServer=authorized, the default); unauthorized mounts it with neither credentials nor roles.
The stream instances currently running. A GET here shows a stream's live, resolved configuration alongside its health and performance. Writes are ephemeral: they change only the running instance, are never saved to disk, and are lost if the stream or the Engine restarts. A 404 on any Runtime endpoint means the stream isn't currently running, not that it doesn't exist at all.
The durable configuration that lives on disk whether or not a stream is running: stream group configs, which apply automatically to any stream whose name matches a pattern, and per-stream overrides. Writing here saves the document, and if a running stream is governed by it, applies the change to that stream immediately. You can also write an override for a stream that isn't running yet; it takes effect as soon as the stream starts.
OpenAI-compatible base URL; absent or null uses the reference's effective URL. The reference's stored key applies either way unless the request carries a key of its own.
Names a configured secret to send as the key, resolved now — what a form whose key slot picks a stored secret verifies with, at whatever url it shows. Mutually exclusive with api_key; a name the secrets document does not hold is a 400.
A stored stream group config by name, whose effective VLM endpoint is the probe target: a vlm config's endpoint.
A stage of the chain config stream_group_config names, by name: the probe targets that stage's stored endpoint and key, each member it leaves blank inherited from the default config's vlm baseline. Valid only for a chain config's vlm stage; detector_type, when given, must match the stage's type. Anything else is a 422 whose detail starts with the offending field.
- http://localhost:8087/v2/vif/probes/vlm-endpoint
- curl
- Java
- Python
- JavaScript
curl -i -X POST \
-u <username>:<password> \
'' \
-H 'Content-Type: application/json' \
-d '{
"url": "string",
"api_key": "string",
"api_key_secret": "string",
"stream_group_config": "string",
"application": "string",
"stream": "string",
"stage": "string"
}'{ "reachable": true, "models": [ "string" ], "error": "string" }
Request
The Engine relays the check to the Video Intelligence Service, which owns the gRPC client: the channel is opened the way the analyzer would open it (plaintext, TLS or mutual TLS), then one detection call with an empty body tells whether a Synthetic Video Detector service answered and took the credentials. Unreachable and rejected are results, not errors; the response is a 200 either way. A 502 means the Video Intelligence Service itself did not answer.
gRPC host:port; absent or null uses the reference's effective address, and then its stored credentials unless explicit ones are given. When present, the reference's stored credentials are not used.
A stored stream group config by name, whose effective SVD endpoint is the probe target: a synthetic config's endpoint.
A stage of the chain config stream_group_config names, by name: the probe targets that stage's stored address and credentials, each member it leaves blank inherited from the default config's synthetic baseline. Valid only for a chain config's synthetic stage; detector_type, when given, must match the stage's type. Anything else is a 422 whose detail starts with the offending field.
- http://localhost:8087/v2/vif/probes/svd-endpoint
- curl
- Java
- Python
- JavaScript
curl -i -X POST \
-u <username>:<password> \
'' \
-H 'Content-Type: application/json' \
-d '{
"address": "string",
"tls": {
"enabled": true,
"ca_cert": "string",
"client_cert": "string",
"client_key": "string",
"client_key_secret": "string"
},
"api_key": "string",
"function_id": "string",
"stream_group_config": "string",
"application": "string",
"stream": "string",
"stage": "string"
}'{ "reachable": true, "accepted": true, "code": "string", "error": "string" }
Request
Delivers a single synthetic event — data.vif_data empty and context.options: "viDetectionTest" — so an operator can confirm a consumer is reachable and authenticated without waiting for a detection. Its context is stamped as the stream's real deliveries are, context.entity included: the detector the stream runs, as objects, scenes, motion, vlm, synthetic or chain. Every value but objects is new: before this version every detection event said objects.
Naming the listener (app + stream + listener) sends to its stored url with its stored credential_secret resolved now, under its credential_header — the only way to test a credential the API never echoes. stream is the stream's name, or the pattern a stream group config is keyed by.
A caller-supplied url is sent under the credential_secret and credential_header beside it in the request, resolved the same way — so a form can test what it shows before it is saved. Without a credential_secret it goes unauthenticated.
Unreachable is a result, not an error — the response is 200 with delivered: false — so 400 means the request named nothing to send to, a url that is not an absolute http(s) URL with a host, or a credential_header that is not a legal header name.
The stream, by name — or by the stream pattern a stream group config is keyed by, which is how a group's listeners are addressed before any stream runs.
An absolute http(s) URL with a host, tested instead of the reference's stored destination, under the request's own credential_secret. With app and stream beside it the test event is stamped as that stream's deliveries are; without them its context carries every member a delivery does, with empty app, appInstance and stream.
With url: the name of the secret the test event authenticates with. The Engine resolves it and sends its value under credential_header, as a delivery would. Ignored without url.
- http://localhost:8087/v2/vif/probes/webhook
- curl
- Java
- Python
- JavaScript
curl -i -X POST \
-u <username>:<password> \
'' \
-H 'Content-Type: application/json' \
-d '{
"app": "string",
"stream": "string",
"listener": "string",
"url": "string",
"credential_secret": "string",
"credential_header": "string"
}'Probe result (delivered or not).
What the event travelled under: sent for the resolved credential, none when the listener, or the request beside its url, names no secret, unresolved when the named secret is not configured — the event went without one, so a 401 here is the missing secret, not the consumer's token.
{ "delivered": true, "status": 0, "error": "string", "credential": "sent" }
Request
Delivers a single synthetic status_changed event — the shape every job transition posts, with test: true beside it and no results — so an operator can confirm the lifecycle consumer is reachable and authenticated without submitting a job. Its job_id names no job, and its state is running, the state a job's first event carries.
Without a url it goes where the VOD settings send every job's notifications: their lifecycle_webhook, with any ${ENV} placeholder resolved, under their lifecycle_webhook_secret and lifecycle_webhook_header. With a url it goes there instead, under the credential_secret and credential_header beside it in the request — what a form shows before it is saved.
One attempt, no retries. Unreachable is a result, not an error — the response is 200 with delivered: false — so 400 means there is nothing to send to (no url and no configured lifecycle_webhook), a destination that is not an absolute http(s) URL with a host, or a credential_header that is not a legal header name.
An absolute http(s) URL with a host, tested instead of the settings' lifecycle_webhook, under the request's own credential_secret. A ${ENV} placeholder is not resolved here; leave url out to test the configured one.
With url: the name of the secret the test event authenticates with, resolved and sent under credential_header as a notification would. Ignored without url.
- http://localhost:8087/v2/vif/probes/lifecycle-webhook
- curl
- Java
- Python
- JavaScript
curl -i -X POST \
-u <username>:<password> \
'' \
-H 'Content-Type: application/json' \
-d '{
"url": "string",
"credential_secret": "string",
"credential_header": "string"
}'Probe result (delivered or not).
What the event travelled under: sent for the resolved credential, none when the listener, or the request beside its url, names no secret, unresolved when the named secret is not configured — the event went without one, so a 401 here is the missing secret, not the consumer's token.
{ "delivered": true, "status": 0, "error": "string", "credential": "sent" }
On-demand analysis: the video files under the Engine's content directory, and the jobs that analyze them. Unlike a running stream, a job outlives its analysis and is neither a config nor a stream. It just names the config it should use when you submit it, and keeps a record of what it actually used once it's done.