Skip to content

Camera analytics API

The Analytics tab works through the central request POST /central/api-v4/streams/{stream}/onvif-call/analytics/{method}. An external system uses the same request. Central forwards the call to the streamer that ingests the camera video, and the streamer forwards it to the camera.

Before you start

The request is available to the superadmin and admin roles. The stream must be running, and the camera must be prepared as described in Stream preparation. The examples require Bash, curl, and jq. The responses in the examples come from an Optimus camera; another camera has its own set of types and settings.

export CENTRAL_URL='https://central.example.com'
export API_LOGIN='admin'
export API_PASSWORD='your-password'
export STREAM='entrance'
analytics() {
  curl --fail-with-body --silent --show-error \
    --user "$API_LOGIN:$API_PASSWORD" \
    --header 'Content-Type: application/json' \
    --data "$2" \
    "$CENTRAL_URL/central/api-v4/streams/$STREAM/onvif-call/analytics/$1"
}

A stream name with a slash is passed encoded: cams%2Fentrance.

Methods

Method names and bodies follow the ONVIF Analytics service written as JSON. XML attributes carry the $ prefix; Optic adds the analytics configuration token itself.

  • GetSupportedRules — the camera's rule types, their count, and incompatible scenarios.
  • GetRuleOptions — limits of rule settings.
  • GetRules — the camera's rules.
  • CreateRules, ModifyRules, DeleteRules — create, change, and delete rules.
  • GetServiceCapabilities — what the camera's analytics service supports.
  • GetSupportedAnalyticsModules, GetAnalyticsModuleOptions, GetAnalyticsModules, ModifyAnalyticsModules — analytics modules, for example the motion detector grid and sensitivity.

Other write methods are rejected with the unsupported_method code. Every attempt to write rules is recorded in the central audit log with the stream_onvif.rules_write action.

Camera capabilities

Rule types, their maximum count, and incompatible scenarios:

analytics GetSupportedRules '{}' | jq '.SupportedRules.RuleDescription[]
  | {type: ."$Name", max: ."$maxInstances", excludes: .Extension.Excludes}'
{"type":"tt:LineDetector","max":"4","excludes":["tt:LineCounting","tt:ObjectAbandoned","tt:ObjectRemoved","cus:CrowdDetector","cus:QueueDetector","tt:ObjectDetection","tt:LicensePlateRecognition"]}

Setting limits of one type:

analytics GetRuleOptions '{"RuleType":"tt:LineDetector"}' | jq '.RuleOptions'
[{"$Name":"Direction","$RuleType":"tt:LineDetector","StringList":"Right Left Any"},
 {"$MaxItems":"3","$Name":"ClassFilter","$RuleType":"tt:LineDetector","StringList":"Human Vehicle NonMotorVehicle"},
 {"$Name":"Sensitivity","$RuleType":"tt:LineDetector","IntRange":{"Max":"100","Min":"1"}}]

The streamer keeps the camera capabilities in memory. To read them from the camera again, for example after a firmware update, add the x-onvif-fresh: true header.

Reading rules

analytics GetRules '{}' | jq

In the response, Extension.Executor names the executor: optimus for the Optimus adapter, camera for standard ONVIF. Extension.Clock.UtcOffsetMinutes is the camera clock offset from UTC; the schedule is set in these hours.

Creating, changing, and deleting

Create a line. The rule name <type>_<number> is assigned on creation and returned in RuleName:

analytics CreateRules '{"Rule":[{"$Type":"tt:LineDetector","Parameters":{
  "SimpleItem":[{"$Name":"Armed","$Value":"false"},{"$Name":"Direction","$Value":"Any"}],
  "ElementItem":[{"$Name":"Segments","Polyline":{"Point":[{"$x":-0.5,"$y":0},{"$x":0.5,"$y":0}]}}]}}]}'
{"Extension":{"Executor":"optimus","NotApplied":[]},"RuleName":["LineDetector_2"]}

Arm the line with a weekday schedule from 9 to 14:

analytics ModifyRules '{"Rule":[{"$Name":"LineDetector_2","$Type":"tt:LineDetector","Parameters":{
  "SimpleItem":[{"$Name":"Armed","$Value":"true"}],
  "ElementItem":[{"$Name":"Schedule","HourSchedule":{
    "Mon":[[9,14]],"Tue":[[9,14]],"Wed":[[9,14]],"Thu":[[9,14]],"Fri":[[9,14]],"Sat":[],"Sun":[]}}]}}]}'

If the camera declared incompatible scenarios (Extension.Excludes) and the window overlaps one of them, the write is rejected: the response is 422 schedule_overlap, see Errors.

Delete the line:

analytics DeleteRules '{"RuleName":["LineDetector_2"]}'

The motion detector sensitivity is set in the analytics module:

analytics ModifyAnalyticsModules '{"AnalyticsModule":[{"$Name":"CellMotionEngine_1","$Type":"tt:CellMotionEngine",
  "Parameters":{"SimpleItem":[{"$Name":"Sensitivity","$Value":"6"}]}}]}'

Write rules:

  • Coordinates — from −1 to 1 on both axes, origin at the frame center, the y axis points up.
  • Schedule — an ElementItem named Schedule: all seven days (Mon…Sun), intervals [start, end) in whole hours of the camera clock. An empty day disables the scenario on that day. On Optimus cameras, the schedule is shared by all rules of the type.
  • Permanent rules (type with $fixed="true") — CreateRules is rejected; DeleteRules disarms the rule.
  • ModifyRules — changes only the settings passed.
  • Extension.NotApplied — settings the camera accepted but did not save, see Response extensions.

Response extensions

What the ONVIF standard has no place for, Optic passes as extensions:

  • Extension.Executor — in every response: optimus for the Optimus adapter, camera for standard ONVIF.
  • Extension.Excludes — in a type description in GetSupportedRules: types the camera does not keep enabled in the same hours as this one. Cameras on standard ONVIF do not declare them.
  • Extension.Clock.UtcOffsetMinutes — in GetRules: the camera clock offset from UTC; the schedule is set in these hours.
  • Extension.NotApplied — in write responses: one item per setting the camera accepted but did not save. $Rule is the rule, $Parameter the setting, $Sent the sent value, $Read the value read after writing. An empty list means the camera saved everything.
  • $Source of a setting in a type description: extension — the setting is not in the standard, for example Sensitivity, MinSize, or Schedule; by-analogy — the setting name is taken by analogy with a rule of a similar type.
  • $Unit of a setting:

    • pixel — frame pixels;
    • second — seconds;
    • frame-width — a fraction of the frame width.
    • $fixed="true" of a type — the rule is permanent: it cannot be created or deleted, only armed or disarmed.
    • The cus: prefix — a type or setting outside the ONVIF standard, for example cus:RegionEntranceDetector or cus:HourSchedule.

A type description with extensions:

{"$Name":"cus:RegionEntranceDetector","$maxInstances":"4","Extension":{"Excludes":["tt:LineCounting","tt:ObjectAbandoned","tt:ObjectRemoved","cus:CrowdDetector","cus:QueueDetector","tt:ObjectDetection","tt:LicensePlateRecognition"]},
 "Parameters":{"SimpleItemDescription":[{"$Name":"MinSize","$Type":"xs:int","$Source":"extension","$Unit":"pixel"}],
  "ElementItemDescription":[{"$Name":"Schedule","$Type":"cus:HourSchedule","$Source":"extension"}]}}

A write response in which the camera kept the rule's switch its own way:

{"Extension":{"Executor":"optimus","NotApplied":[{"$Parameter":"Armed","$Read":true,"$Rule":"LineDetector_1","$Sent":false}]}}

Triggers

Rule triggers are stored as episodes with episode_type=trigger. The rule code goes in episode_subtype; separate several codes with commas.

curl --fail-with-body --silent --show-error \
  --user "$API_LOGIN:$API_PASSWORD" \
  "$CENTRAL_URL/central/api-v4/episodes?media=$STREAM&episode_type=trigger&episode_subtype=lcd,enter" | jq
{"episode_id":"7701038866144167249","media":"entrance","opened_at":1793037836008,"closed_at":1793037836008,
 "frame_preview":{"sha256":"5c43636115c17e9cdd6810added59741109f637ce529f4d38e06fd2f341e954a","mime_type":"image/jpeg"},
 "detections":[],"details":{"type":"trigger","rule":"lcd"}}

Rule codes:

  • lcd — line crossing;
  • cc — crossing count;
  • enter — region entrance;
  • exit — region exiting;
  • pid — intrusion;
  • sod — abandoned or removed object;
  • cd — crowd;
  • qd — queue;
  • tamper — tampering.

The trigger frame is served by GET /central/api-v4/episodes/{episode_id}/images/{sha256}, with sha256 taken from frame_preview.

Request headers

  • x-onvif-fresh: true — read the camera capabilities again, bypassing the streamer's memory.
  • x-onvif-auth: user:password — camera credentials instead of the ones from the RTSP URL.
  • x-onvif-port — the camera's HTTP port if it differs from the default.

Errors

  • 400 unsupported_method — a write method outside the analytics list; 400 auth_error — the x-onvif-auth header is not in the user:password form.
  • 401 — invalid Optic credentials.
  • 404 not_found — the stream is not running on any streamer.
  • 409 no_camera_target — the stream has no RTSP source with the camera address.
  • 422 param_error — a value outside the camera limits; 422 schedule_overlap — incompatible scenarios in the same hours. errors[].path points to the field.
  • 502 auth_error — the camera rejected the login and password; 502 onvif_error — the camera returned an error or is unreachable.
  • 503 — the camera is busy or asks to wait; repeat the request later.
  • 504 — the camera did not answer in time. The write may have been applied: read the rules with GetRules before retrying, otherwise a repeated CreateRules creates a second rule.

Example 422 responses:

{"error":{"code":"param_error","message":"tt:LineDetector: MinSize is 8 px, the camera takes 64..1080 px",
  "errors":[{"path":["Rule",0,"Parameters","SimpleItem",4,"$Value"],"message":"tt:LineDetector: MinSize is 8 px, the camera takes 64..1080 px"}]}}
{"error":{"code":"schedule_overlap","message":"tt:LineDetector and tt:LicensePlateRecognition are both active on day 0 from 14:00 to 18:00 by the camera clock; the camera keeps only one of them at a time",
  "errors":[{"path":["Rule",0,"Parameters","ElementItem",0,"HourSchedule"],"message":"…"}]}}

Central does not retry a call on another streamer after a connection loss: the write to the camera may already be done, and a retry would change it twice.